Entity Framework Core自引用一对一关系失败

3
构建迁移时,出现了以下错误:
无法确定类型为“Location”的导航属性“Location.NorthLocation”所表示的关系。请手动配置该关系,或从模型中忽略此属性。
位置实体:
public class Location
{
    public Guid Id { get; set; }

    public DateTime CreatedWhen { get; set; }
    public string CreatedBy { get; set; }
    public DateTime ModifiedWhen { get; set; }
    public string ModifiedBy { get; set; }

    public Guid? NorthLocationId { get; set; }
    public virtual Location NorthLocation { get; set; }

    public Guid? SouthLocationId { get; set; }
    public virtual Location SouthLocation { get; set; }

    public Guid? EastLocationId { get; set; }
    public virtual Location EastLocation { get; set; }

    public Guid? WestLocationId { get; set; }
    public virtual Location WestLocation { get; set; }

}

类型配置:

public class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options) : base(options)
    {
    }

    public DbSet<Location> Locations { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<T>().HasKey("Id");
        builder.Entity<T>().Property("Id").ValueGeneratedOnAdd();
        builder.Entity<T>().Property("CreatedWhen").HasDefaultValueSql("GETDATE()").ValueGeneratedOnAdd();
        builder.Entity<T>().Property("ModifiedWhen").IsRequired();
        builder.Entity<T>().Property("CreatedBy").HasMaxLength(50).IsRequired();
        builder.Entity<T>().Property("ModifiedBy").HasMaxLength(50).IsRequired();

        // Locations
        builder.Entity<Location>().HasOne(x => x.NorthLocation).WithOne(x => x.SouthLocation).HasForeignKey(typeof(Location), "NorthLocationId").OnDelete(DeleteBehavior.SetNull);
        builder.Entity<Location>().HasOne(x => x.SouthLocation).WithOne(x => x.NorthLocation).HasForeignKey(typeof(Location), "SouthLocationId").OnDelete(DeleteBehavior.SetNull);
        builder.Entity<Location>().HasOne(x => x.EastLocation).WithOne(x => x.WestLocation).HasForeignKey(typeof(Location), "EastLocationId").OnDelete(DeleteBehavior.SetNull);
        builder.Entity<Location>().HasOne(x => x.WestLocation).WithOne(x => x.EastLocation).HasForeignKey(typeof(Location), "WestLocationId").OnDelete(DeleteBehavior.SetNull);
    }

}

我的目标是创建一个位置实体(Location entity),可以自引用它自己的南/北/东/西邻居。
有人能够建议我为什么会遇到这个错误吗?
2个回答

5
您的模型配置不正确,因为它将每个导航属性映射了两次。例如,SouthLocation 既被映射为 NorthLocationId 外键的反向导航,又被映射为 SouthLocationId 的直接导航。
每个导航属性(即 NorthLocationSouthLocationEastLocationWestLocation)只能映射到一个关系(即一个外键)。
如果我删除关系配置部分的第二行和第四行,模型似乎可以正常运行。
一般情况下,在 EF Core 中,我们尝试通过让最后执行的配置获胜来处理冲突的配置,但这有一些限制,并且很难预测执行此代码时会发生什么。当然,使用 EF Core 2.0 preview1 对 SQL Server 进行操作时,我得到了一个不同的异常(SQL Server 的错误,抱怨级联删除中的循环依赖关系),因此我们可能已经改进了如何处理这种情况。
以下是代码:

    using System;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata;

    namespace ConsoleApp4
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (var db = new MyContext())
                {
                    db.Database.EnsureDeleted();
                    db.Database.EnsureCreated();
                }
            }
        }

        public class MyContext : DbContext
        {
            public DbSet<Location> Locations { get; set; }
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer(@"server=(localdb)\mssqllocaldb;database=hey;ConnectRetryCount=0");
            }

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Location>().HasKey("Id");
                modelBuilder.Entity<Location>().Property("Id")
                   .ValueGeneratedOnAdd();
                modelBuilder.Entity<Location>().Property("CreatedWhen")
                   .HasDefaultValueSql("GETDATE()")
                   .ValueGeneratedOnAdd();
                modelBuilder.Entity<Location>().Property("ModifiedWhen")
                   .IsRequired();
                modelBuilder.Entity<Location>().Property("CreatedBy")
                   .HasMaxLength(50)
                   .IsRequired();
                modelBuilder.Entity<Location>().Property("ModifiedBy")
                   .HasMaxLength(50)
                   .IsRequired();
                modelBuilder.Entity<Location>()
                   .HasOne(x => x.NorthLocation)
                   .WithOne(x => x.SouthLocation)
                   .HasForeignKey(typeof(Location), "NorthLocationId")
                   .OnDelete(DeleteBehavior.Restrict);
                modelBuilder.Entity<Location>()
                   .HasOne(x => x.EastLocation)
                   .WithOne(x => x.WestLocation)
                   .HasForeignKey(typeof(Location), "EastLocationId")
                   .OnDelete(DeleteBehavior.Restrict);
            }
        }

        public class Location
        {
            public Guid Id { get; set; }
            public DateTime CreatedWhen { get; set; }
            public string CreatedBy { get; set; }
            public DateTime ModifiedWhen { get; set; }
            public string ModifiedBy { get; set; }
            public Guid? NorthLocationId { get; set; }
            public virtual Location NorthLocation { get; set; }
            public Guid? SouthLocationId { get; set; }
            public virtual Location SouthLocation { get; set; }
            public Guid? EastLocationId { get; set; }
            public virtual Location EastLocation { get; set; }
            public Guid? WestLocationId { get; set; }
            public virtual Location WestLocation { get; set; }
        }
    }

如果排除这两行代码,那么对应的外键 ID 属性会如何运作呢?EF 是否足够聪明以便自动识别并将其填充? - Hades
在数据库中,每个南北和东西关系实际上只有一个外键。外键的位置选择(例如,是在北行还是南行)完全是任意的。这反过来更多地涉及关系型数据库设计问题,而不是EF Core问题。顺便说一句,我到目前为止还没有提到它,但我不确定跟踪南北和东西关系是否是模拟实际地理分布位置的好方法。 - divega
我尝试删除这两行代码,但是对我来说仍然会抛出相同的错误。这是为策略游戏中的空间地图而设计的。本质上,我想要能够在任何方向上“旅行”。 - Hades
使用1.1.2版本后,我删除了两个反向关系配置后出现以下错误:System.Data.SqlClient.SqlException发生 HResult = 0x80131904 Message = 引入FOREIGN KEY约束“FK_Locations_Locations_EastLocationId”到表“Locations”可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束。 无法创建约束或索引。请参阅先前的错误。这是由SQL Server强加的限制造成的。 - divega
在将 OnDelete() 调用更改为使用 DeleteBehavior.Restrict 后,我正在不更改 Location 类的情况下使用它,但必须修复其余代码中无法编译的一些东西。我将更新我的答案以包含完整清单。如果有任何遗漏,请在评论中回复。 - divega

1

我只需要在主模型中添加.HasOne()就可以解决相同的问题。

    // Locations
    builder.Entity<Location>().HasOne(x => x.NorthLocation);
    builder.Entity<Location>().HasOne(x => x.SouthLocation);
    builder.Entity<Location>().HasOne(x => x.EastLocation);
    builder.Entity<Location>().HasOne(x => x.WestLocation);

这里难道不也需要调用 HasForeignKey() 吗? - James Poulose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接