EF Core中能否抽象带附加数据的桥接实体?
我正在探索EF Core 8的设计空间,当前需要抽象EF Core中带附加数据的桥接实体的部分配置,但创建数据库迁移时遇到了错误,具体情况如下:
抽象桥接类定义
public abstract class Bridge<Q,R,S,T> where Q : Entity<R> where S : Entity<T> { protected internal Q Left { get; set; } protected internal R LeftId { get; set; } protected internal S Right { get; set; } protected internal T RightId { get; set; } }
具体桥接实体实现
public class TestBridgeEntity : Bridge<LeftSideEntity, Guid, RightSideEntity, Guid> { public LeftSideEntity MyLeftSideEntity { get { return Left; } set { Left = value; } } public Guid LeftSideEntityId { get { return LeftId; } set { LeftId = value; } } public RightSideEntity MyRightSideEntity { get { return Right; } set { Right = value; } } public Guid RightSideEntityId { get { return RightId; } set { RightId = value; } } public double MyTestData { get; set; } }
尝试的配置扩展方法
public static EntityTypeBuilder<T> ConfigureBridge<T,A,B,C,D>(this EntityTypeBuilder<T> builder, string leftIdName, string rightIdName) where T : Bridge<A,B,C,D> where A: Entity<B> where C:Entity<D> { builder.HasKey(x => new { x.LeftId, x.RightId } ); builder.HasOne(x => x.Left) .WithMany() .HasForeignKey(x => x.LeftId); builder.HasOne(x => x.Right) .WithMany() .HasForeignKey(x => x.RightId); builder.Property(e => e.LeftId).HasColumnName(leftIdName); builder.Property(e => e.RightId).HasColumnName(rightIdName); return builder; }
实体配置类
internal class MyTestBirdgeEntityConfiguration : IEntityTypeConfiguration<TestBridgeEntity> { public void Configure(EntityTypeBuilder<TestBridgeEntity> builder) { builder.ToTable("test_bridge"); builder.ConfigureBridge<TestBridgeEntity, LeftSideEntity, Guid, RightSideEntity, Guid>("left_side_id", "right_side_id"); builder.Property(e => e.MyTestData ).IsRequired(); } }
关联实体定义
public class LeftSideEntity { // ... public List<RightSideEntity> RightSideEntities { get; } public List<TestBridgeEntity> TestBridgeEntities { get; } // ... }
RightSideEntity未引用LeftSideEntities集合。
迁移错误信息
Unable to create a 'DbContext' of type 'TestDbContext'. The exception ''TestBridgeEntity.LeftId' and 'TestBridgeEntity.LeftSideEntityId' are both mapped to column 'left_side_id' in 'test_bridge', but the properties are contained within the same hierarchy. All properties on an entity type must be mapped to unique different columns.'
解决方案
原因分析
EF Core会自动检测实体的所有公共属性并尝试映射到数据库列。你的TestBridgeEntity中同时存在抽象类的LeftId(保护内部属性)和子类的LeftSideEntityId(公共包装属性),EF会将两者都视为需要映射的属性,而扩展方法中把LeftId映射到left_side_id,EF默认又会将LeftSideEntityId映射到同名列,最终导致重复映射错误。
方法1:忽略包装属性的映射
修改扩展方法,添加对包装属性的忽略配置,同时通过表达式参数传入需要忽略的属性:
public static EntityTypeBuilder<T> ConfigureBridge<T,A,B,C,D>(this EntityTypeBuilder<T> builder, string leftIdName, string rightIdName, Expression<Func<T, object>> leftNavigationWrapper, Expression<Func<T, object>> leftIdWrapper, Expression<Func<T, object>> rightNavigationWrapper, Expression<Func<T, object>> rightIdWrapper) where T : Bridge<A,B,C,D> where A: Entity<B> where C:Entity<D> { // 忽略子类的包装属性 builder.Ignore(leftNavigationWrapper); builder.Ignore(leftIdWrapper); builder.Ignore(rightNavigationWrapper); builder.Ignore(rightIdWrapper); builder.HasKey(x => new { x.LeftId, x.RightId } ); builder.HasOne(x => x.Left) .WithMany() .HasForeignKey(x => x.LeftId); builder.HasOne(x => x.Right) .WithMany() .HasForeignKey(x => x.RightId); builder.Property(e => e.LeftId).HasColumnName(leftIdName); builder.Property(e => e.RightId).HasColumnName(rightIdName); return builder; }
调用扩展方法时传入包装属性的表达式:
internal class MyTestBirdgeEntityConfiguration : IEntityTypeConfiguration<TestBridgeEntity> { public void Configure(EntityTypeBuilder<TestBridgeEntity> builder) { builder.ToTable("test_bridge"); builder.ConfigureBridge<TestBridgeEntity, LeftSideEntity, Guid, RightSideEntity, Guid>( "left_side_id", "right_side_id", x => x.MyLeftSideEntity, x => x.LeftSideEntityId, x => x.MyRightSideEntity, x => x.RightSideEntityId); builder.Property(e => e.MyTestData ).IsRequired(); } }
方法2:调整抽象类与子类的属性设计
修改抽象类的属性为受保护,子类通过new关键字直接公开同名属性,避免包装属性导致的重复映射:
修改抽象类:
public abstract class Bridge<Q,R,S,T> where Q : Entity<R> where S : Entity<T> { protected Q Left { get; set; } protected R LeftId { get; set; } protected S Right { get; set; } protected T RightId { get; set; } }
子类实现:
public class TestBridgeEntity : Bridge<LeftSideEntity, Guid, RightSideEntity, Guid> { public new LeftSideEntity Left { get { return base.Left; } set { base.Left = value; } } public new Guid LeftId { get { return base.LeftId; } set { base.LeftId = value; } } public new RightSideEntity Right { get { return base.Right; } set { base.Right = value; } } public new Guid RightId { get { return base.RightId; } set { base.RightId = value; } } public double MyTestData { get; set; } }
这种方式下,原扩展方法无需修改,EF只会映射子类的公共属性,不会出现重复映射问题。
内容的提问来源于stack exchange,提问作者WeLikeAich

