如何结合 Find() 和 AsNoTracking()?

问题描述 投票:0回答:6

如何在查询 EF 上下文时将

Find()
AsNoTracking()
结合起来,以防止跟踪返回的对象。这是我做不到的

 _context.Set<Entity>().AsNoTracking().Find(id);

我该怎么做?我正在使用 EF 版本 6.

注意:我不想使用

SingleOrDefault()
,或
Where
。我不能,因为参数
Id
是通用的,它是一个
struct
,在那种情况下我不能为泛型应用运算符
==

c# entity-framework
6个回答
53
投票

因此,除了使用

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()
方法。


27
投票
<context>.<Entity>.AsNoTracking().Where(s => s.Id == id);

Find()
AsNoTracking()
没有意义,因为
Find
应该能够在不进入数据库的情况下返回被跟踪的实体..您使用
AsNoTracking
的唯一选择是
Where
First
Single... 


4
投票

接受的答案有一个问题,如果您尝试查找的项目已经被跟踪,它将返回该项目然后将其标记为未跟踪(这可能会弄乱代码的其他部分)。

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。


4
投票

早在 2015 年,官方要求包含功能,即组合 Find() 和 AsNoTracking()。这个问题在给出这个论点后立即关闭:

AsNoTracking
对于
Find
没有真正的意义,因为 find 的一个关键特性是如果它已经在内存中,它将返回已经跟踪的实体版本而不访问数据库。如果您想通过键加载实体而不跟踪它,请使用
Single
.

因此,您可以替换:

_context.Set<Entity>().AsNoTracking().Find(id); // Invalid

像这样的东西:

_context.Set<Entity>().AsNoTracking().Single(e => e.Id == id);

1
投票

好吧,我想如果你真的想这样做,你可以尝试自己创造你的表情。我假设您有一个通用的基本实体类,这就是通用键属性的来源。我将该类命名为

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();

0
投票

在 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);
© www.soinside.com 2019 - 2024. All rights reserved.