次のデータベーステーブルについて考えてみます。残念ながら、テーブルを変更することはできません。
Houses
には、Id
という名前の自動インクリメントIDフィールド、Name
という名前の文字列フィールド、およびAreaId
という名前の整数フィールドがあります。後者はAreas
テーブルへの外部キーではありません。
Areas
には、AreaId
、CountryId
、およびLangId
で構成される複合キーがあります。同じAreaId
を持つエリアが存在できますが、CountryId
とLangId
は異なります。例:同じAreaId
でLangId
が異なる2つの行が存在する可能性があります。
注:House
に複数のArea
があるのはなぜですか? House
には複数のArea's, it only has one
Area. The
Area`sテーブルには複合キーがありません。つまり、特定の行に複数の変換があります。例:エリアID 5には、英語の場合はLangId 5、スペイン語の場合はLangId3が含まれる場合があります。
2つのテーブルは、次の2つのC#クラスによって記述されます。
public class House
{
public int Id { get; set; }
[MaxLength(80)]
public string Name { get; set; }
public int? AreaId { get; set; }
[ForeignKey("AreaId")]
public List<Area> Areas { get; set; }
}
public class Area
{
public int AreaId { get; set; }
public int CountryId { get; set; }
public string LangId { get; set; }
public string Name { get; set; }
}
複合キーは、ドキュメントに記載されているとおりに、コンテキストで定義されます。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Area>()
.HasKey(a => new { a.AreaId, a.CountryId, a.LangId });
}
たとえば、データベース内のすべての家のリストを、それぞれのエリアを含めて取得しましょう。
_context.Houses.Include(h => h.Areas).ToList();
次のSQLが出力ウィンドウで生成され、結果のリストには、エリアと誤って一致したハウスが含まれます。
SELECT [a].[AreaId], [a].[CountryId], [a].[LangId], [a].[Name]
FROM [Areas] AS [a]
WHERE EXISTS (
SELECT 1
FROM [Houses] AS [h]
WHERE [a].[AreaId] = [h].[Id])
ORDER BY [a].[Id]
ご覧のとおり、EntityFrameworkは[a].[AreaId]
を[h].[Id]
ではなく[h].[AreaId]
に関連付けます。この関係をEFでどのように表現できますか?
EFでこれを正しくマッピングすることはできません。 House
がArea
を参照するようにする場合、外部キーはArea
の複合キーと同じフィールドで構成する必要があります。そうでない場合、EFはマッピングを受け入れません。回避策は、マッピングをスキップし、必要に応じてエンティティを手動で結合することですが、それは本当の問題を隠します:貧弱な設計。
主な設計上の欠陥は、翻訳を追加するときにArea
を複製する必要があることです。さて、問題は-そして常にそうなる-私の物理的なArea
エンティティを表すレコードはどれですか?リレーショナルデータベースの基本的な前提は、エンティティがuniqueレコードで表されることです。あなたのデザインはそのコア原則に違反しています。
残念ながら、テーブルを変更することはできません。
まあ、彼らはすべきです!このままにしておくことは考慮されるべきではありません。ワープされたリレーショナルモデルを使用するべきではありません。それは、スムーズなアプリケーション開発にはあまりにも重要です。
モデルは、あなたの説明からまとめることができるので、おそらく次のようなものになるはずです。
public class House
{
public int Id { get; set; }
public string Name { get; set; }
public int? AreaId { get; set; }
public Area Area { get; set; }
}
public class Area
{
public int Id { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
public string Name { get; set; } // E.g. the name in a default language
public ICollection<AreaTranslation> AreaTranslations { get; set; }
}
public class AreaTranslation
{
public int AreaId { get; set; }
public int LanguageId { get; set; }
public string LocalizedName { get; set; }
}
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Language
{
public int Id { get; set; }
public string Name { get; set; }
}
このモデルでは、1つの明示的なマッピング命令が必要です(EFが残りを推測します):
modelBuilder.Entity<AreaTranslation>()
.HasKey(a => new { a.AreaId, a.LanguageId });
Area
は、実際にそこにある物理的な領域を表していることがわかります。 House
には当然1つのArea
がありますが、何らかの形で1つの領域と見なす必要があるこの奇妙なArea
sのコレクションではありません。さまざまな言語がAreaTranslation
ジャンクションクラスによって機能します。 Area
は1つのCountry
に属していると思います。