你能从DbSet中获取DbContext吗



在我的应用程序中,有时需要在一次操作中将10000行或更多行保存到数据库中。我发现,简单地迭代并一次添加一个项目可能需要半个多小时。

但是,如果我禁用AutoDetectChangesEnabled,大约需要5秒(这正是我想要的)

我正在尝试为DbSet制作一个名为"AddRange"的扩展方法,该方法将禁用AutoDetectChangesEnabled,然后在完成后重新启用它。

public static void AddRange<TEntity>(this DbSet<TEntity> set, DbContext con, IEnumerable<TEntity> items) where TEntity : class
    {
        // Disable auto detect changes for speed
        var detectChanges = con.Configuration.AutoDetectChangesEnabled;
        try
        {
            con.Configuration.AutoDetectChangesEnabled = false;
            foreach (var item in items)
            {
                set.Add(item);
            }
        }
        finally
        {
            con.Configuration.AutoDetectChangesEnabled = detectChanges;
        }
    }

所以,我的问题是:有没有一种方法可以从DbSet中获取DbContext?我不喜欢把它作为一个参数——感觉它应该是不必要的。

使用实体框架核心(2.1版测试),您可以使用获取当前上下文

// DbSet<MyModel> myDbSet
var context = myDbSet.GetService<ICurrentDbContext>().Context;

如何在EntityFramework Core 2.0 中从DbSet获取DbContext

是的,您可以从DbSet<TEntity>中获取DbContext,但解决方案的反射量很大。我在下面提供了一个如何做到这一点的例子。

我测试了以下代码,它能够成功地检索生成DbSetDbContext实例。请注意,尽管它确实回答了您的问题,但几乎可以肯定,您的问题有更好的解决方案

public static class HackyDbSetGetContextTrick
{ 
    public static DbContext GetContext<TEntity>(this DbSet<TEntity> dbSet)
        where TEntity: class
    { 
        object internalSet = dbSet
            .GetType()
            .GetField("_internalSet",BindingFlags.NonPublic|BindingFlags.Instance)
            .GetValue(dbSet);
        object internalContext = internalSet
            .GetType()
            .BaseType
            .GetField("_internalContext",BindingFlags.NonPublic|BindingFlags.Instance)
            .GetValue(internalSet); 
        return (DbContext)internalContext
            .GetType()
            .GetProperty("Owner",BindingFlags.Instance|BindingFlags.Public)
            .GetValue(internalContext,null); 
    } 
}

示例用法:

using(var originalContextReference = new MyContext())
{
   DbSet<MyObject> set = originalContextReference.Set<MyObject>();
   DbContext retrievedContextReference = set.GetContext();
   Debug.Assert(ReferenceEquals(retrievedContextReference,originalContextReference));
}

解释:

根据Reflector,DbSet<TEntity>具有类型为InternalSet<TEntity>的私有字段_internalSet。该类型是EntityFramework dll的内部类型。它继承自InternalQuery<TElement>(其中TEntity : TElement)。InternalQuery<TElement>也是EntityFramework dll的内部。它具有类型为InternalContext的专用字段_internalContextInternalContext也是EntityFramework内部的。但是,InternalContext公开了一个名为Owner的公共DbContext属性。因此,如果您有一个DbSet<TEntity>,您可以通过反射访问这些属性中的每一个并将最终结果强制转换为DbContext来获得对DbContext所有者的引用。

从@LonePixel更新

在EF7中,直接在实现DbSet的类中有一个私有字段_context。公开这个字段并不难。

为什么要在DbSet上这样做?尝试在DbContext上执行此操作:

public static void AddRangeFast<T>(this DbContext context, IEnumerable<T> items) where T : class
{
    var detectChanges = context.Configuration.AutoDetectChangesEnabled;
    try
    {
        context.Configuration.AutoDetectChangesEnabled = false;
        var set = context.Set<T>();
        foreach (var item in items)
        {
            set.Add(item);
        }
    }
    finally
    {
        context.Configuration.AutoDetectChangesEnabled = detectChanges;
    }
}

然后使用它很简单:

using (var db = new MyContext())
{
    // slow add
    db.MyObjects.Add(new MyObject { MyProperty = "My Value 1" });
    // fast add
    db.AddRangeFast(new[] {
        new MyObject { MyProperty = "My Value 2" },
        new MyObject { MyProperty = "My Value 3" },
    });
    db.SaveChanges();
}

也许您可以创建一个为您禁用此功能的助手,然后从AddRange方法中调用该助手

我的用例略有不同,但我也想为我调用的Save()dbsetextension方法解决这个问题,该方法将根据需要对dbset执行添加或修改,具体取决于要保存的项是否与dbset中的项匹配。

该解决方案适用于从smartcaveman响应导出的EF 6.4.4

public static DbContext GetContext<TEntity>(this DbSet<TEntity> dbSet) where TEntity : class
{
    var internalSetPropString = "System.Data.Entity.Internal.Linq.IInternalSetAdapter.InteralSet";
    var bfnpi = BindingFlags.NonPublic | BindingFlags.Instance;
    var bfpi = BindingFlags.Public | BindingFlags.Instance;
    var internalSet = dbSet.GetType().GetProperty(internalSetPropString, bfnpi).GetValue(dbSet);
    var internalContext = internalSet.GetType().BaseType.GetField("_internalContext", bfnpi).GetValue(internalSet);
    var ownerProperty = internalContext.GetType().GetProperty("Owner", bfpi);
    var dbContext = (dbContext)ownerProperty.GetValue(internalContext);
    return dbContext;
}

我在DbSetExtensions 中的用例

//yes I have another overload where i pass the context in. but this is more fun
public static void Save<T>(this DbSet<T> dbset, Expresssion<Fun<T, bool>> func, T item) where T :class
{
    var context = dbset.GetContext(); //<--
    var entity = dbset.FrirstOrDefault(func);
    if(entity == null) 
        dbset.Add(item);
    else 
    {
        var entry = context.Entry(entity);
        entry.CurrentValues.SetValues(item);
        entry.State = EntityState.Modified; 
    }
}

用例的示例使用

db.AppUsers.Save(a => a.emplid == appuser.emplid, appuser);
db.SaveChangesAsync();

最新更新