如何在查询 EF 上下文时将
Find()
与 AsNoTracking()
结合起来,以防止跟踪返回的对象。这是我做不到的
_context.Set<Entity>().AsNoTracking().Find(id);
我该怎么做?我正在使用 EF 版本 6.
注意:我不想使用
SingleOrDefault()
,或Where
。我不能,因为参数Id
是通用的,它是一个struct
,在那种情况下我不能为泛型应用运算符==
。
因此,除了使用
AsNoTracking()
之外,您可以做的是 Find()
然后将其从上下文中分离出来。我相信除了跟踪实体的额外开销之外,这会给您与AsNoTracking()
相同的结果。有关详细信息,请参阅EntityState。
var entity = Context.Set<T>().Find(id);
Context.Entry(entity).State = EntityState.Detached;
return entity;
编辑:这有一些潜在的问题,如果上下文没有加载一些关系,那么那些导航属性将不起作用,你会感到困惑和沮丧,为什么一切都返回空值!有关详细信息,请参阅https://stackoverflow.com/a/10343174/2558743。现在,在这些存储库上,我正在覆盖我需要的存储库中的
FindNoTracking()
方法。
<context>.<Entity>.AsNoTracking().Where(s => s.Id == id);
Find()
对 AsNoTracking()
没有意义,因为 Find
应该能够在不进入数据库的情况下返回被跟踪的实体..您使用 AsNoTracking
的唯一选择是 Where
或 First
或 Single...
接受的答案有一个问题,如果您尝试查找的项目已经被跟踪,它将返回该项目然后将其标记为未跟踪(这可能会弄乱代码的其他部分)。
Akos 提出自己构建表达式的建议是正确的,但该示例仅适用于具有单个主键的实体(涵盖大多数情况)。
此扩展方法适用于 EF Core 并有效匹配
DbSet<T>.Find(object [])
的签名。但它是 DbContext
而不是 DbSet
的扩展方法,因为它需要从 DbContext 访问实体的元数据。
public static T FindNoTracking<T>(this DbContext source, params object[] keyValues)
where T : class
{
DbSet<T> set = source.Set<T>();
if (keyValues == null || !keyValues.Any())
{
throw new Exception("No Keys Provided.");
}
PropertyInfo[] keyProps = GetKeyProperties<T>(source);
if (keyProps.Count() != keyValues.Count())
{
throw new Exception("Incorrect Number of Keys Provided.");
}
ParameterExpression prm = Expression.Parameter(typeof(T));
Expression body = null;
for (int i = 0; i < keyProps.Count(); i++)
{
PropertyInfo pi = keyProps[i];
object value = keyValues[i];
Expression propertyEx = Expression.Property(prm, pi);
Expression valueEx = Expression.Constant(value);
Expression condition = Expression.Equal(propertyEx, valueEx);
body = body == null ? condition : Expression.AndAlso(body, condition);
}
var filter = Expression.Lambda<Func<T, bool>>(body, prm);
return set.AsNoTracking().SingleOrDefault(filter);
}
public static PropertyInfo[] GetKeyProperties<T>(this DbContext source)
{
return source.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties.Select(p => p.PropertyInfo).ToArray();
}
然后您可以直接在 DbContext 上使用该方法。例如,如果您的实体有一个由两个字符串组成的组合键:
context.FindNoTracking<MyEntity>("Key Value 1", "Key Value 2");
如果您真的希望扩展方法位于
DbSet
而不是 DbContext
上,您可以这样做,但您需要从集合中获取上下文才能访问有关实体的元数据。目前没有很好的方法来做到这一点。 有一些 hacky 方法可以做到这一点,但它们涉及使用反射来访问框架类的私有字段,所以我建议不要这样做。
或者……
如果您有办法在不使用 DbContext/Metadata 的情况下找出 Key 属性是什么,则可以将其作为
DbSet
的扩展。例如,如果您所有的 Key 属性都标有 [Key]
属性,您可以使用此代码:
public static T FindNoTracking<T>(this DbSet<T> source, params object[] keyValues)
where T : class
{
//Pretty much the same...
}
public static PropertyInfo[] GetKeyProperties<T>()
{
return typeof(T).GetProperties()
.Where(pi => pi.GetCustomAttribute<KeyAttribute>() != null).ToArray();
}
这也适用于 Entity Framework 和 EF Core。
早在 2015 年,官方要求包含功能,即组合 Find() 和 AsNoTracking()。这个问题在给出这个论点后立即关闭:
对于AsNoTracking
没有真正的意义,因为 find 的一个关键特性是如果它已经在内存中,它将返回已经跟踪的实体版本而不访问数据库。如果您想通过键加载实体而不跟踪它,请使用Find
.Single
因此,您可以替换:
_context.Set<Entity>().AsNoTracking().Find(id); // Invalid
像这样的东西:
_context.Set<Entity>().AsNoTracking().Single(e => e.Id == id);
好吧,我想如果你真的想这样做,你可以尝试自己创造你的表情。我假设您有一个通用的基本实体类,这就是通用键属性的来源。我将该类命名为
KeyedEntityBase<TKey>
,TKey
是密钥的类型(如果您没有这样的类,那很好,我唯一使用它的是通用约束)。然后你可以创建一个像这样的扩展方法来自己构建表达式:
public static class Extensions
{
public static IQueryable<TEntity> WhereIdEquals<TEntity, TKey>(
this IQueryable<TEntity> source,
Expression<Func<TEntity, TKey>> keyExpression,
TKey otherKeyValue)
where TEntity : KeyedEntityBase<TKey>
{
var memberExpression = (MemberExpression)keyExpression.Body;
var parameter = Expression.Parameter(typeof(TEntity), "x");
var property = Expression.Property(parameter, memberExpression.Member.Name);
var equal = Expression.Equal(property, Expression.Constant(otherKeyValue));
var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);
return source.Where(lambda);
}
}
然后,您可以像这样使用它(对于整数键类型):
context.Set<MyEntity>.AsNoTracking().WhereIdEquals(m=>m.Id, 9).ToList();
在 asp net core 7 中,您可以在需要时禁用跟踪https://learn.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries:
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var product = await context.Products.FindAsync(id);