你能从DbSet获得DbContext吗?

ber*_*oss 22 c# entity-framework entity-framework-4.1 dbcontext dbset

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

但是,如果我禁用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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

所以,我的问题是:有没有办法从DbSet获取DbContext?我不喜欢把它作为参数 - 感觉它应该是不必要的.

sma*_*man 22

是的,你可以DbContext从a 获得DbSet<TEntity>,但解决方案是反思重.我在下面提供了一个如何执行此操作的示例.

我测试了以下代码,它能够成功检索生成DbContext它的实例DbSet.请注意,虽然它确实回答了您的问题,但几乎可以肯定是您的问题的更好解决方案.

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); 
    } 
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

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

说明:

根据Reflector,DbSet<TEntity>有一个私人领域_internalSet的类型InternalSet<TEntity>.该类型是EntityFramework dll的内部类型.它继承自InternalQuery<TElement>(where TEntity : TElement). InternalQuery<TElement>也是EntityFramework dll的内部.它有一个私人领域_internalContext类型InternalContext. InternalContext也是EntityFramework的内部.但是,InternalContext暴露了一个DbContext名为的公共财产Owner.因此,如果您有一个DbSet<TEntity>,您可以DbContext通过反复访问每个属性并将最终结果转换为所有者来获取对所有者的引用DbContext.

@LoneyPixel更新

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


Tim*_*ers 14

你为什么在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后使用它就像:

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();
}
Run Code Online (Sandbox Code Playgroud)

  • 我发现正确的答案是首先重新思考你的问题,在这种情况下,我是根据你的目标来思考的,而不是你想如何实现它。我相信这就是“跳出框框思考”的核心原则,是一项值得学习的伟大技能。 (2认同)

Chr*_*jen 13

使用Entity Framework Core(使用版本2.1测试),您可以使用获取当前上下文

// DbSet<MyModel> myDbSet
var context = myDbSet.GetService<ICurrentDbContext>().Context;
Run Code Online (Sandbox Code Playgroud)

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