web-dev-qa-db-ja.com

EntityFrameworkコアと複合キーの関係

次のデータベーステーブルについて考えてみます。残念ながら、テーブルを変更することはできません。

Database schema

Housesには、Idという名前の自動インクリメントIDフィールド、Nameという名前の文字列フィールド、およびAreaIdという名前の整数フィールドがあります。後者はAreasテーブルへの外部キーではありません。

Areasには、AreaIdCountryId、およびLangIdで構成される複合キーがあります。同じAreaIdを持つエリアが存在できますが、CountryIdLangIdは異なります。例:同じAreaIdLangIdが異なる2つの行が存在する可能性があります。

注:Houseに複数のAreaがあるのはなぜですか? Houseには複数のArea's, it only has oneArea. TheArea`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でどのように表現できますか?

6
giannoug

EFでこれを正しくマッピングすることはできません。 HouseAreaを参照するようにする場合、外部キーは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つの領域と見なす必要があるこの奇妙なAreasのコレクションではありません。さまざまな言語がAreaTranslationジャンクションクラスによって機能します。 Areaは1つのCountryに属していると思います。

14
Gert Arnold