Ошибка сохранения изменений EF Core (v8.01 в .NET 8)

После обновления с .NET Framework с EF до .NET 8 с EF Core я не могу изменить свой файл SalesQuotes.

Я использую стратегию таблицы для каждого типа.

Этот код не работает:

var connect = HandyDefaults.MakeConnectFromConnectionString(connectionString);

var quote = connect.SalesQuotes.SingleOrDefault(x => x.DocumentNumber == "SQ000017");

var guidString = Guid.NewGuid().ToString();
quote.CustomerReference = guidString;

connect.SaveChanges();

connect = HandyDefaults.MakeConnectFromConnectionString(connectionString);
var quote2 = connect.SalesQuotes.SingleOrDefault(x => x.DocumentNumber == "SQ000017");

Assert.AreEqual(guidString, quote2.CustomerReference); // fails

Где DbContext строится как:

public static MyEFCoreDbContext MakeConnectFromConnectionString(string connectionString)
{
    var optionsBuilder = new DbContextOptionsBuilder<MyEFCoreDbContext>()
            .UseSqlServer(connectionString)
            .UseChangeTrackingProxies()
            .UseObjectSpaceLinkProxies();

    return new MyEFCoreDbContext(optionsBuilder.Options);
}

А в OnModelCreating у меня есть:

modelBuilder.SetOneToManyAssociationDeleteBehavior(DeleteBehavior.SetNull, DeleteBehavior.Cascade);
modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues);
modelBuilder.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction);

а также

modelBuilder.Entity<SalesQuoteLine>()
            .HasOne(j => j.SalesQuote)
            .WithMany(x => x.Lines)
            .HasForeignKey(x => x.SalesQuote_Id)
            .OnDelete(DeleteBehavior.Cascade);

Бизнес-модель что-то вроде

public class SalesQuote : BaseSalesHeader
{ 
    public SalesQuote()
    {
       Lines = new ObservableCollection<SalesQuoteLine>();
    }

    public virtual ObservableCollection<SalesQuoteLine> Lines { get; set; }
    // other properties
}

С BaseSalesHeader похоже на:

public abstract class BaseSalesHeader 
{
    // contains properties common to different sales documents.

    public virtual string CustomerReference { get; set; }
}  

Я использую Entity Framework Core v8.01, .NET 8, C#, SQL Server.

Я могу добавлять SalesQuotes, но не изменять их.

Обновление: я изменил код модульного теста следующим образом:

var connect = HandyDefaults.MakeConnectFromConnectionString(connectionString);

var quote = connect.SalesQuotes.SingleOrDefault(x => x.DocumentNumber == "SQ000017");

var guidString = Guid.NewGuid().ToString();
Debug.WriteLine($"Original CustomerReference is {quote.CustomerReference}");
quote.CustomerReference = guidString;

Debug.WriteLine($"Modified CustomerReference is {quote.CustomerReference}");

connect.SaveChanges();

connect = HandyDefaults.MakeConnectFromConnectionString(connectionString);
var quote2 = connect.SalesQuotes.SingleOrDefault(x => x.DocumentNumber == "SQ000017");
Debug.WriteLine($"Retrieved CustomerReference is {quote2.CustomerReference}");
Assert.AreEqual(guidString, quote2.CustomerReference); // fails

Отладочный вывод

Original CustomerReference is DC Replacement
Modified CustomerReference is 8944f1f4-0df4-4a3b-bdd8-85b0d80c792f
Retrieved CustomerReference is DC Replacement

Обновление №2: наведя курсор на .UseChangeTrackingProxies(), я вижу это:

ссылка здесь для записей уведомлений

Я добавил тег xaf, поскольку код находится в решении DevExpress XAF.

Обновление 3. Я пытался вызвать.

connect.ChangeTracker.DetectChanges();

до

connect.SaveChanges();

но это не имело никакого значения.

Обновление №4

Прочитав очень полезный ответ Стива Пая, я понял, что добавил не упомянутое полное объявление BaseSalesHeader.

public abstract class BaseSalesHeader : BaseDocumentBo, IObjectSpaceLink, IHasSequencedLines , INotifyPropertyChanged

Мой тест проходит, когда я удаляю INotifyPropertyChanged

Я создал новый бизнес-объект с помощью мастера Dev Express следующим образом и заметил, что в коде упоминается

Вам не нужно реализовывать интерфейс INotifyPropertyChanged — EF Core реализует его автоматически. И ссылки на Обнаружение и уведомление об изменениях.

  // Register this entity in your DbContext (usually in the BusinessObjects folder of your project) with the "public DbSet<EntityObject1> EntityObject1s { get; set; }" syntax.
  [DefaultClassOptions]
  //[ImageName("BO_Contact")]
  //[DefaultProperty("Name")]
  //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)]
  // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701).
  // **You do not need to implement the INotifyPropertyChanged interface - EF Core implements it automatically.**
  // (see [https://learn.microsoft.com/en-us/ef/core/change-tracking/change-detection#change-tracking-proxies][3] for details).
  public class EntityObject1 : BaseObject
  {
      public EntityObject1()
      {
          // In the constructor, initialize collection properties, e.g.: 
          // this.AssociatedEntities = new ObservableCollection<AssociatedEntityObject>();
      }

      // You can use the regular Code First syntax.
      // Property change notifications will be created automatically.
      // (see https://learn.microsoft.com/en-us/ef/core/change-tracking/change-detection#change-tracking-proxies for details)
      //public virtual string Name { get; set; }

     // Alternatively, specify more UI options:
      [XafDisplayName("My display name"), ToolTip("My hint message")]
      [ModelDefault("EditMask", "(000)-00"), VisibleInListView(false)]
      [RuleRequiredField(DefaultContexts.Save)]
      public virtual string Name { get; set; }

      // Collection property:
      //public virtual IList<AssociatedEntityObject> AssociatedEntities { get; set; }

      //[Action(Caption = "My UI Action", ConfirmationMessage = "Are you sure?", ImageName = "Attention", AutoCommit = true)]
      //public void ActionMethod() {
      //    // Trigger custom business logic for the current record in the UI (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument112619.aspx).
      //    this.PersistentProperty = "Paid";
      //}
  }

Как выглядит объявление свойства CustomerReference? Каково начальное значение при первом чтении записи, а затем при втором чтении? Проверьте, действительно ли поле базы данных обновляется после сохранения. Я не думаю, что это имеет какое-либо отношение к наследственности. Не знаю, почему вы используете другое поведение отслеживания изменений, но настройка DbContext или конфигурации может все испортить.

Steve Py 10.07.2024 01:54

Спасибо. Я обновил вопрос, чтобы показать его внутри BaseSalesHeader.

Kirsten 10.07.2024 04:37
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
67
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Из документации EF я подозреваю, что проблема в том, что вы используете:

modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues);

https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.changetrackingstrategy?view=efcore-8.0

ChangingAndChangedNotificationsWithOriginalValues Чтобы использовать это стратегии, класс сущности должен реализовать INotifyPropertyChanged и INotifyPropertyChanging. Исходные значения регистрируются, когда предприятие вызывает PropertyChanging. Свойства помечаются как измененные, если объект вызывает событие PropertyChanged.

Этот параметр предполагает, что объекты будут использовать INotifyPropertyChanged и INotifyProperyChanging. При удалении этого параметра следует использовать Snapshot, который автоматически использует вызов DetectChanges.

Обновлено: Кроме того, если вы хотите использовать эту стратегию отслеживания изменений, она ожидает как INotifyPropertyChanged, так и INotifyPropertyChanging, что AFAIK не будет работать с автоматическими свойствами:

public virtual string CustomerReference { get; set; }

... Эти события работают с методами доступа к свойствам на основе полей, так что прослушиватели событий NotifiyPropertyChanged/ing могут быть использованы. Что в сочетании с PropertyAccessMode.PreferFieldDuringConstruction Если вы используете трекер изменений, который связан с NotifyPropertyChanged/ing, вы не хотите, чтобы он постоянно отключал обработчики событий при заполнении каждого свойства каждого объекта:

public event PropertyChangingEventHandler PropertyChanging;  
public event PropertyChangedEventHandler PropertyChanged;  


private string _customerReference;
public virtual string CustomerReference 
{ 
    get => _customerReference; 
    set
    {
        if (value == _customerReference) return;
        var result = RaisePropertyChanging(nameof(CustomerReference), value);
        if (result)
        {
            _customerReference = value;
            RaisePropertyChanged(nameof(CustomerReference), value);
        }
    } 
}

где методы RaisePropertyChanging и RaisePropertyChanged являются оболочками для проверки того, установлены ли обработчики событий и передаются ли они обработчикам событий. Изменение будет проверять отмену и возвращать значение True или False в зависимости от того, отклонил ли какой-либо слушатель изменение или нет.

Спасибо, что направили меня на правильный путь. Я обновил вопрос.

Kirsten 10.07.2024 06:25

Да, если бы трекер изменений был настроен для ChangeAndChangedNotificationsWithOriginalValues, то AFAIK не работал бы с автоматическими свойствами. Вам потребуется правильно реализовать обработчики событий NotifyPropertyChanged и NotifyPropertyChanging для подключения EF. Вероятно, он проверяет реализации интерфейса, и, если они найдены, он рассчитывает их использовать, если нет, то его прокси-класс, скорее всего, связан с ними. Я обновил ответ вероятными изменениями, если вы расширите NotifyPropertyChanged/ing.

Steve Py 10.07.2024 06:57

Другие вопросы по теме