简体   繁体   English

实体框架AsNoTracking中断了对Distinct的调用

[英]Entity Framework AsNoTracking breaks call to Distinct

I am trying to load a list of distinct colors from previously loaded list of products on a page. 我正在尝试从页面上先前加载的产品列表中加载不同颜色的列表。 So to pull in the products I do this: 因此,要引入产品,我需要这样做:

var products = Products
    .Include(p => p.ProductColor)
    .ToList();

Then I do some processing on the products them I want to get a list of all of the distinct colors used by the products, so I do this: 然后,我要对产品进行一些处理,以获取产品使用的所有不同颜色的列表,因此我要这样做:

var colors = products   
    .Select(p => p.ProductColor)
    .Distinct();

And this works great, however if I add a call to .AsNoTracking() to the original products call, I now get an entry in my color list for each entry in the product list. 这很有效,但是,如果我.AsNoTracking()的调用添加到原始产品调用中,那么现在我在颜色列表中为该产品列表中的每个条目获取一个条目。

Why is there a difference in these two? 为什么这两者有区别? Is there a way to keep Entity Framework from tracking the objects (they're being used for read only) and to get the desired behavior? 有没有一种方法可以防止Entity Framework跟踪对象(它们仅用于读取对象)并获得所需的行为?

Here is my query after adding the call to AsNoTracking() 这是将调用添加到AsNoTracking()之后的查询

var products = Products
    .AsNoTracking()
    .Include(p => p.ProductColor)
    .ToList();

AsNoTracking "breaks" Distinct because AsNoTracking "breaks" identity mapping. AsNoTracking “中断”身份Distinct因为AsNoTracking “中断”身份映射。 Since entities loaded with AsNoTracking() won't get attached to the context cache EF materializes new entities for every row returned from the query whereas when tracking is enabled it would check if an entity with the same key value does already exist in the context and if yes, it wouldn't create a new object and just use the attached object instance instead. 由于加载有AsNoTracking()实体不会附加到上下文缓存,因此EF为查询返回的每一行具体化新实体,而启用跟踪时,它将检查上下文中是否已经存在具有相同键值的实体,并且如果是,则不会创建新对象,而仅使用附加的对象实例。

For example, if you have 2 products and both are Green: 例如,如果您有2种产品,并且两者均为绿色:

  • Without AsNoTracking() your query will materialize 3 objects: 2 Product objects and 1 ProductColor object (Green). 如果没有AsNoTracking()查询将实现3个对象:2个Product对象和1个ProductColor对象(绿色)。 Product 1 has a reference to Green (in ProductColor property) and Product 2 has a reference to the same object instance Green, ie 产品1引用了Green(在ProductColor属性中),产品2引用了相同的对象实例 Green,即

     object.ReferenceEquals(product1.ProductColor, product2.ProductColor) == true 
  • With AsNoTracking() your query will materialize 4 objects: 2 product objects and 2 color objects (both represent Green and have the same key value). 使用AsNoTracking()您的查询将实现4个对象:2个产品对象和2个颜色对象(均表示Green并具有相同的键值)。 Product 1 has a reference to Green (in ProductColor property) and Product 2 has a reference to Green but this is another object instance , ie 产品1引用了Green(在ProductColor属性中),产品2引用了Green,但这是另一个对象实例 ,即

     object.ReferenceEquals(product1.ProductColor, product2.ProductColor) == false 

Now, if you call Distinct() on a collection in memory (LINQ-to-Objects) the default comparison for Distinct() without parameter is comparing object reference identities. 现在,如果在内存中的集合(LINQ到对象Distinct()上调用Distinct() ,则不带参数的Distinct()的默认比较就是比较对象引用标识。 So, in case 1 you get only 1 Green object, but in case 2 you'll get 2 Green objects. 因此,在情况1中,您只会得到1个绿色对象,但在情况2中,您将得到2个绿色对象。

To get the desired result after you have run the query with AsNoTracking() you need a comparison by the entity key. 为了在使用AsNoTracking()运行查询之后获得所需的结果,您需要通过实体键进行比较。 You can either use the second overload of Distinct which takes an IEqualityComparer as parameter. 您可以使用Distinct的第二个重载,该重载以IEqualityComparer作为参数。 An example for its implementation is here and you would use the key property of ProductColor to compare two objects. 此处是实现该示例的示例,您可以使用ProductColor的key属性来比较两个对象。

Or - which seems easier to me than the tedious IEqualityComparer implementation - you rewrite the Distinct() using a GroupBy (with the ProductColor key property as the grouping key): 或者-在我看来,这比乏味的IEqualityComparer实现要容易-您可以使用GroupBy (将ProductColor键属性作为分组键)重写Distinct() ):

var colors = products   
    .Select(p => p.ProductColor)
    .GroupBy(pc => pc.ProductColorId)
    .Select(g => g.First());

The First() basically means that you are throwing all duplicates away and just keep the first object instance per key value. First()基本上意味着您将丢弃所有重复项,并且只保留每个键值的第一个对象实例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM