微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Entity Framework Core 2.1.14 DB First Missing Foreign Key on non-primaryCandidate key of a misconfigured data type

如何解决Entity Framework Core 2.1.14 DB First Missing Foreign Key on non-primaryCandidate key of a misconfigured data type

首先使用 Entity Framework Core 2.1.14 和 MysqL 数据库

tl;dr

...无法更改数据库

我有两个损坏的表,它们之间有一个隐式的“外键”关系,在一个错误的候选键上而不是主键上,并且“外键”字段是错误的数据类型。这两个表之间的数据库中没有正式的外键。我将“外键”放在引号中,因为它不是外键,但应该表现得像外键。

引用的父候选键是 int 类型。 引用子“外键”字段的类型为 bigint。

详情:

我需要将数据写入数据库并直接匹配不维护正式外键的表之间的隐式表关系,它们的关系存在于假候选上(它应该是唯一的,但因为没有数据该表上的完整性控制,它不可避免地接收重复项并接受它们)而不是主要的,并且数据类型在引用表中交叉。我需要实体框架在同一个 context.SaveChanges(); 操作中编写两者并正确管理这种关系。

我已经扩展了 DbContext,设置了 ValueConverters,设置了 PrincipalKey,并设置了我的导航属性。每当我尝试使用 SaveChanges() 时,当我将转换后的 CandidateKeyFieldBorkedForeignKey 用于此关系时,我都会发现数据类型不匹配。

当我使用 PrincipalKeyBorkedForeignKey 时,每当我使用 PrincipalKey is not mapped 添加新的 BorkedParentEntity 时,我都会从上下文中收到错误 BorkedChildEntity其 Nav 集合中的条目。

这是我所做的事情的代表:

public partial class BorkedParentEntity
{
   //PrimaryKeyField is generated in the generated partial to this class; including for context
   //public long PrimaryKeyField{get;set;}
   public ICollection<BorkedChildEntity> ChildrenNavigation {get;set;}

   //CandidateKeyField is generated in the generated partial to this class; including for context
   //public int CandidateKeyField { get; set; }

   //An attempt to bypass the type mismatch since ValueConverter is not being honored
   public long PrincipalKey { get { return (long)PrimaryKeyField; } }
}
public partial class BorkedChildEntity
{
   //BorkedForeignKeyField is generated in the other partial to this class; including for context
   //public long BorkedForeignKeyField {get;set;}

   public BorkedParentEntity ParentNavigation {get;set;}
}
public class myDatabaseContextNavigable : myDatabaseContext
{
   protected override void OnModelCreating(ModelBuilder modelBuilder)
   {
      base.OnModelCreating(modelBuilder);

      modelBuilder.Entity<BorkedParentEntity>(entity =>
      {
          entity.Property(e=>e.Id).ValueGeneratedOnAdd();

          entity.Ignore(e=>e.PrincipalKey); 

          entity.Property(e => e.CandidateKeyField).HasConversion<long>();

          entity.HasMany(r => r.ChildrenNavigation)
                .WithOne(s => s.ParentNavigation)
                //.HasPrincipalKey(s => s.PrincipalKey) //=>Added this PrincipalKey because my conversion was not being honored at runtime.
                //.HasPrincipalKey(s =>(long)s.CandidateKeyField) //=>Added this PrincipalKey to attempt to resolve the mismatch or at least change the error I was getting
                .HasForeignKey(r => r.BorkedForeignKeyField);
      });

      modelBuilder.Entity<BorkedChildEntity>(entity =>
      {
          entity.Property(e=>e.Id).ValueGeneratedOnAdd();
          
      });
}

这是我每次尝试收到的输出

请注意,派生的 PrincipalKey 属性仅包含在部分实体类中,以用于直接引用它的尝试。

CandidateKeyField 和 ForeignKey 上的值转换器

         entity.Property(e => e.CandidateKeyField).HasConversion<long>();

         entity.HasMany(r => r.ChildrenNavigation)
               .WithOne(s => s.ParentNavigation)
               .HasPrincipalKey(s => s.CandidateKeyField)
               .HasForeignKey(r => r.BorkedForeignKeyField);
         ...

         context.BorkedParents.Add(_new_record);//=>throws InvalidOperationException
         context.SaveChanges();//=>never reached

            system.invalidOperationException: 'The types of the properties specified for the foreign key {'BorkedForeignKeyField'} on entity type 'BorkedChildEntity' do not match the types of the properties in the principal key {'CandidateKeyField'} on entity type 'BorkedParentEntity'.'

CandidateKeyField 的手动转换

         entity.HasMany(r => r.ChildrenNavigation)
               .WithOne(s => s.ParentNavigation)
                .HasPrincipalKey(s => (long)s.CandidateKeyField) 
                .HasForeignKey(r => r.BorkedForeignKeyField);

         ...
         context.BorkedParents.Add(_new_record);//=>throws InvalidOperationException
         context.SaveChanges();//=>never reached

            system.invalidOperationException: 'The types of the properties specified for the foreign key {'BorkedForeignKeyField'} on entity type 'BorkedChildEntity' do not match the types of the properties in the principal key {'CandidateKeyField'} on entity type 'BorkedParentEntity'.'

添加 PrincipalKey 字段(不忽略属性

         entity.HasMany(r => r.ChildrenNavigation)
                .WithOne(s => s.ParentNavigation)
                .HasPrincipalKey(s => s.PrincipalKey)
                .HasForeignKey(r => r.BorkedForeignKeyField);
         ...
         context.BorkedParents.Add(_new_record); //=>succeeds
         context.SaveChanges();//=>throws dbupdateException


            Message: UnkNown column 'PrincipalKey' in 'field list'

PrincipalKey 字段(忽略属性

         entity.Ignore(e => e.PrincipalKey);         

         entity.HasMany(r => r.ChildrenNavigation)
                .WithOne(s => s.ParentNavigation)
                .HasPrincipalKey(s => s.PrincipalKey) 
                .HasForeignKey(r => r.BorkedForeignKeyField);

         ...
         context.BorkedParents.Add(_new_record); //=>succeeds
         context.SaveChanges();//=>throws dbupdateException


            Message: UnkNown column 'PrincipalKey' in 'field list'

没有PrincipalKey,没有ValueConverter

         entity.HasMany(r => r.ChildrenNavigation)
               .WithOne(s => s.ParentNavigation)
               .HasForeignKey(r => r.BorkedForeignKeyField);

         ...
         context.BorkedParents.Add(_new_record); //=>succeeds
         context.SaveChanges();//=>succeeds with bad data

            The BorkedForeignKeyField on the BorkedChildEntity table row has the value of the BorkedParent's Primary Key rather than the CandidateKey it is supposed to have.

我的想法很新鲜。谁能帮助将 BorkedChildEntity 的 bigint BorkedForeignKeyFieldBorkedParentEntity 的 int CandidateKeyField 匹配,将 CandidateKeyFieldBorkedParentEntity 放入 { {1}} 的 BorkedChildEntity数据库中?

解决方法

解决这个复杂问题的方法是“上述所有方法”++。

这三个问题必须相互依存。

  1. 首先,消除 PrincipalKey 上的 BorkedParentEntity 属性。这是不必要的,而且会适得其反。

  2. 候选键上的隐式外键本身通过 2 个步骤来解决:

a) 手动将适当的 NavigationProperties 添加到实体类的部分。就我而言,这会将 ChildrenNavigation 发送到 BorkedParentEntity 并将 ParentNavigation 发送到 BorkedChildEntity

   public partial class BorkedParentEntity
   {
      public ICollection<BorkedChildEntity> ChildrenNavigation { get;set; }
   }

   public partial class BorkedChildEntity
   {
      public BorkedParentEntity ParentNavigation {get;set;}
   }

b) 通过这些导航属性手动定义这些表之间的关系。

   public class myDatabaseContextNavigable: myDatabaseContext
   {
      public override void OnModelCreating(ModelBuilder modelBuilder)
      {
         base.OnModelCreating(modelBuilder);
         
         modelBuilder.Entity<BorkedParentEntity>(entity =>
         {
            entity.HasMany(s => s.ChildrenNavigation)
                  .WithOne(r => r.ParentNavigation)
                  .HasPrincipalKey(r => r.CandidateKeyField)
                  .HasForeignKey(s => s.BorkedForeignKeyField);
         });
      }
   }

这解决了缺失的关系以及与候选人关闭的便利。

  1. 修复列类型不匹配

无论我做什么(我想我尝试了所有可用的转换排列)我都无法让实体框架在映射的属性类型未对齐的情况下尊重我的转换类型。我对这个问题的解决方案是在该实体的部分类中添加 CandidateKeyField 的重复实现,并向在此字段上遇到构建错误的任何人添加说明,以删除脚手架实体类中相应的生成属性。手动输入的属性与关系的预期数据类型一起输入。

   //so `BorkedParentEntity` becomes:
   public partial class BorkedParentEntity
   {
      public ICollection<BorkedChildEntity> ChildrenNavigation { get;set; }
      
      //This is my note. There are many like it,but this one is mine
      //If you have a build error on a duplicated property,delete the other one or you will break stuff
      public long CandidateKeyField { get; set; }
   }

并从生成的实体类型中删除完全相同名称的属性。

剩下的最后一步是指示数据库此运行时 long 实际上映射到数据库 int

OnModelCreating(ModelBuilder modelBuilder) 中配置 BorkedParentEntity 时,现在添加

   entity.Property(e => e.CandidateKeyField).HasConversion<int>();

实体现在将按照我的预期持续存在。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。