Многомерные отношения теряют связь с оберткой - PullRequest
0 голосов
/ 25 октября 2018

Я думаю, что-то не так с моделью в примере ниже, но я не могу понять, что.

В этом примере у меня есть класс Container с элементами, содержащими подэлементы.Как только я пытаюсь создать контейнер с более чем одним уровнем Предметов, он теряет связь с контейнером и поэтому терпит неудачу из-за ограничений внешнего ключа.

Исключение, которое я получаю:

Microsoft.EntityFrameworkCore.DbUpdateException: при обновлении записей произошла ошибка.Смотрите внутреннее исключение для деталей.---- System.Data.SqlClient.SqlException: оператор INSERT конфликтовал с ограничением FOREIGN KEY "FK_Items_Containers_ContainerId".Конфликт произошел в базе данных «Тест», таблице «dbo.Containers», столбце «Id».Оператор был прерван.

Когда я смотрю на SQL, который генерирует EF Core, используя SQL Profiler, он пытается вставить 0 в качестве идентификатора для контейнера для всех непрямых дочерних элементов.

Итак, создание Контейнера не является проблемой, как и дочерние объекты первого уровня, но как только я добавляю второй уровень, он теряет связь с Контейнером.

public class Test
{
    public class Container
    {
        [Required]
        public int Id { get; set; }
        public IEnumerable<Item> Items { get; set; }
    }

    public class Item
    {
        [Required]
        public int Id { get; set; }
        [Required]
        public int ContainerId { get; set; }
        public virtual Container Container { get; set; }
        public int? ParentItemId { get; set; }
        public virtual Item ParentItem { get; set; }
        public IEnumerable<Item> ChildItems { get; set; }
    }

    public class TestContext : DbContext
    {
        public virtual DbSet<Container> Containers { get; set; }
        public virtual DbSet<Item> Items { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Container>()
                .HasMany(c => c.Items)
                .WithOne(c => c.Container)
                .HasForeignKey(c => c.ContainerId);

            modelBuilder.Entity<Item>()
                .HasOne(x => x.Container)
                .WithMany(x => x.Items)
                .HasForeignKey(x => x.ContainerId);

            modelBuilder.Entity<Item>()
                .HasOne(x => x.ParentItem)
                .WithMany(x => x.ChildItems)
                .HasForeignKey(x => x.ParentItemId);
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder.UseSqlServer(
                @"Server=(localdb)\mssqllocaldb;Database=Test;Trusted_Connection=True;ConnectRetryCount=0");

    }

    public void ContextTest()
    {
        using (var context = new TestContext())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            var container = new Container();

            container.Items = new List<Item>
            {
                new Item
                {
                    ChildItems = new List<Item>
                    {
                        new Item()
                    }
                }
            };

            context.Containers.Add(container);

            context.SaveChanges();
        }
    }
}

1 Ответ

0 голосов
/ 25 октября 2018

Когда вы добавляете экземпляр Container в контекст, процесс исправления отношений EF Core проверит коллекцию Items и автоматически назначит свойство Item.Container (и FK).Тем не менее, элементы более низкого уровня, которые не содержатся в коллекции Items и не имеют назначенного свойства Container, поэтому EF будет пытаться использовать любое значение, содержащееся в FK (поскольку оно не может быть пустым, оно будет использовать 0 - обратите внимание, что 0является допустимым значением для не сгенерированного ключа).

Если вам интересно, почему он не присваивает верхний Container рекурсивно, ответ - потому что модель не предполагает такого поведения.С реляционной точки зрения нет никакой связи между ParentItem.Container и ChildItem.Container - вполне допустимо, чтобы они имели разные значения.Если предполагается, что все дочерние элементы совместно используют контейнер корневых элементов, тогда модель сущности содержит избыточность - Container свойство / FK должно иметь значение NULL и назначаться только для корневых элементов (в основном взаимоисключающих с ParentItem).

Если вы хотите оставить все как есть, вы не сможете выразить свое намерение EF Core (или реляционной базе данных в целом).Таким образом, вам нужно применить это ограничение вручную, либо добавив элементы нижнего уровня в коллекцию контейнера Items, либо проще - назначьте экземпляр контейнера их свойству Container:

var container = new Container();
container.Items = new List<ContainerItem>
{
    new ContainerItem
    {
        ChildItems = new List<ContainerItem>
        {
            new ContainerItem
            {
                Container = container // <-- do the same for all non direct items
            }
        }
    }
};
...