我正在尝试从页面上先前加载的产品列表中加载不同颜色的列表。因此,要引入产品,我要做的是:
var products = Products
.Include(p => p.ProductColor)
.ToList();
然后,我要对产品进行一些处理,以获取产品使用的所有不同颜色的列表,因此我要这样做:
var colors = products
.Select(p => p.ProductColor)
.Distinct();
这很有效,但是,如果我将
.AsNoTracking()
的调用添加到原始产品调用中,则现在我在颜色列表中为该产品列表中的每个条目获得一个条目。为什么这两者有区别?有没有一种方法可以防止Entity Framework跟踪对象(它们仅用于读取对象)并获得所需的行为?
这是将调用添加到
AsNoTracking()
后的查询var products = Products
.AsNoTracking()
.Include(p => p.ProductColor)
.ToList();
最佳答案
AsNoTracking
“breaks” Distinct
,因为AsNoTracking
“breaks”身份映射。由于加载了AsNoTracking()
的实体不会附加到上下文缓存中,EF会为查询返回的每一行具体化新实体,而启用跟踪后,它将检查上下文中是否已经存在具有相同键值的实体,如果是, ,它不会创建新对象,而仅使用附加的对象实例。
例如,如果您有2种产品,并且两者均为绿色:
AsNoTracking()
,则查询将实现3个对象:2个Product
对象和1个ProductColor
对象(绿色)。产品1对Green的引用(在ProductColor
属性中),产品2对同一对象实例Green的引用,即object.ReferenceEquals(product1.ProductColor, product2.ProductColor) == true
AsNoTracking()
,您的查询将实现4个对象:2个产品对象和2个颜色对象(均表示Green并具有相同的键值)。产品1对Green的引用(在ProductColor
属性中),产品2对Green的引用,但这是另一个对象实例,即object.ReferenceEquals(product1.ProductColor, product2.ProductColor) == false
现在,如果在内存中的集合(LINQ-to-Objects)上调用
Distinct()
,则不带参数的Distinct()
的默认比较就是比较对象引用身份。因此,在情况1中,您只会得到1个绿色对象,但是在情况2中,您将得到2个绿色对象。为了在使用
AsNoTracking()
运行查询之后获得所需的结果,您需要通过实体键进行比较。您可以使用Distinct
的第二个重载,该重载将IEqualityComparer
作为参数。其实现的示例是here,您可以使用ProductColor
的key属性来比较两个对象。或者-对我而言,这比单调乏味的
IEqualityComparer
实现更容易-您可以使用Distinct()
(将GroupBy
键属性作为分组键)重写ProductColor
:var colors = products
.Select(p => p.ProductColor)
.GroupBy(pc => pc.ProductColorId)
.Select(g => g.First());
First()
基本上意味着您将丢弃所有重复项,并仅保留每个键值的第一个对象实例。