[英]Entity Framework Core 2.1.14 DB First Missing Foreign Key on non-primary candidate key of a misconfigured data type
首先使用 Entity Framework Core 2.1.14 和 MySQL 數據庫:
tl;博士
...無法更改數據庫。
我有兩個損壞的表,它們之間存在隱式的“外鍵”關系,在錯誤的候選鍵而不是主鍵上,並且“外鍵”字段是錯誤的數據類型。 這兩個表之間的數據庫中沒有正式的外鍵。 我將“外鍵”放在引號中,因為它不是外鍵,但應該表現得像一個外鍵。
引用的父候選鍵是 int 類型。 引用子“外鍵”字段的類型為 bigint。
細節:
我需要將數據寫入數據庫並直接匹配不維護正式外鍵的表之間的隱式表關系,它們的關系存在於錯誤的候選者上(它應該是唯一的,但由於沒有數據完整性控制這個表,它不可避免地接收重復並接受它們)而不是主表,並且數據類型在引用表中被交叉。 我需要實體框架在相同的上下文中編寫兩者context.SaveChanges();
正確操作和管理這種關系。
我已經擴展了 DbContext,設置了 ValueConverters,設置了PrincipalKey
並設置了導航屬性。 每當我嘗試SaveChanges()
時,當我將 Converted CandidateKeyField
和BorkedForeignKey
用於此關系時,數據類型就會不匹配。
當我使用PrincipalKey
和BorkedForeignKey
時,我從上下文中得到一個錯誤,即每當我 go 在其 Nav 集合中添加一個帶有BorkedChildEntity
條目的新BorkedParentEntity
時, PrincipalKey is not mapped
。
這是我所做的工作的表示:
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
屬性僅包含在部分實體 class 中,用於直接引用它的嘗試。
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 BorkedForeignKeyField
與BorkedParentEntity
的 int CandidateKeyField
匹配,以將 BorkedParentEntity 的CandidateKeyField
放入BorkedChildEntity
BorkedParentEntity
BorkedForeignKeyField
中嗎?
這個復雜問題的解決方案是“所有上述方法”++。
這三個問題必須相互依存地處理。
首先,消除BorkedParentEntity
上的PrincipalKey
屬性。 這是不必要的,而且適得其反。
候選鍵上的隱式外鍵本身通過 2 個步驟解決:
a) 手動將適當的 NavigationProperties 添加到 Entity 類的部分。 就我而言,這會將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);
});
}
}
這解決了缺失的關系和關閉候選人的便利。
無論我做了什么(我想我嘗試了所有可用的轉換排列),我都無法讓實體框架在映射的屬性類型未對齊的情況下兌現我的轉換類型。 我對這個問題的解決方案是在該實體的部分 class 中添加 CandidateKeyField 的重復實現,並向在該字段上遇到構建錯誤的任何人指示 go 刪除腳手架實體 ZA2F2ED4F8EBC29AB61DZC21 中相應生成的屬性。 手動輸入的屬性是使用關系的預期數據類型輸入的。
//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
在配置 BorkedParentEntity 時在BorkedParentEntity
OnModelCreating(ModelBuilder modelBuilder)
內部,現在添加
entity.Property(e => e.CandidateKeyField).HasConversion<int>();
這些實體現在將按照我的預期持續存在。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.