gpt4 book ai didi

c# - 如何在 Entity Framework 代码优先方法中映射自身的递归关系

转载 作者:可可西里 更新时间:2023-11-01 09:08:15 25 4
gpt4 key购买 nike

我想要创建的只是基本的递归类别。如果 RootCategory_Id 设置为 null,则类别为根;如果设置为某个 id,则它属于其他某个类别。我在 Seed() 方法中添加了带有两个子类别的类别进行测试,但它不起作用。 (后来查了DB,有插入)

类别模型

public class Category
{
public int ID { get; set; }
public Category RootCategory { get; set; } // This one works good, it also creates "RootCategory_Id" in database on "update-database"

public ICollection<Category> ChildCategories { get; set; } // This is always null, how to map it correctly?

public string Name { get; set; }
}

种子法

protected override void Seed(Test.Infrastructure.TestDataContext context)
{
context.Categories.Add(new Category() {
Name = "First Category", ChildCategories = new List<Category>() {
new Category(){
Name = "Second Category"
},
new Category(){
Name = "Third Category"
}
}
});

context.SaveChanges();
}

我是这样测试的,不行

public ActionResult Test()
{
// After checking DB my root is 4, and two categories that have RootCategory_Id set to 4
var c = _db.Categories.Where(x => x.ID == 4).Single();
return Content(c.ChildCategories.FirstOrDefault().Name); // Always returns null, even c.ChildCategories.Count() returns 'null'
}

我想要实现的目标的图片

这是通过使用 linq-to-sql 的数据库优先方法生成的
enter image description here

最佳答案

我真的不想在这里死机,但我一直在寻找解决这个确切问题的方法。

这是我的解决方案;它有点冗长,但它允许使用更具可扩展性的 Code First 编程方法。它还引入了策略模式以允许 SoC,同时尽可能保持 POCO。

第 1 步:创建实体基元和接口(interface)。

IEntity 接口(interface):

/// <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 接口(interface):

/// <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; }
}

递归实体抽象类:

/// <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
}

注意:一些人建议对这篇关于该类(class)的帖子进行编辑。该类只需要接受 RecursiveEntity<TEntity>作为约束,而不是 IEntity这样它就只能处理递归实体。这有助于缓解类型不匹配异常。如果你使用 IEntity相反,您需要添加一些异常处理来应对不匹配的基类型。

使用 IEntity将给出完全有效的代码,但它不会在所有情况下都按预期工作。使用可用的最顶层根并不总是最佳实践,在这种情况下,我们需要进一步限制继承树而不是根级别。我希望这是有道理的。这是我一开始玩的东西,但是在填充数据库时我遇到了很大的问题;特别是在 Entity Framework 迁移期间,您没有细粒度的调试控制。

在测试期间,它似乎也不太适合 IRecursiveEntity<TEntity>任何一个。我可能很快就会回到这个问题上,因为我正在刷新一个使用它的旧项目,但是这里写的方式是完全有效的并且可以工作,我记得调整它直到它按预期工作。我认为代码优雅和继承层次结构之间存在权衡,其中使用更高级别的类意味着在 IEntity 之间转换大量属性。和 IRecursiveEntity<IEntity> ,这反过来又给出了较低的性能并且看起来很丑陋。

第 2 步:派生递归实体。

我使用了原始问题中的示例...

类别具体类:

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; }
}

除了非派生属性之外,我已经从类中删除了所有内容。 CategoryRecursiveEntity 的自关联泛型继承中派生出所有其他属性类。

第 3 步:扩展方法(可选)。

为了使整个事情更易于管理,我添加了一些扩展方法来轻松地将新子项添加到任何父项。我发现,困难在于您需要设置一对多关系的两端,而仅仅将 child 添加到列表中并不能让您按照预期的方式处理它们。这是一个简单的修复,从长远来看可以节省大量时间。

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);
}

关于c# - 如何在 Entity Framework 代码优先方法中映射自身的递归关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12656914/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com