У меня есть следующая база данных:
public partial class Seed_Languages : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "Languages",
columns: new[] { "LanguageId", "LangCode", "LangName", "Sort" },
values: new object[,]
{
{ 1, "AU", "Австралия", 0 },
{ 159, "CX", "Остров Рождества", 0 },
{ 160, "PN", "Острова Питкэрн", 0 },
{ 161, "SH", "Острова Святой Елены, Вознесения и Тристан-да-Кунья", 0 },
{ 162, "PK", "Пакистан", 0 },
{ 163, "PW", "Палау", 0 },
.... and so on ...
Как вы видите, я собираюсь заполнить некоторую таблицу, которая содержит названия языков (на русском языке, для отображения в пользовательском интерфейсе), коды языка, некоторые дополнительное поле - Sort
(здесь не важно) и первичный ключ. Просто, правда? Вот таблица:
Затем я создаю ее внутри моего OnModelCreating
:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// many fluent api calls
LanguagesSeeder.SeedLanguages(modelBuilder);
}
Затем я запускаю $ dotnet ef database update
и посев работает отлично! Но вскоре начались проблемы.
Когда я пытаюсь вставить новый язык, NET дает мне:
Данные об исключении: Серьезность: ОШИБКА SqlState: 23505 MessageText: значение дублированного ключа нарушает уникальное ограничение "PK_Languages "Detail: Key (" LanguageId ") = (1) уже существует. SchemaName: public TableName: Languages ConstraintName: PK_Languages Файл: nbtinsert. c Строка: 434
«Хмммм, давайте попробуем снова», - подумал я. И:
Исключительные данные: Серьезность: ОШИБКА SqlState: 23505 MessageText: дубликат значения ключа нарушает уникальное ограничение "PK_Languages" Подробно: Ключ ("LanguageId") = (2) уже существует. SchemaName: public TableName: Languages ConstraintName: PK_Languages Файл: nbtinsert. c Строка: 434 Подпрограмма: _bt_check_unique
Вы это видите? Та же ошибка, но с другой жалобой первичного ключа! Первый был: Key ("LanguageId")=(1) already exists.
, а второй Key ("LanguageId")=(2) already exists.
!
Итак, что делать? Я знаю это так:
ALTER SEQUENCE <name of sequence> RESTART WITH <your number is here>;
Но довольно неудобно запускать это SQL в консоли после посева. Я что-то пропустил? Может быть, есть стандартный способ для этого, я имею в виду использование EF API?
Обновление
Я покажу вам мою Language
модель:
namespace Domains
{
public class Language
{
public int LanguageId { get; set; }
public int Sort { get; set; }
public List<Customer> Customers { get; set; }
public List<PushMessageLang> PushMessageLangs { get; set; }
[NotMapped]
public IEnumerable<PushMessage> PushMessages
{
get => PushMessageLangs?.Select(r => r.PushMessage);
set => PushMessageLangs = value.Select(v => new PushMessageLang()
{
PushMessageId = v.PushMessageId
}).ToList();
}
public string LangName { get; set; }
public string LangCode { get; set; }
}
}
Я делаю вставку через абстракцию репозитория:
Базовый репозиторий:
public class BaseRepository<T, C> : IRepository<T>
where T : class
where C : DbContext
{
protected C DataContext;
private readonly DbSet<T> _dbset;
public BaseRepository(C context)
{
DataContext = context;
_dbset = context.Set<T>();
}
public virtual IQueryable<T> All => _dbset;
public virtual async Task SaveAsync(T entity)
{
await _dbset.AddAsync(entity);
await DataContext.SaveChangesAsync();
}
public async Task SaveAsync(List<T> entity)
{
await _dbset.AddRangeAsync(entity);
await DataContext.SaveChangesAsync();
}
public virtual async Task UpdateAsync(T entity)
{
_dbset.Attach(entity).State = EntityState.Modified;
_dbset.Update(entity);
await DataContext.SaveChangesAsync();
}
public virtual async Task DeleteAsync(int id)
{
var dbEntity = await _dbset.FindAsync(id);
if (dbEntity != null)
{
_dbset.Remove(dbEntity);
await DataContext.SaveChangesAsync();
}
}
}
А в контроллере:
public async Task<IActionResult> Create([FromForm] LanguageViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
var newLanguage = new Language()
{
Sort = viewModel.Sort,
LangCode = viewModel.Code,
LangName = viewModel.Name
};
await _languageRepository.SaveAsync(newLanguage);
return RedirectToAction("Index");
}
Обновление 2
Как и просят в комментариях, я здесь прикреплю все беглые API для Language
модели:
// many to many with `Message` entity
modelBuilder.Entity<PushMessageLang>()
.HasKey(bc => new { bc.PushLangId, bc.PushMessageId });
modelBuilder.Entity<PushMessageLang>()
.HasOne(bc => bc.Language)
.WithMany(b => b.PushMessageLangs)
.HasForeignKey(bc => bc.PushLangId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<PushMessageLang>()
.HasOne(bc => bc.PushMessage)
.WithMany(c => c.PushMessageLangs)
.HasForeignKey(bc => bc.PushMessageId)
.OnDelete(DeleteBehavior.Cascade);
// has unique language code
modelBuilder.Entity<Language>()
.HasIndex(x => x.LangCode).IsUnique();
Обновление 3
Как и спросил @Roman Marusyk, я здесь SQL скрипт для создания Languages
таблицы.
-- auto-generated definition
create table "Languages"
(
"LanguageId" integer generated by default as identity
constraint "PK_Languages"
primary key,
"LangName" text,
"LangCode" text,
"Sort" integer default 0 not null
);
alter table "Languages"
owner to makeapp_pushes;
create unique index "IX_Languages_LangCode"
on "Languages" ("LangCode");
Хмм, теперь я вижу, что ничего не имею об автоприращении. Но мой SQL клиент показывает мой: