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

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()基本上意味着您将丢弃所有重复项,并仅保留每个键值的第一个对象实例。

    10-07 13:55