После обновления с .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";
//}
}
Спасибо. Я обновил вопрос, чтобы показать его внутри BaseSalesHeader.
Из документации EF я подозреваю, что проблема в том, что вы используете:
modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues);
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 в зависимости от того, отклонил ли какой-либо слушатель изменение или нет.
Спасибо, что направили меня на правильный путь. Я обновил вопрос.
Да, если бы трекер изменений был настроен для ChangeAndChangedNotificationsWithOriginalValues, то AFAIK не работал бы с автоматическими свойствами. Вам потребуется правильно реализовать обработчики событий NotifyPropertyChanged и NotifyPropertyChanging для подключения EF. Вероятно, он проверяет реализации интерфейса, и, если они найдены, он рассчитывает их использовать, если нет, то его прокси-класс, скорее всего, связан с ними. Я обновил ответ вероятными изменениями, если вы расширите NotifyPropertyChanged/ing.
Как выглядит объявление свойства CustomerReference? Каково начальное значение при первом чтении записи, а затем при втором чтении? Проверьте, действительно ли поле базы данных обновляется после сохранения. Я не думаю, что это имеет какое-либо отношение к наследственности. Не знаю, почему вы используете другое поведение отслеживания изменений, но настройка DbContext или конфигурации может все испортить.