Можете ли вы получить DbContext из DbSet?
в моем приложении иногда необходимо сохранить 10 000 или более строк в базе данных за одну операцию. Я обнаружил, что простое повторение и добавление каждого элемента по одному может занять более получаса.
однако, если я отключу AutoDetectChangesEnabled, это займет ~ 5 секунд (что именно то, что я хочу)
Я пытаюсь сделать метод расширения под названием "AddRange" DbSet, который отключит 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;
}
}
Итак, мой вопрос: есть ли способ получить DbContext из DbSet? Мне не нравится делать это параметром - кажется, что это должно быть ненужным.
4 ответов
да вы можете узнать DbContext
С 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);
}
}
пример использование:
using(var originalContextReference = new MyContext())
{
DbSet<MyObject> set = originalContextReference.Set<MyObject>();
DbContext retrievedContextReference = set.GetContext();
Debug.Assert(ReferenceEquals(retrievedContextReference,originalContextReference));
}
объяснение:
согласно рефлектору,DbSet<TEntity>
есть частное поле _internalSet
типа InternalSet<TEntity>
. Тип является внутренним для библиотеки dll EntityFramework. Он наследуется от InternalQuery<TElement>
(где TEntity : TElement
). InternalQuery<TElement>
также является внутренним для библиотеки dll EntityFramework. Он имеет частное поле _internalContext
типа InternalContext
. InternalContext
также является внутренним для EntityFramework. Однако,InternalContext
предоставляет общие DbContext
свойство Owner
. Итак, если у вас есть DbSet<TEntity>
, вы можете получить ссылку на элемент DbContext
владелец, путем доступа к каждому из этих свойств рефлексивно и приведения конечного результата к DbContext
.
обновление от @LoneyPixel
в EF7 есть частное поле _context непосредственно в классе реализует DbSet. Это не трудно выставить это поле публично
почему вы делаете это на 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();
}
С ядром Entity Framework (протестировано с версией 2.1) вы можете получить текущий контекст, используя
// DbSet<MyModel> myDbSet
var context = myDbSet.GetService<ICurrentDbContext>().Context;
возможно, вы могли бы создать помощника, который отключил это для вас, а затем просто вызвать помощника из метода AddRange