【问题标题】:Multi dimensional relationship looses the relation with wrapper多维关系松散与包装器的关系
【发布时间】:2019-03-29 15:29:33
【问题描述】:

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

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

我得到的例外是:

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

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

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

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();
        }
    }
}

【问题讨论】:

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


【解决方案1】:

当您将 Container 实例添加到上下文时,EF Core 关系修复过程将检查 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
            }
        }
    }
};

【讨论】:

  • 谢谢你,你说的很有道理!唯一让我有点好奇的是它适用于 EF 但不适用于 EF Core。你知道为什么行为不同吗?
  • 他们在行为上肯定有很多不同。但是,如果这在 EF6 中特别有效,我会感到非常惊讶。会检查何时有时间,虽然我们无能为力 - 就是这样。
  • 我目前正在做的是将一个项目从 ef6 迁移到核心,这是我遇到的事情之一..
猜你喜欢
  • 1970-01-01
  • 2014-03-15
  • 1970-01-01
  • 2011-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-02
相关资源
最近更新 更多