entity frameworkAsNoTracking中断了对Distinct的调用
我正在尝试从以前加载的页面上的产品列表中加载不同颜色的列表。 所以我要这样做:
var products = Products .Include(p => p.ProductColor) .ToList();
然后我对产品进行一些处理我想得到产品使用的所有不同颜色的列表,所以我这样做:
var colors = products .Select(p => p.ProductColor) .Distinct();
这非常.AsNoTracking()
,但是如果我将.AsNoTracking()
的调用添加到原始产品调用中,我现在会在产品列表中的每个条目的颜色列表中获得一个条目。
为什么这两个有区别? 有没有办法让Entity Framework不跟踪对象(它们被用于只读)并获得所需的行为?
将调用添加到AsNoTracking()
后,这是我的查询
var products = Products .AsNoTracking() .Include(p => p.ProductColor) .ToList();
AsNoTracking
“打破” Distinct
因为AsNoTracking
“打破”身份映射。 由于使用AsNoTracking()
加载的实体不会附加到上下文缓存,因此为查询返回的每一行实现新实体,而当启用跟踪时,它将检查上下文中是否已存在具有相同键值的实体如果是,它将不会创建新对象,而只是使用附加的对象实例。
例如,如果您有2个产品且都是绿色:
-
如果没有
AsNoTracking()
您的查询将实现3个对象:2个Product
对象和1个ProductColor
对象(绿色)。 产品1引用了Green(在ProductColor
属性中),Product 2引用了相同的对象实例 Green,即object.ReferenceEquals(product1.ProductColor, product2.ProductColor) == true
-
使用
AsNoTracking()
您的查询将实现4个对象:2个产品对象和2个颜色对象(两者都代表绿色并具有相同的键值)。 产品1引用了Green(在ProductColor
属性中),Product 2引用了Green,但这是另一个对象实例 ,即object.ReferenceEquals(product1.ProductColor, product2.ProductColor) == false
现在,如果在内存中的集合(LINQ-to-Objects Distinct()
上调用Distinct()
,则不带参数的Distinct()
的默认比较是比较对象引用标识。 因此,在案例1中,您只获得1个绿色对象,但在案例2中,您将获得2个绿色对象。
要在使用AsNoTracking()
运行查询后获得所需结果,您需要通过实体键进行比较。 您可以使用Distinct
的第二个重载,它将IEqualityComparer
作为参数。 它的实现示例在这里 ,您将使用ProductColor
的key属性来比较两个对象。
或者 – 这对我来说比繁琐的IEqualityComparer
实现更容易 – 您使用GroupBy
重写Distinct()
(使用ProductColor
键属性作为分组键):
var colors = products .Select(p => p.ProductColor) .GroupBy(pc => pc.ProductColorId) .Select(g => g.First());
First()
基本上意味着你抛弃所有重复项,只保留每个键值的第一个对象实例。