EntityFrameworkCore отслеживает изменения при записи поведения

#c# #entity-framework-core #.net-5

#c# #entity-framework-core #.net-5

Вопрос:

MyContext.cs

 public class MyContext: DbContext
{
    public MyContext(DbContextOptions<DBContext> options) : base(options)
    {
        ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        ChangeTracker.LazyLoadingEnabled = false;
        ChangeTracker.AutoDetectChangesEnabled = false;
        this.Database.EnsureCreated();
    }
}
 

BaseRepository.cs

  public abstract class BaseRepository<TDBEntity> where TDBEntity : class, new()
 {
        protected readonly MyContext dbContext;

        public BaseRepository(MyContext dbContext)
        {
            this.dbContext = dbContext;
        }

        public virtual void Update(TDBEntity model)
        {
            dbContext.Entry(model).State = EntityState.Modified;
        }

        public Task CommitAsync()
        {
            return dbContext.SaveChangesAsync();
        }
  }  
 

Если я обновлю один и тот же объект два раза, я получу следующую ошибку:

  The instance of entity type 'Foobar' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.
 When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
 

Я использую «сценарий подключенного DbContext», но я не хочу никакого отслеживания поведения. Я хочу использовать EntityFrameworkCore для «просто» генерации SQL-инструкций.

Комментарии:

1. Нет connected DbContext scenario . DbContext всегда работает отключенным, если вы не открываете явную длительную транзакцию, что, как правило, плохо

2. I want to use EntityFrameworkCore for 'just' generating SQL-Statements. это не то, для чего это нужно. EF — это ORM, а не генератор запросов.

3. Кстати, этот «базовый репозиторий» является анти-шаблоном и самой причиной возникновения проблем. Проблемы вызваны кодом, который вы не опубликовали. То, что вы опубликовали, ничего не предлагает поверх DbContext, кроме как запутать людей. CommitAsync ничего не фиксирует. Нет транзакции для фиксации. Он сохраняет все отслеживаемые изменения атомарным способом, используя внутреннюю транзакцию.

4. DbSet уже является репозиторием, DbContext уже является единицей работы. Вы можете исправить свои проблемы с производительностью, корректностью и параллелизмом, удалив дополнительные классы. Прочитайте книгу Гуннара Пейпмана » Нет необходимости в репозиториях и единице работы с Entity Framework Core «. Кстати, в этом нет ничего нового, люди знали, что вам не нужен дополнительный репозиторий поверх ORM с 2009 года: репозиторий — это новый синглтон

Ответ №1:

Если вы проверите QueryTrackingBehavior документы enum NoTracking , вы увидите следующее:

Отслеживание изменений не будет отслеживать ни одну из сущностей, которые возвращаются из запроса LINQ. Если экземпляры сущностей будут изменены, это не будет обнаружено средством отслеживания изменений, и SaveChanges() не сохранит эти изменения в базе данных.

Таким образом, он отвечает за «автоматическое» отслеживание изменений для экземпляров, возвращаемых вашими запросами, для выполнения обновлений и вставок EF по-прежнему необходимо будет отслеживать объекты и вместе с dbContext.Entry(model).State = EntityState.Modified; вами прикреплять объект (и все ссылки на другие объекты, которые у него есть, как написано в этом документе) к контексту, чтобы он начал отслеживать этот объект и помечатьэто как измененное.

Ответ №2:

Ну, это сложная вещь. Я несколько часов отлаживал исходные тексты ядра EF, чтобы найти лучший способ, но безуспешно. Проблема, которую ChangeTracker ищет EntityEntry по ссылке. Если не найдено по ссылке, выполняется поиск по ключам сущностей, и если запись найдена — у вас есть это исключение.

Итак, рождаются эти методы расширения. Не очень эффективно даже при использовании внутренних компонентов, но это показывает, как это сделать.

 public static class ChangeTrackerExtensions
{
    public static EntityEntry<T> GetEntry<T>(this DbContext ctx, T entity, out bool isNew) where T : class
    {
        var entityType = ctx.Model.FindEntityType(typeof(T));
        var stateManager = ctx.GetService<IStateManager>();

        isNew = false;
        foreach (var key in entityType.GetKeys())
        {
            var keyArray = key.Properties
                .Where(p => p.PropertyInfo != null || p.FieldInfo != null)
                .Select(p =>
                    p.PropertyInfo != null
                        ? p.PropertyInfo.GetValue(entity)
                        : p.FieldInfo.GetValue(entity)).ToArray();

            if (keyArray.Length == key.Properties.Count)
            {
                var entry = stateManager.TryGetEntry(key, keyArray);

                if (entry != null)
                {
                    return ctx.Entry((T)entry.Entity);
                }
            }
        }

        isNew = true;
        var newEntry = ctx.Entry(entity);

        return newEntry;
    }

    public static EntityEntry<T> UpdateEntry<T>(this DbContext ctx, T entity) where T : class
    {
        bool isNew;
        var entry = ctx.GetEntry(entity, out isNew);

        if (isNew)
        {
            entry.State = EntityState.Modified;
        }
        else
        {
            entry.CurrentValues.SetValues(entry);
        }
        return entry;
    }
}
 

Итак, обновление в вашем случае должно выглядеть так

    public virtual void Update(TDBEntity model)
   {
       dbContext.UpdateEntry(model);
   }
 

Комментарии:

1. Лучший способ — отказаться от «общего репозитория», поскольку он совершенно не нужен и вызвал проблему. Ядро EF не нарушено. Ничего из этого кода не требуется

2. @PanagiotisKanavos, просто забыл об этом коде 😉 Я исследовал такую возможность из-за разработки библиотеки, которая заменяет EF Core LINQ translator. И это был только один способ. Также люди могут захотеть обновить одну и ту же сущность несколько раз из-за десериализации сущностей.

3. Но этот вопрос вообще не касается замены транслятора EF Core. Кроме того, перевод выполняется не EF Core, он выполняется поставщиком

4. Кстати, у этого кода есть некоторые серьезные проблемы с производительностью, потому что он каждый раз извлекает информацию об отражении для типа, хотя ничего не изменилось. По крайней мере, вам нужно указать свойства ключа. Однако это совершенно не подходит для этого вопроса — это не устраняет реальную проблему, и OP не сможет разобраться в этом

5. Ядро EF не выполняет преобразование… Ну, теоретически да, но помимо сцен EF Core выполняет преобразование в запрос AST, а сторонние поставщики преобразуют AST в SQL — небольшая работа для провайдера, не так ли?.