多维关系使与包装器的关系松散

时间:2018-10-25 05:48:58

标签: c# sql-server entity-framework-core

我认为以下示例中的模型有问题,但是我不知道是什么。

在示例中,我有一个Container类,其中Items包含子Items。一旦我尝试创建一个包含多个级别的Items的容器,它就会失去与该容器的关系,因此由于外键约束而失败。

我得到的例外是:

  

Microsoft.EntityFrameworkCore.DbUpdateException:更新条目时发生错误。请参阅内部异常   细节。       ---- System.Data.SqlClient.SqlException:INSERT语句与FOREIGN KEY约束冲突   “ FK_Items_Containers_ContainerId”。数据库中发生了冲突   “测试”,表“ dbo.Containers”,列“ Id”。   该声明已终止。

当我查看EF Core使用SQL Profiler生成的SQL时,它将尝试为所有非直接子代的容器插入0作为id。

因此,创建容器不是问题,第一层级的子级也不是问题,但是一旦我添加第二层级,它就会失去与容器的关系。

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 :(得分:4)

Container实例添加到上下文时,EF核心关系修复过程将检查Items集合并自动分配Item.Container属性(和FK)。但是,较低级别的项既没有包含在Items集合中,也没有分配Container属性,因此EF将尝试使用FK包含的任何值(由于该值不可为空,因此将使用0 -请注意,0是未生成密钥的有效值。

如果您想知道为什么它不递归地分配顶部Container,答案是-因为模型并不暗示这种行为。从关系的角度来看,ParentItem.ContainerChildItem.Container之间没有关系-它们具有不同的值是很有效的。如果意图是所有子项共享根项目容器,那么实体模型将包含冗余-Container属性/ FK必须为空并且仅分配给根项目(基本上与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
            }
        }
    }
};