変更できない既存のデータベースがありますが、EF を使用してアクセスしたいと考えています。データベースの 90% で、Code First EF を機能させることができ、非常に感銘を受けました。
ナビゲーション プロパティを介してデータをモデル化またはアクセスする方法を疑問に思っているケースに遭遇しました。
ある場合、表は次のようになります (この例は完全にでっち上げですが、問題を表しています)。
CREATE TABLE Dog (
id INTEGER PRIMARY KEY,
name VARCHAR(50) NULL,
breed_id INTEGER NOT NULL
);
CREATE TABLE Breed (
id INTEGER NOT NULL,
organization_id INTEGER NOT NULL,
description VARCHAR(100) NOT NULL,
Primary key (id, organization)
);
CREATE TABLE Organization (
id INTEGER PRIMARY KEY,
description VARCHAR(100) NOT NULL
);
表の品種では、組織は品種を定義した組織を表します。犬は複数の品種を持つことができますが、プログラムは 1 つだけを表示し、結果は組織 ID (プログラムのセットアップ時に設定される値) によって「フィルター処理」されます。
存在する可能性のあるデータの例は次のとおりです。
id organizationID Description
1 1 Basset Hound
2 1 Great Dane
2 2 Grande Dane
組織 2 は、組織 1 とは異なる名前で品種を呼び出すことを選択しました。固有の主キーは、id と organizationID の組み合わせです。犬には品種がありますが、1 つ以上の関連付けられた組織を定義するプロパティがありません。犬の品種を見つけるために、別のテーブルまたは構成された値 (おそらく列挙値) から追加情報を取得します。
私の場合、特定の犬種を見つけるには、犬の ID と、プログラム構成に関連する別の情報 (organization_id) が必要です。
犬、品種、組織のクラスは次のようになります。
public class Dog {
public int id { get; set; };
public string name { get; set; };
public int breedID { get; set; };
public virtual Breed { get; set; }
}
public class Breed {
public int id { get; set; };
public int organizationID { get; set; };
public string description { get; set; };
public virtual Organization { get; set; }
}
public class Organization {
public int id { get; set; };
public string description { get; set; };
}
コードに見られるように、品種を返す Dog の "Navigation" プロパティを使用したいのですが、最初にコードでこれを構成できるとは思いません。
私はいくつかの異なることを試しました(流暢なAPIで、組織を除外します-それは簡単なので)、うまくいかないと思うことも文書化します:
1)
modelBuilder.Entity<Dog>().HasKey(t => t.id);
modelBuilder.Entity<Breed>().HasKey(t => t.id);
modelBuilder.Entity<Dog>().HasRequired(d => d.Breed).WithMany().HasForeignKey(d => d.breedID);
もちろん、これには複数の品種が返され、エンティティ フレームワークが例外をスローするという問題があります。これは、品種 ID 自体がモデルが要求している単一の値を生成するには不十分であるためです。
2)
Change class dog:
Remove:
public virtual Breed { get; set; }
Add:
public virtual ICollection<Breed> Breeds { get; set; }
public Breed Breed {
get {
// Assume 1 is configured organization value
return Breeds.Single(t => t.OrganizationId == 1);
};
set {
Breeds.Add(value);
}
}
Change model:
I don't know how to do this for the given classes. Since it's a Collection, it must look something like
modelBuilder.Entity<Dog>().HasMany(d => d.Breeds)...
but I don't see how to specify that breedID is a foriegn key into the breed table.
モデルを機能させることができれば、残りは機能しますが、奇妙で非効率的に見えます。
3)
Change model to account for composite key (using first set of classes):
modelBuilder.Entity<Breed>().HasKey(b => new { b.id, b.organizationId });
modelBuilder.Entity<Dog>().HasRequired(t => t.Breed).WithMany().HasForeignKey(t => new { t.breedID, configuredValue });
最後の行のようにconfiguredValueを「注入」する方法がわからないため、これも機能しません。
上記の方法のいずれも機能しない場合、または最初にコードを適切に構成する別の方法が見つからない場合は、Breed ナビゲーション プロパティの getter が呼び出されたときに、適切なプロパティを取得できるクエリを使用する必要があることを指定したいと思います。適当に繁殖させて返します。
ただし、クエリの結果を返すために Context 呼び出しで POCO を汚したくありません。言い換えれば、次のように見えない Dog のプロパティが必要です。
public Breed Breed {
get {
return context.Breed.Where(b => b.id == this.id && b.organizationID == 1).Single();
}
}
理想的には、ナビゲーション コレクションと同じように機能し、EF が魔法のように機能して適切な結果を返します。
直感的には、POCO のようなコードを使用してこれを構成するか、プロキシを使用/拡張して構成を拡張し、アクセサーが呼び出されたときに必要な特定のクエリを使用できるようにする必要があるようです。または、読み取り時にプロパティを設定し、書き込み時にPOCOをダーティにすることができるはずです。これを行う方法を知るには、EFに精通していません。
これは可能ですか?
最初の投稿への追加として、私は自分の POCO クラスをきれいに保ちたいので、おそらくリポジトリ パターンを実装して、説明したような複雑なクエリをカプセル化し、他の操作もサポートすると思います。
Code First モデルから生成した EDMX を見ると、データベース ファーストの観点からモデルを実装する方法も明らかではありません。
どんな考えでも大歓迎です。