ここでネクロスレッドを作成したくはありませんでしたが、この正確な問題を解決する方法を探していました。
これが私の解決策です。少し時間がかかりますが、CodeFirstプログラミングへのはるかにスケーラブルなアプローチが可能になります。また、可能な限りPOCOを維持しながら、SoCを可能にする戦略パターンを紹介します。
手順1:エンティティプリミティブとインターフェイスを作成します。
IEntityインターフェイス:
/// <summary>
/// Represents an entity used with Entity Framework Code First.
/// </summary>
public interface IEntity
{
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
int Id { get; set; }
}
IRecursiveEntityインターフェイス:
/// <summary>
/// Represents a recursively hierarchical Entity.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public interface IRecursiveEntity <TEntity> where TEntity : IEntity
{
/// <summary>
/// Gets or sets the parent item.
/// </summary>
/// <value>
/// The parent item.
/// </value>
TEntity Parent { get; set; }
/// <summary>
/// Gets or sets the child items.
/// </summary>
/// <value>
/// The child items.
/// </value>
ICollection<TEntity> Children { get; set; }
}
エンティティ抽象クラス:
/// <summary>
/// Acts as a base class for all entities used with Entity Framework Code First.
/// </summary>
public abstract class Entity : IEntity
{
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
public int Id { get; set; }
}
RecursiveEntity抽象クラス:
/// <summary>
/// Acts as a base class for all recursively hierarchical entities.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public abstract class RecursiveEntity<TEntity> : Entity, IRecursiveEntity<TEntity>
where TEntity : RecursiveEntity<TEntity>
{
#region Implementation of IRecursive<TEntity>
/// <summary>
/// Gets or sets the parent item.
/// </summary>
/// <value>
/// The parent item.
/// </value>
public virtual TEntity Parent { get; set; }
/// <summary>
/// Gets or sets the child items.
/// </summary>
/// <value>
/// The child items.
/// </value>
public virtual ICollection<TEntity> Children { get; set; }
#endregion
}
注:このクラスに関して、この投稿の編集を提案する人もいます。クラスは、再帰エンティティのみを処理するように制約されるのではRecursiveEntity<TEntity>
なく、制約としてのみ受け入れる必要があります。IEntity
これは、タイプの不一致の例外を軽減するのに役立ちます。代わりに使用する場合はIEntity
、不一致の基本タイプに対抗するためにいくつかの例外処理を追加する必要があります。
を使用IEntity
すると完全に有効なコードが得られますが、すべての状況で期待どおりに機能するとは限りません。利用可能な最上位のルートを使用することが常にベストプラクティスであるとは限りません。この場合、そのルートレベルよりも継承ツリーのさらに下に制約する必要があります。それが理にかなっていることを願っています。最初は遊んでいましたが、データベースにデータを入力するときに大きな問題が発生しました。特に、きめ細かいデバッグ制御がないEntityFrameworkの移行中。
テスト中は、どちらともうまく機能していないようでしたIRecursiveEntity<TEntity>
。それを使用する古いプロジェクトを更新しているので、すぐにこれに戻る可能性がありますが、ここでの記述方法は完全に有効で機能しており、期待どおりに機能するまで調整したことを覚えています。コードのエレガンスと継承階層の間にはトレードオフがあったと思います。より高いレベルのクラスを使用するIEntity
とIRecursiveEntity<IEntity>
、との間に多くのプロパティをキャストすることになり、パフォーマンスが低下し、見栄えが悪くなります。
ステップ2:RecursiveEntityを導出します。
元の質問の例を使用しました...
カテゴリコンクリートクラス:
public class Category : RecursiveEntity<Category>
{
/// <summary>
/// Gets or sets the name of the category.
/// </summary>
/// <value>
/// The name of the category.
/// </value>
public string Name { get; set; }
}
派生していないプロパティを除いて、クラスからすべてを削除しました。Category
は、クラスの自己関連付けされたジェネリック継承から他のすべてのプロパティを派生させますRecursiveEntity
。
ステップ3:拡張メソッド(オプション)。
全体をより管理しやすくするために、親アイテムに新しい子を簡単に追加するための拡張メソッドをいくつか追加しました。私が見つけた難しさは、1対多の関係の両端を設定する必要があり、リストに子を追加するだけでは、意図したとおりに処理できないことでした。これは、長期的には大幅な時間を節約する簡単な修正です。
RecursiveEntityEx静的クラス:
/// <summary>
/// Adds functionality to all entities derived from the RecursiveEntity base class.
/// </summary>
public static class RecursiveEntityEx
{
/// <summary>
/// Adds a new child Entity to a parent Entity.
/// </summary>
/// <typeparam name="TEntity">The type of recursive entity to associate with.</typeparam>
/// <param name="parent">The parent.</param>
/// <param name="child">The child.</param>
/// <returns>The parent Entity.</returns>
public static TEntity AddChild<TEntity>(this TEntity parent, TEntity child)
where TEntity : RecursiveEntity<TEntity>
{
child.Parent = parent;
if (parent.Children == null)
parent.Children = new HashSet<TEntity>();
parent.Children.Add(child);
return parent;
}
/// <summary>
/// Adds child Entities to a parent Entity.
/// </summary>
/// <typeparam name="TEntity">The type of recursive entity to associate with.</typeparam>
/// <param name="parent">The parent.</param>
/// <param name="children">The children.</param>
/// <returns>The parent Entity.</returns>
public static TEntity AddChildren<TEntity>(this TEntity parent, IEnumerable<TEntity> children)
where TEntity : RecursiveEntity<TEntity>
{
children.ToList().ForEach(c => parent.AddChild(c));
return parent;
}
}
これらすべてを配置したら、次のようにシードできます。
シード法
/// <summary>
/// Seeds the specified context.
/// </summary>
/// <param name="context">The context.</param>
protected override void Seed(Test.Infrastructure.TestDataContext context)
{
// Generate the root element.
var root = new Category { Name = "First Category" };
// Add a set of children to the root element.
root.AddChildren(new HashSet<Category>
{
new Category { Name = "Second Category" },
new Category { Name = "Third Category" }
});
// Add a single child to the root element.
root.AddChild(new Category { Name = "Fourth Category" });
// Add the root element to the context. Child elements will be saved as well.
context.Categories.AddOrUpdate(cat => cat.Name, root);
// Run the generic seeding method.
base.Seed(context);
}