10

我正在尝试从页面上先前加载的产品列表中加载不同颜色的列表。因此,为了引入产品,我这样做:

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

然后我对产品进行一些处理,我想获得产品使用的所有不同颜色的列表,所以我这样做:

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

这很好用,但是如果我.AsNoTracking()在原始产品调用中添加调用,我现在会在产品列表中的每个条目的颜色列表中获得一个条目。

为什么这两者有区别?有没有办法让实体框架不跟踪对象(它们被用于只读)并获得所需的行为?

这是我在添加调用后的查询AsNoTracking()

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

1 回答 1

22

AsNoTracking“中断”Distinct是因为AsNoTracking“中断”身份映射。由于加载的实体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 个颜色对象(都代表绿色并具有相同的键值)。产品 1 引用了绿色(在ProductColor属性中),产品 2 引用了绿色,但这是另一个对象实例,即

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

现在,如果您调用Distinct()内存中的集合(LINQ-to-Objects),则无Distinct()参数的默认比较是比较对象引用标识。因此,在情况 1 中,您只获得 1 个绿色对象,但在情况 2 中,您将获得 2 个绿色对象。

要在运行查询后获得所需的结果,AsNoTracking()您需要通过实体键进行比较。您可以使用Distinct带有IEqualityCompareras 参数的第二个重载。它的实现示例在这里,您将使用 key 属性ProductColor来比较两个对象。

或者 - 这对我来说似乎比繁琐的IEqualityComparer实现更容易 - 你重写Distinct()使用 a GroupBy(使用ProductColorkey 属性作为分组键):

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

First()基本上意味着您将丢弃所有重复项,只保留每个键值的第一个对象实例。

于 2013-06-14T22:50:53.003 回答