我正在尝试从以前加载的页面上的产品列表中加载不同颜色的列表。因此,为了吸引我的产品:
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();
答案 0 :(得分:19)
AsNoTracking
“中断”Distinct
,因为AsNoTracking
“打破”身份映射。由于加载了AsNoTracking()
的实体不会附加到上下文缓存,因此EF为查询返回的每一行实现新实体,而当启用跟踪时,它将检查具有相同键值的实体是否已存在于上下文,如果是,它将不会创建一个新对象,而只是使用附加的对象实例。
例如,如果您有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()
的默认比较是比较对象引用标识。因此,在案例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()
基本上意味着你抛弃所有重复项,只保留每个键值的第一个对象实例。