В приложении MVVM с EF Core в качестве ORM я решил смоделировать таблицу с вручную вставленным текстовым первичным ключом.
Это потому, что в этом c приложении я Я бы предпочел использовать значащие ключи вместо бессмысленных целочисленных идентификаторов, по крайней мере, для простых таблиц "ключ-значение", таких как таблица стран мира. У меня есть что-то вроде:
Id | Description
-----|--------------------------
USA | United States of America
ITA | Italy
etc. etc.
Итак, сущность:
public class Country
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; set; }
public string Description { get; set; }
}
Вот моя модель просмотра. Это немного больше, чем контейнер для ObservableCollection стран. Фактически он загружается из репозитория. Это тривиально, и я добавил весь код в конце. Это не совсем актуально, и я мог бы использовать только DbContext. Но я хотел показать все слои, чтобы увидеть, к чему относится решение. О да, тогда он содержит код синхронизации, который на самом деле оскорбляет EF Core.
public class CountriesViewModel
{
//CountryRepository normally would be injected
public CountryRepository CountryRepository { get; set; } = new CountryRepository(new AppDbContext());
public ObservableCollection<Country> Countries {get; set;}
public CountriesViewModel()
{
Countries = new ObservableCollection<Country>();
Countries.CollectionChanged += Countries_CollectionChanged;
}
private void Countries_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (Country c in e.NewItems)
{
CountryRepository.Add(c);
}
}
}
В моем MainWindow у меня просто:
<Window.DataContext>
<local:CountriesViewModel/>
</Window.DataContext>
<DockPanel>
<DataGrid ItemsSource="{Binding Countries}"/>
</DockPanel>
Проблема и вопрос
Теперь это не работает. Когда мы пытаемся вставить новую запись, в этом случае я делаю это с помощью функции Automati c DataGrid, я получаю:
System.InvalidOperationException: 'Unable to track an entity of type 'Country'
because primary key property 'Id' is null.'
Каждый раз, когда я добавляю новую запись в ObservableCollection Я также пытаюсь добавить его обратно в репозиторий, который, в свою очередь, добавляет его в EF DbContext, который не принимает объекты с нулевым ключом.
Итак, какие у меня здесь варианты? Один из них - откладывание добавления новой записи до тех пор, пока не будет вставлен идентификатор. Это нетривиально, как показанная мною обработка коллекции, но проблема не в этом. Хуже всего то, что таким образом у меня будет какая-то запись, которая отслеживается EF (обновленная и удаленная и новая с назначенным pk), а некоторые отслеживаются моделью представления (новые с еще не назначенным ключом).
Другой использует альтернативные ключи; У меня был бы целочисленный автогенерированный первичный ключ, а код ITA, USA et c был бы альтернативным ключом, который также использовался бы в отношениях. Это не так уж плохо с точки зрения простоты, но я бы хотел решение только для приложений.
То, что я ищу
Я ищу здесь изящное решение, шаблон для использоваться всякий раз, когда возникает эта проблема, и это хорошо работает в контексте приложения MVVM / EF. Конечно, я мог бы также посмотреть в направлении событий просмотра, то есть заставить пользователя вставить ключ перед определенным событием, которое запускает вставку. Я бы счел это второсортным решением, потому что оно зависит от вида.
Оставшийся код
Просто для полноты, если вы хотите запустить код, вот оставшийся код .
DbContext
(Настроен для postgres)
public class AppDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql("Host=localhost;Database=WpfApp1;Username=postgres;Password=postgres");
}
public DbSet<Country> Countries { get;set; }
}
Репозиторий
Причина, по которой я реализовал репозиторий для такого простого примера: потому что я думаю, что возможным решением может быть включение управления записями без ключа в репозиторий, а не в модель просмотра. Я все еще надеюсь, что кто-нибудь предложит более простое решение.
public class CountryRepository
{
private AppDbContext AppDbContext { get; set; }
public CountryRepository(AppDbContext appDbContext) => AppDbContext = appDbContext;
public IEnumerable<Country> All() => AppDbContext.Countries.ToList();
public void Add(Country country) => AppDbContext.Add(country);
//ususally we don't have a save here, it's in a Unit of Work;
//for the example purpose it's ok
public int Save() => AppDbContext.SaveChanges();
}