为什么,在 Entity Framework 6 中,当将域模型复制到另一个域模型时,原始对象是否会在更改副本的值时更新?
Why, in Entity Framework 6, when copying a domain model to another domain model, does the original object get updated when changing the copy's values?
以下是此示例所需的模型。
public class OrderDetailPackageVM
{
public OrderDetail OrderDetail { get; set; }
public Package Package { get; set; }
}
public class Package
{
public Package()
{
this.PackageProducts = new List<PackageProduct>();
}
public int PackageId { get; set; }
public int WebsiteId { get; set; }
public virtual List<PackageProduct> PackageProducts { get; set; }
}
public class PackageProduct
{
public int PackageProductId { get; set; }
public int PackageId { get; set; }
public virtual Package Package { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public int ProductCategoryId { get; set; } // not a FK but data only
public virtual ProductCategory ProductCategory { get; set; }
}
在下面的代码片段中,您应该会看到说明的问题。
List<OrderDetailPackageVM> pkgs = (from odx in db.OrderDetails
from pax in db.Packages
where odx.OrderId == orderId
&& pax.PackageId == odx.PackageId
&& odx.PricelistProduct.Product.isStandalone == true
&& pax.WebsiteId == websiteId
select new OrderDetailPackageVM
{
Package = pax,
OrderDetail = odx
}).AsNoTracking().ToList();
List<OrderDetailPackageVM> packages = new List<OrderDetailPackageVM>();
packages.AddRange(pkgs);
//also tried packages = pkgs;
//also tried packages.injectFrom(pkgs) //from omu valueInjector - similar to automapper
此时在我的手表中我们看到:
pkgs.Package.PackageProducts.Count = 6;
packages.Package.PackageProducts.Count = 6;
foreach (OrderDetailPackageVM pac in packages)
{
pac.Package.PackageProducts.RemoveAll();
}
此时在我的手表中我们看到:
pkgs.Package.PackageProducts.Count = 0;
packages.Package.PackageProducts.Count = 0;
当我期待看到:
pkgs.Package.PackageProducts.Count = 6;
packages.Package.PackageProducts.Count = 0;
那么为什么在将更改应用到副本时原始对象会发生变化。我不记得早期版本的 EF 中有这种行为?
解决这个问题的方法是什么?
我认为使用 NoTracking 进行 select 应该 'Free' 来自 EF 更改跟踪的模型中的数据?
非常感谢您帮助我理解这种行为。
以下是我根据以下反馈解决此问题的方法:
public static T DeepClone<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
}
您没有创建新对象。您将现有对象放在新列表中。您需要创建全新的对象并手动复制值。这也称为深层复制或克隆(参见 ICloneable)。
如果 omu valueInjector 将 属性 分配给 属性,它会将对象 a 的列表分配给对象 b 的列表。因为是引用类型,所以其实是一样的。如果您想拥有新对象,则必须进行深拷贝。有关详细信息,请参阅 Deep cloning objects。
该行为实际上与从 EF 视图跟踪更改无关。您使用引用类型。
一个小示例程序:
using System;
using System.Collections.Generic;
namespace _28637405 {
class Outer {
public string MyProperty { get; set; }
}
class Program {
static void Main( string[] args ) {
var listOne = new List<Outer>();
for ( int i = 0; i < 10; i++ ) {
listOne.Add( new Outer { MyProperty = "obj #" + (i + 1) } );
}
// first line
Console.WriteLine( listOne[0].MyProperty );
var listTwo = new List<Outer>();
listTwo.AddRange( listOne );
// second line
Console.WriteLine( listTwo[0].MyProperty );
listTwo[0].MyProperty = "Changed";
// third and fourth line
Console.WriteLine( listOne[0].MyProperty );
Console.WriteLine( listTwo[0].MyProperty );
var listThree = new List<Outer>();
foreach ( var obj in listOne )
listThree.Add( new Outer { MyProperty = obj.MyProperty } );
listThree[0].MyProperty += " again";
// lines 5,6,7
Console.WriteLine( listOne[0].MyProperty );
Console.WriteLine( listTwo[0].MyProperty );
Console.WriteLine( listThree[0].MyProperty );
}
}
}
它产生的输出:
obj #1
obj #1
Changed
Changed
Changed
Changed
Changed again
class 外部如果实现 ICloneable
:
看起来像这样
class Outer : ICloneable {
public string MyProperty { get; set; }
public object Clone() {
return new Outer { MyProperty = this.MyProperty };
}
}
用法是(包括转换为 Outer
):
var newObject = existingObject.Clone() as Outer;
以下是此示例所需的模型。
public class OrderDetailPackageVM
{
public OrderDetail OrderDetail { get; set; }
public Package Package { get; set; }
}
public class Package
{
public Package()
{
this.PackageProducts = new List<PackageProduct>();
}
public int PackageId { get; set; }
public int WebsiteId { get; set; }
public virtual List<PackageProduct> PackageProducts { get; set; }
}
public class PackageProduct
{
public int PackageProductId { get; set; }
public int PackageId { get; set; }
public virtual Package Package { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public int ProductCategoryId { get; set; } // not a FK but data only
public virtual ProductCategory ProductCategory { get; set; }
}
在下面的代码片段中,您应该会看到说明的问题。
List<OrderDetailPackageVM> pkgs = (from odx in db.OrderDetails
from pax in db.Packages
where odx.OrderId == orderId
&& pax.PackageId == odx.PackageId
&& odx.PricelistProduct.Product.isStandalone == true
&& pax.WebsiteId == websiteId
select new OrderDetailPackageVM
{
Package = pax,
OrderDetail = odx
}).AsNoTracking().ToList();
List<OrderDetailPackageVM> packages = new List<OrderDetailPackageVM>();
packages.AddRange(pkgs);
//also tried packages = pkgs;
//also tried packages.injectFrom(pkgs) //from omu valueInjector - similar to automapper
此时在我的手表中我们看到: pkgs.Package.PackageProducts.Count = 6; packages.Package.PackageProducts.Count = 6;
foreach (OrderDetailPackageVM pac in packages)
{
pac.Package.PackageProducts.RemoveAll();
}
此时在我的手表中我们看到:
pkgs.Package.PackageProducts.Count = 0;
packages.Package.PackageProducts.Count = 0;
当我期待看到:
pkgs.Package.PackageProducts.Count = 6;
packages.Package.PackageProducts.Count = 0;
那么为什么在将更改应用到副本时原始对象会发生变化。我不记得早期版本的 EF 中有这种行为?
解决这个问题的方法是什么?
我认为使用 NoTracking 进行 select 应该 'Free' 来自 EF 更改跟踪的模型中的数据?
非常感谢您帮助我理解这种行为。
以下是我根据以下反馈解决此问题的方法:
public static T DeepClone<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
}
您没有创建新对象。您将现有对象放在新列表中。您需要创建全新的对象并手动复制值。这也称为深层复制或克隆(参见 ICloneable)。
如果 omu valueInjector 将 属性 分配给 属性,它会将对象 a 的列表分配给对象 b 的列表。因为是引用类型,所以其实是一样的。如果您想拥有新对象,则必须进行深拷贝。有关详细信息,请参阅 Deep cloning objects。
该行为实际上与从 EF 视图跟踪更改无关。您使用引用类型。
一个小示例程序:
using System;
using System.Collections.Generic;
namespace _28637405 {
class Outer {
public string MyProperty { get; set; }
}
class Program {
static void Main( string[] args ) {
var listOne = new List<Outer>();
for ( int i = 0; i < 10; i++ ) {
listOne.Add( new Outer { MyProperty = "obj #" + (i + 1) } );
}
// first line
Console.WriteLine( listOne[0].MyProperty );
var listTwo = new List<Outer>();
listTwo.AddRange( listOne );
// second line
Console.WriteLine( listTwo[0].MyProperty );
listTwo[0].MyProperty = "Changed";
// third and fourth line
Console.WriteLine( listOne[0].MyProperty );
Console.WriteLine( listTwo[0].MyProperty );
var listThree = new List<Outer>();
foreach ( var obj in listOne )
listThree.Add( new Outer { MyProperty = obj.MyProperty } );
listThree[0].MyProperty += " again";
// lines 5,6,7
Console.WriteLine( listOne[0].MyProperty );
Console.WriteLine( listTwo[0].MyProperty );
Console.WriteLine( listThree[0].MyProperty );
}
}
}
它产生的输出:
obj #1
obj #1
Changed
Changed
Changed
Changed
Changed again
class 外部如果实现 ICloneable
:
class Outer : ICloneable {
public string MyProperty { get; set; }
public object Clone() {
return new Outer { MyProperty = this.MyProperty };
}
}
用法是(包括转换为 Outer
):
var newObject = existingObject.Clone() as Outer;