Добрый вечер: -)
У меня есть вопрос, на который я не могу ответить, относительно случая INSERT «многие ко многим» с EFCore.
У меня есть Recipe
, у которого много Tag
. A Tag
(возьмем, например, «ужин») может быть во многих Recipe
. У обоих есть своя таблица SQL.
Чтобы эта функция «многие ко многим» работала, у меня также есть объединяющая таблица SQL и связанный с ней объект RecipeTag
.
Примечание №1: код классов находится в конце этого сообщения
Примечание №2: каждая операция GET / SELECT работает как шарм, реализовано и работает «многие ко многим»
Чтобы взять простой пример, начиная с fre sh пустых таблиц, я публикую свой новый Recipe
:
POST /recipes
{
"title": "A first title",
"tags": [
{
"name": "dinner"
}
]
}
Это хорошо. Я вижу свои новые записи в таблице Recipe
, таблице Tag
и RecipeTag
, связывающих обе id
.
Теперь я снова регистрирую новый Recipe
с тем же Tag
:
POST /recipes
{
"title": "A second title",
"tags": [
{
"name": "dinner"
}
]
}
Здесь я получил нарушение ограничения UNIQUE key от EF Core. Он попытается зарегистрировать новый Tag
с именем dinner
. На самом деле это вполне нормально для исключения, поскольку я ввел уникальное ограничение в поле Tag
. tagnam
.
Вопрос: есть ли хороший способ обойти это? Это означает, что EF Core регистрирует вторую Recipe
и присоединяющуюся RecipeTag
сущность, распознавая сам Tag
, но на этот раз не только ПК, а уникальным ограничением.
Кажется (и это logi c) EF Core не будет пытаться добавить объект, если увидит, что id
(PK) уже находится в db. Но в моем случае: я бы хотел, чтобы EF Core также проверял уникальное ограничение, и если оно конфликтует, возьмите id
конфликтующего объекта в db и lazy-put-it в моем объекте Tag
, чтобы он мог (сразу после ) правильно вставьте присоединяемый объект RecipeTag
с правильным id
!
Извините, это немного беспорядочно ...
Единственное "неплохое" решение, которое у меня есть на данный момент :
- Контроллер API получает
RecipeDto
- мы извлекаем теги из этого нового рецепта как
string[]
- извлекаем существующие теги из базы данных, совпадающей с теми, которые мы хотели бы добавить
- при преобразовании
RecipeDto
в Recipe
, поле (см. ниже) ICollection<RecipeTag>
будет содержать правильный Tag
с id
, установленным для существующих , и некоторые объекты no-id Tag
, для тех, которые нам действительно нужно добавить - , как уже говорилось, EF Core распознает некоторые
Tag
с заполненным идентификатором и не будет пытаться вставить их в db - он просто вставит те, у которых нет идентификатора
Классы, которые я использую:
/// <summary>
/// Recipe that can contain many tags
/// </summary>
[Table("rcp")]
public class Recipe
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("id")]
public Guid Id { get; set; }
[Column("rcptit")]
public string Title { get; set; }
public ICollection<RecipeTag> RecipeTags { get; set; }
}
/// <summary>
/// Tag contained into many recipes
/// </summary>
[Table("tag")]
public class Tag
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("id")]
public Guid Id { get; set; }
[Column("tagnam")] // this has a UNIQUE constraint in db to avoid having 2 times the same tag
public string Name { get; set; }
}
/// <summary>
/// Join table between Recipes and Tags
/// </summary>
[Table("rcptag")]
public class RecipeTag
{
[Column("rcpid")]
public Guid RecipeId { get; set; }
[Column("tagid")]
public Guid TagId { get; set; }
public Recipe Recipe { get; set; }
public Tag Tag { get; set; }
}
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{ }
public DbSet<Recipe> Recipes { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<RecipeTag> RecipeTags { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.HasPostgresExtension("uuid-ossp");
builder.Entity<Tag>().HasIndex(e => e.Name).IsUnique();
// composite primary key for RecipeTag
builder.Entity<RecipeTag>().HasKey(rt => new { rt.RecipeId, rt.TagId });
builder.Entity<RecipeTag>().HasOne(rt => rt.Recipe).WithMany(r => r.RecipeTags).HasForeignKey(rt => rt.RecipeId);
}
}