既存の SQL Server データベースの上に Entity Framework を ORM として使用して、ASP.NET MVC 4 アプリケーションを構築しています。これは、コード ファーストではなく、モデル ファーストです。私は LINQ to Entities にかなり慣れていません。最近、私は多くの問題を引き起こし、オンラインでヘルプを見つけるのに非常に苦労しました。私は今それを2回解決しました。これは私の話です。
モジュールのリストを表示する必要があるビューがあります (教室での授業など)。各モジュールはユニットに属しますが、各ユニットは多くのモジュールを持つことができます。各モジュールには複数のインストラクターを配置でき、各インストラクターは多くのモジュールを教えることができます。その関係を結合するための ModuleInstructor テーブルがあります。
Unit Module ModuleInstructor Instructor
======== 1:N ======== N:1 ================== 1:N ============
UnitID ModuleID ModuleInsturctorID InstructorID
UnitName UnitID ModuleID InstructorName
StartDate InsturctorID
EntityFramework には、オブジェクト用のこれらがあります [簡潔にするために不要なプロパティは削除されています]:
public partial class Unit
{
public int UnitID { get; set; }
public string UnitName { get; set; }
public virtual ICollection<Module> Module { get; set; }
}
public partial class Module
{
public Module()
{
this.ModuleInstructor = new HashSet<ModuleInstructor>();
this.Record = new HashSet<Record>();
}
public int ModuleID { get; set; }
public Nullable<int> UnitID { get; set; }
public Nullable<System.DateTime> ModuleDate { get; set; }
public virtual Unit Unit { get; set; }
public virtual ICollection<ModuleInstructor> ModuleInstructor { get; set; }
}
public partial class ModuleInstructor
{
public int ModuleInstructorID { get; set; }
public Nullable<int> InstructorID { get; set; }
public Nullable<int> ModuleID { get; set; }
public Instructor Instructor { get; set; }
public virtual Module Module { get; set; }
}
public partial class Instructor
{
public Instructor()
{
this.ModuleInstructor = new HashSet<ModuleInstructor>();
}
public int InstructorID { get; set; }
public string InstructorName { get; set; }
public virtual ICollection<ModuleInstructor> ModuleInstructor { get; set; }
}
とにかく、表示したかった: UnitName、ModuleDate、Instructor(s) をカンマ区切りのリストで。私の問題は、Linq がインストラクターを取得するのに最も苦労したことでした。取得できるのは、インストラクター情報が読み込まれていない ModuleInstructors だけでした。
private Entities db = new Entities();
public ActionResult Index(int p = 1)
{
int pageSize = 20;
var modules = db.Modules
.Include(m => m.Unit)
.Include(m => m.ModuleInstructor)
.Include(m => m.ModuleInstructor.Instructor) //Doesn't work
.OrderByDescending(m => m.ModuleStartDate)
.Skip(pageSize * (p - 1))
.Take(pageSize);
return View(modules);
}
私の最初の解決策は、Module クラスを変更して別のコレクションを追加することでした。
public partial class Module
{
public Module()
{
this.ModuleInstructor = new HashSet<ModuleInstructor>();
this.Record = new HashSet<Record>();
}
public int ModuleID { get; set; }
public Nullable<int> UnitID { get; set; }
public Nullable<System.DateTime> ModuleDate { get; set; }
public virtual Unit Unit { get; set; }
public virtual ICollection<Instructor> Instructor { get; set; } //Added
public virtual ICollection<ModuleInstructor> ModuleInstructor { get; set; }
}
ModuleController では、インストラクター以外のすべてを取得し、次にインストラクターを個別に取得し、それらをコレクションに挿入して for ループしました。
private Entities db = new Entities();
public ActionResult Index(int p = 1)
{
int pageSize = 20;
var modules = db.Modules
.Include(m => m.Unit)
.Include(m => m.ModuleInstructor)
.OrderByDescending(m => m.ModuleStartDate)
.Skip(pageSize * (p - 1))
.Take(pageSize);
var instructors = db.Instructors.ToList();
foreach (var m in modules)
{
m.Instructor = new List<Instructor>();
foreach (var mi in m.ModuleInstructor)
{
m.Instructor.Add(instructors
.Where(i => i.InstructorID == mi.InstructorID).SingleOrDefault());
}
}
return View(modules);
}
LinqPad によると、これには 2 つの SQL ステートメントが必要です。悪くない。サーバーにかかる負担はそれほど多くありません。なぜなら、私にはインストラクターが多くなく、一度に表示されるモジュールが 20 個しかないからです。
ただし、1 回のデータベース呼び出しでそれを行う方法が必要であると考えました。これが私の新しいソリューションです。Modules オブジェクトの Instructor コレクションに Instructors を挿入するループは引き続き行いますが、すべての Instructors を他のすべての Instructor と共に取得します。
public ActionResult Index(int p = 1)
{
int pageSize = 20;
var modules = db.Modules
.Include(m => m.Unit)
.Include(m => m.ModuleInstructor)
.Include(m => m.ModuleInstructor.Select(mi => mi.Instructor)) //New 'trick'
.OrderByDescending(m => m.ModuleStartDate)
.Skip(pageSize * (p - 1))
.Take(pageSize);
foreach (var m in modules)
{
m.Instructor = new List<Instructor>();
foreach (var mi in m.ModuleInstructor)
{
//Adds from the ModuleInstructor instead of pairing from another list
m.Instructor.Add(mi.Instructor);
}
}
return View(modules);
}
LinqPad によると、SQL ステートメントのみを使用し、最初の方法よりもわずかに高速に実行されます。
正直なところ、これがどのように機能し、なぜ機能するのかを正確に理解するのは困難ですが、機能します。「ちょうどいい」問題にたどり着くまで何度も戻ってきて、問題に固執したことをとてもうれしく思います。
私の1つの質問:これはこれを行うための最良の方法ですか、それとももっと良い方法がありますか?
明確化:データベース テーブルを変更できず、クラスの変更にも関心がありません。データ構造をそのままにして、この操作を行うためのより良い方法があるかどうかを尋ねています。StackOverflow でのこの特定の状況に対する答えはないようで、私はここで取得しようとしています。質問には私のすべての試みが含まれており、回答が得られない場合は、私自身の最終的な解決策で回答します。