EF Core 外键:不兼容的类型

EF Core Foreign Key: incompatible types

我看了很多类似的问题,但没有找到适用的解决方案。

我在测试期间收到以下错误消息:

System.InvalidOperationException : The relationship from 'Product.FeatureType' to 'FeatureType.Product' with foreign key properties {'Type' : string} cannot target the primary key {'Id' : Guid} because it is not compatible. Configure a principal key or a set of compatible foreign key properties for this relationship.

外键应该是 FeatureTypeType 字段。

只有当我将 Product.Type 的类型设置为 string 而不是 Guid 时才会发生这种情况。但它应该是 string,而不是 Guid。我根本不明白这里有什么问题。我以 DB-first 方法执行项目,使用此逻辑可以毫无问题地创建数据库 SQL。感谢您的帮助。

编辑:这是我的 MSSQL 型号:

CREATE TABLE [Core].[FeatureType](
    [id] [uniqueidentifier] NOT NULL,
    [int_id] [int] IDENTITY(1,1) NOT NULL,
    [type] [varchar](50) NOT NULL UNIQUE,
    [description] [uniqueidentifier] NULL,
    [SysStartTime] [datetime2](7) GENERATED ALWAYS AS ROW START NOT NULL,
    [SysEndTime] [datetime2](7) GENERATED ALWAYS AS ROW END NOT NULL,
 CONSTRAINT [PK_FeatureType] PRIMARY KEY NONCLUSTERED
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
    PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime])
) ON [PRIMARY]
WITH
(
SYSTEM_VERSIONING = ON ( HISTORY_TABLE = [Core].[FeatureTypeHistory] )
)
GO

CREATE TABLE [Core].[Product](
        [id] [uniqueidentifier] NOT NULL, 
        [name] [varchar](100) NOT NULL UNIQUE, 
        [type] [varchar](50) NOT NULL FOREIGN KEY REFERENCES [Core].[FeatureType](type),
        [SysStartTime] [datetime2](7) GENERATED ALWAYS AS ROW START NOT NULL,
        [SysEndTime] [datetime2](7) GENERATED ALWAYS AS ROW END NOT NULL,
        CONSTRAINT [PK_Product] PRIMARY KEY NONCLUSTERED ([id] ASC)
        WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) 
        ON [PRIMARY],
        PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime])
    ) ON [PRIMARY] WITH (SYSTEM_VERSIONING = ON ( HISTORY_TABLE = [Core].[ProductHistory] ))
GO

据我所知,这应该有效,因为 FeatureType table 的 Type 列是 UNIQUE.

我有以下型号:

public class Product : IDBModel
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Type { get; set; }

    public DateTime SysStartTime { get; set; }
    public DateTime SysEndTime { get; set; }

    public FeatureType FeatureType { get; }
}

public class FeatureType : IDBModel
{
    public Guid Id { get; set; }
    public string Type { get; set; }
    public Guid? Description { get; set; }

    public DateTime SysStartTime { get; set; }
    public DateTime SysEndTime { get; set; }

    public TxtID TxtIDDescription { get; set; }
    public ICollection<Feature> Feature { get; }

    public ICollection<Product> Product { get; }
 }

以及以下相关上下文配置:

public virtual DbSet<Product> Product { get; set; }
public virtual DbSet<FeatureType> FeatureType { get; set; }
...

modelBuilder.Entity<Product>(entity =>
        {
            entity.ToTable("Product", CoreSchema)
                .HasKey(k => new { k.Id }) 
                .HasName("PK_Product");
            entity.Property(a => a.Id).HasColumnName("id");
            entity.Property(a => a.Name).HasColumnName("name").IsRequired();
            entity.Property(a => a.Type).HasColumnName("type").HasMaxLength(50).IsRequired().IsUnicode(false);
            entity.Property(e => e.SysStartTime).HasColumnName("SysStartTime").ValueGeneratedOnAddOrUpdate();
            entity.Property(e => e.SysEndTime).HasColumnName("SysEndTime").ValueGeneratedOnAddOrUpdate();
            entity.HasOne(p => p.FeatureType)
                .WithMany(d => d.Product)
                .HasForeignKey(p => p.Type)
                .OnDelete(DeleteBehavior.Restrict);
        });

modelBuilder.Entity<FeatureAttributeSet>(entity =>
        {
            entity.ToTable("FeatureAttributeSet", "Core")
                .HasKey(e => new { e.Id })
                .HasName("PK_FeatureAttributeSet");

            entity.Property(e => e.Id).HasColumnName("id").IsRequired();
            entity.Property(e => e.AttributeSetId).HasColumnName("as_id").IsRequired();
            entity.Property(e => e.FeatureId).HasColumnName("feature_id").IsRequired();

            entity.Property(e => e.SysStartTime).ValueGeneratedOnAddOrUpdate();
            entity.Property(e => e.SysEndTime).ValueGeneratedOnAddOrUpdate();

            entity.HasOne(d => d.AttributeSet)
                        .WithMany(p => p.FeatureAttributeSet)
                        .HasForeignKey(d => d.AttributeSetId)
                        .OnDelete(DeleteBehavior.Cascade)
                        .HasConstraintName("FK_FeatureAttributeSet_AttributeSet");
            entity.HasOne(d => d.Feature)
                        .WithMany(p => p.FeatureAttributeSet)
                        .HasForeignKey(d => d.FeatureId)
                        .OnDelete(DeleteBehavior.Restrict)
                        .HasConstraintName("FK_FeatureAttributeSet_Feature");
        });

解决方法是配置PrincipalKey。 PrincipalKey 将允许我们定义具有唯一限制的引用键,这将是关系的目的地。 所以你可以这样使用

 modelBuilder.Entity<Product>(entity =>
    {
            entity.ToTable("Product", CoreSchema)
                .HasKey(k => new { k.Id }) 
                .HasName("PK_Product");
            entity.Property(a => a.Id).HasColumnName("id");
            entity.Property(a => a.Name).HasColumnName("name").IsRequired();
            entity.Property(a => a.Type).HasColumnName("type").HasMaxLength(50).IsRequired().IsUnicode(false);
            entity.Property(e => e.SysStartTime).HasColumnName("SysStartTime").ValueGeneratedOnAddOrUpdate();
            entity.Property(e => e.SysEndTime).HasColumnName("SysEndTime").ValueGeneratedOnAddOrUpdate();
            entity.HasOne(p => p.FeatureType)
                .WithMany(d => d.Product)
                .HasPrincipalKey(p => p.Type)
                .HasForeignKey(p => p.Type)
                .OnDelete(DeleteBehavior.Restrict);
        });

您在模型构建器语句中为 .HasForeignKey(p => p.Type) 中产品的 FeatureType 指定的外键必须与 FeatureType 的主键类型相同。因为 FeatureType 的 Id 属性 是一个 Guid,所以 Product 上的 Type 也必须是 Guid 类型才能用作与 FeatureType 关系的外键。

有什么原因不能将 Type 属性 的类型更改为 Guid?

您可以使用 value conversion.

在 EF 中对其进行建模

第一步是改变Product.Type的类型:

public class Product
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public DateTime SysStartTime { get; set; }
    public DateTime SysEndTime { get; set; }

    public Guid Type { get; set; } // <= Guid, not string
    public FeatureType FeatureType { get; }
}

这满足了 EF 的主键和外键属性应该具有相同类型的要求。

但仅此一项就会生成 SQL 并引发异常,因为数据库类型不同。这就是价值转换有用的地方。只需在 OnModelCreating 中添加一行:

entity.ToTable("Product")
    .HasKey(k => new { k.Id })
    .HasName("PK_Product");
// To convert string value from the database:
entity.Property(e => e.Type).HasConversion<string>();
...

现在 EF 接受关联并且也知道如何为查询和插入生成正确的 SQL。