簡體   English   中英

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

[英]How to combine Find() and AsNoTracking()?

在對 EF 上下文進行查詢時如何將Find()AsNoTracking()結合使用,以防止跟蹤返回的對象。 這是我做不到的

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

我怎樣才能做到這一點? 我正在使用 EF 版本 6。

注意:我不想使用SingleOrDefault()Where 我只是不能,因為參數Id是通用的並且它是一個struct ,在這種情況下我不能將 operator ==應用於泛型。

因此,您可以做的不是使用AsNoTracking() ,而是Find() ,然后將其從上下文中分離出來。 我相信這會給您與AsNoTracking()相同的結果,除了跟蹤實體的額外開銷。 有關詳細信息,請參閱EntityState

var entity = Context.Set<T>().Find(id);
Context.Entry(entity).State = EntityState.Detached;
return entity;

編輯:這有一些潛在的問題,如果上下文沒有加載一些關系,那么這些導航屬性將不起作用,你會感到困惑和沮喪,為什么一切都返回 null! 有關更多信息,請參閱https://stackoverflow.com/a/10343174/2558743 現在,在那些存儲庫中,我正在覆蓋我需要的存儲庫中的FindNoTracking()方法。

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

Find()AsNoTracking()沒有意義,因為Find應該能夠在不進入數據庫的情況下返回跟蹤的實體AsNoTracking的唯一選擇是WhereFirstSingle...

接受的答案的問題是,如果您嘗試查找的項目已被跟蹤,它將返回該項目然后將其標記為未跟蹤(這可能會弄亂代碼的其他部分)。

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

如果您確實希望 Extension 方法位於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。

好吧,我想如果你真的想這樣做,你可以嘗試自己創建你的表情。 我假設您有一個通用的基本實體類,這就是通用鍵屬性的來源。 我將該類命名為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();

我知道這是一個老問題,但這應該取代“查找”:

var variable = <context>.<Entity>.AsNoTracking().FirstOrDefault(s => s.Id == id)

然后檢查variable!=null

早在 2015 年,官方就要求包含該功能,即結合 Find() 和 AsNoTracking()。 給出這個論點后,該問題立即關閉:

AsNoTracking對於Find並沒有真正意義,因為 find 的關鍵特性之一是如果它已經在內存中,它將返回已跟蹤的實體版本,而不會訪問數據庫。 如果您想按鍵加載實體而不跟蹤它,請使用Single

因此,您可以替換:

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

像這樣:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM