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.
外键应该是 FeatureType
的 Type
字段。
只有当我将 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。
我看了很多类似的问题,但没有找到适用的解决方案。
我在测试期间收到以下错误消息:
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.
外键应该是 FeatureType
的 Type
字段。
只有当我将 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。