我真的不想在这里使用 necro-thread,但我一直在寻找如何解决这个确切问题的方法。
这是我的解决方案;它有点冗长,但它允许使用更具可扩展性的 Code First 编程方法。它还引入了策略模式以允许 SoC,同时尽可能保持 POCO。
第 1 步:创建实体原语和接口。
实体接口:
/// <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
改为使用,则需要添加一些异常处理来应对不匹配的基本类型。
UsingIEntity
将给出完全有效的代码,但它不会在所有情况下都按预期工作。使用可用的最高根并不总是最佳实践,在这种情况下,我们需要将继承树限制在比该根级别更远的位置。我希望这是有道理的。这是我一开始玩的东西,但是在填充数据库时我遇到了很大的问题;特别是在实体框架迁移期间,您没有细粒度的调试控制。
在测试期间,它似乎也不能很好地与IRecursiveEntity<TEntity>
任何一个一起玩。我可能很快就会回到这个问题,因为我正在刷新一个使用它的旧项目,但是这里写的方式是完全有效的并且可以工作,我记得调整它直到它按预期工作。我认为代码优雅和继承层次结构之间存在权衡,其中使用更高级别的类意味着在IEntity
and之间转换大量属性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 步:扩展方法(可选)。
为了使整个事情更易于管理,我添加了一些扩展方法来轻松地将新子项添加到任何父项。我发现,困难在于您需要设置一对多关系的两端,而仅仅将孩子添加到列表中并不能让您按照预期的方式处理它们。这是一个简单的修复程序,从长远来看可以节省大量时间。
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);
}