繁体   English   中英

在Entity Framework中获取父部门节点

[英]Get parent department node in Entity Framework

我有一个像这样的SQL表:

myDepartment

DepartmentID是部门的主管。 我通过这个表构建了一个树(在ASP.net(C#)项目中):

树

上面树中的记录是:

记录

我需要让父母在这棵树上。

我可以在SQL Server中这样做(例如id=2id是输入参数):

with cte1
as
(
select id,name,DepartmentID, 0 AS level 
from Department 
where id =2
union all 
select Department.ID,Department.name,Department.DepartmentID, level+1  
from Department 
inner join cte1 on Department.ID=cte1.DepartmentID
)
select * from cte1

输出(id = 2(A))

产量

输出(id = 4(A1))

两个更高级别

我知道EF不支持cte ,但我需要在EF中获得此结果。

如果有人可以解释这个问题的解决方案将是非常有帮助的。

这些帖子与您的问题类似。请看这些:

写递归使用CTE-实体框架流利,语法或内联语法
转换-SQL语句是-含有与- CTE对LINQ

我认为没有办法编写一个可以获取所有内容的LINQ to SQL查询但是,LINQ支持一种执行查询的方法(足够严格地称为DataContext.ExecuteQuery)。 看起来你可以使用它来调用任意一段SQL并将其映射回LINQ。

请参阅此文章: common-table-expression-in-entityframework

我能想到的最简单的方法是在EF中映射关系,然后检索所有部门,然后从该列表中获取根父级。 所有这些都应该加载到内存中,EF将使用映射来处理树结构。 或者,您可以启用延迟加载并获取父级,但随后每个子级或子级集将在检索期间由EF执行查询。

模型

public class Department
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? DepartmentId { get; set; }
    public Department ParentDepartment { get; set; }
    public virtual ICollection<Department> ChildDepartments { get; set; }
}

映射(使用流利)

public DbSet<Department> Departments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // other mapping code

    modelBuilder.Entity<Department>()
      .HasOptional(x => x.ParentDepartment)
      .WithMany(x => x.ChildDepartments)
      .HasForeignKey(x => x.DepartmentId);

    // other mapping code
}

急切检索根父母

using (var context = new YourDbContext())
{
    var allDepartments = context.Departments.ToList(); // eagerly return everything
    var rootDepartment = allDepartments.Single(x => x.DepartmentId == null);
}

仅检索根父级然后使用延迟加载,请注意DbContext需要可用于延迟加载才能工作,并且还必须在DbContext上启用它

using (var context = new YourDbContext())
{
    var rootDepartment = context.Departments.Single(x => x.DepartmentId == null);
   // do other stuff, as soon as context is disposed you cant lazy load anymore
}

尝试其中一个,

1-

int _ID = 2; // ID criteria
List<object> result = new List<object>(); // we will use this to split parent at child, it is object type because we need Level

 var departments = entites.Departments.Where(x => x.ID == _ID).SelectMany(t => entites.Departments.Where(f => f.ID == t.DepartmentID),
            (child, parent) => new { departmentID = child.DepartmentID, Name = child.Name, ID = child.ID, level = 0,
                Parent = new { DepartmentID = parent.DepartmentID, Name = parent.Name, ID = parent.ID, level = 1 }});
        // first we check our ID (we take A from where criteria), then with selectmany T represents the Department A, we need
        // department A's departmentID to find its parent, so another where criteria that checks ID == DepartmentID, so we got T and the new list 
        // basically child from first where parent from second where, and object created.

        // for showing the results
        foreach (var item in departments)
        {
            result.Add(new { DepartmentID = item.departmentID,ID = item.ID, level= item.level,Name = item.Name}); // child added to list
            result.Add(new { DepartmentID = item.Parent.DepartmentID, ID = item.Parent.ID, level = item.Parent.level, Name = item.Parent.Name }); // parent added to list
        }

结果; 在此输入图像描述

2-

List<object> childParent = new List<object>();
// basically get the child first
Departments child1 = entites.Departments.Where(x => x.ID == _ID).FirstOrDefault();
// find parent with child object
Departments parent1 = entites.Departments.Where(x => x.ID == child1.DepartmentID).FirstOrDefault();
// create child object with level
childParent.Add(new { child1.DepartmentID, child1.ID,child1.Name , level = 0});
// create parent object with level
childParent.Add(new { parent1.DepartmentID,parent1.ID,parent1.Name, level = 1 });

结果 (不是相同的图像,检查列标题文本); 在此输入图像描述

编辑1:

3-另一种方式,通过给出ID作为输入并假设ID列是唯一的,因此在数组中总会有2个值,并且通过返回列表,项目的索引实际上代表它们的级别。 (不会添加结果,因为它们是相同的:))。顺便说一句,你也可以使用Union而不是Concat

var ress = list.Where(x=> x.ID ==2)
               .SelectMany(x=> list.Where(c=> c.ID == x.ID).Concat(list.Where(s => s.ID == x.DepartmentID))).ToList();

            DataTable dt = new DataTable();
            dt.Columns.Add("DepartmentID");
            dt.Columns.Add("ID");
            dt.Columns.Add("Name");
            dt.Columns.Add("Level");
            for (int i = 0; i < ress.Count(); i++)
            {
                dt.Rows.Add(ress[i].DepartmentID, ress[i].ID, ress[i].Name, i);
            }
            dataGridView1.DataSource = dt;

编辑2

在linq中没有cte,基本上是使用view,sp是第一个选择,但这里是一个解决方案,它可能有点推。 无论如何它给出了结果。

 List<Departments> childParent = new List<Departments>();
 // or basically get the child first
 Departments child1 = entites.Departments.Where(x => x.ID == 7).FirstOrDefault();
 // find parent with child object
 Departments parent1 = entites.Departments.Where(x => x.ID == child1.DepartmentID).FirstOrDefault();
 // create child object with level
 Departments dep = new Departments(); // I add to department class a string level field
 dep.DepartmentID = child1.DepartmentID;
 dep.ID = child1.ID;
 dep.Name = child1.Name;
 dep.level = 0; // first item
 childParent.Add(dep);
 // create parent object with level
 dep = new Departments();
 dep.DepartmentID = parent1.DepartmentID;
 dep.ID = parent1.ID;
 dep.Name = parent1.Name;
 dep.level = 1; // parent one
 childParent.Add(dep);

 while (childParent.Select(t => t.DepartmentID).Last() != null) // after added to list now we always check the last one if it's departmentID is null, if null we need to stop searching list for another parent
 {
       int? lastDepID = childParent.Last().DepartmentID; // get last departmentID 
       Departments tempDep = entites.Departments.Single(x => x.ID == lastDepID); // find as object
       tempDep.level = childParent.Last().level + 1; // increase last level
       childParent.Add(tempDep); // add to list          
 }

(增加了另外一个C1来检查第4级)

在此输入图像描述

希望有帮助,

下面是简单的控制台项目程序类代码。

您可以使用不同的ID检查GetParentSet方法的输入参数。

class Program
{
    static void Main(string[] args)
    {
      Program p = new Program();
      var result=  p.GetParentSet(6);
        foreach(var a in result)
        {
            Console.WriteLine(string.Format("{0} {1} {2}",a.ID,a.Name,a.DepartmentId));
        }
       Console.Read();
    }



    private List<Department> GetParentSet(int id)
    {
        List<Department> result = new List<Department>(); //Result set
        using (RamzDBEntities context = new RamzDBEntities())
        {
            var nodeList = context.Departments.Where(t=>t.ID<=id).ToList(); //Get All the the entries where ID is below or greater than the given to the list
            var item = nodeList.Where(a => a.ID == id).SingleOrDefault(); //Get the default item for the given ID
            result.Add(item); //Add it to the list. This will be the leaf of the tree

            int size = nodeList.Count(); //Get the nodes count
            for (int i = size;  i >= 1;i--)
            {
                var newItem=    nodeList.Where(j => j.ID == item.DepartmentId).SingleOrDefault(); //Get the immediate parent. This can be done by matching the leaf Department ID against the parent ID
                if (item!=null && !result.Contains(newItem)) //If the selcted immediate parent item is not null and it is not alreday in the list
                {
                    result.Add(newItem); //Add immediate parent item  to the list
                }
                if (newItem.ID == 1) //If the immediate parent item  ID is 1 that means we have reached the root of the tree and no need to iterate any more.
                    break;
                item = newItem; //If the immediate parent item ID is not 1 that means there are more iterations. Se the immediate parent as the leaf and continue the loop to find its parent

            }
        }
        return result; //return the result set
    }
}

代码本身是不言自明的。 但下面是解释。 希望这会有所帮助!

  • 首先,ID低于或等于给定ID的所有条目都被分配给List
  • 然后获取树的叶子并将其添加到名为result的列表中。 这是我们结果集的第一个元素
  • 我们按降序迭代检索的条目。 通过将父级ID与叶子的部门ID等同来获取叶子的直接父级
  • 如果此直接父级不为空且未在列表中,则将其添加到列表中。
  • 将直接父项作为叶子并继续循环,以便我们可以获得直接父项的父项。
  • 继续这一直到我们到达树根。
  • 如果直接父ID是= 1,这意味着我们已经到达树的根,我们可以打破循环。

既然你产生EDMX,你有你的DbContext并为您的模型类,包括像部门生成的代码这个截图。

您不应该修改它们,因为它们可能(将)在任何模型操作上被EF工具覆盖。 幸运的是,这两个类都是partial生成的,所以创建者会想到人们想要安全地自定义它。

以下示例是为了简化实现而不是为了获得最佳性能。 我假设包含Departments的表格不是很大,层次结构中的嵌套级别也不是很深。

  1. 在项目中创建一个新类(* .cs文件),并通过自定义方法或属性扩展自动生成的Departments类:

     using System; using System.Collections.Generic; using System.Linq; namespace CustomEF.EFStuff { public partial class Departments { public List<Departments> Hierarchy { get { List<Departments> retVal = new List<Departments>(); retVal.Add(this); using (YourAutoGeneratedContext ctx = new YourAutoGeneratedContext()) { Departments tmp = this; while(tmp.DepartmentID != null) { tmp = ctx.Departments.First(d => d.ID == tmp.DepartmentID); retVal.Add(tmp); } } return retVal; } private set { } } } } 

    扩展分部类时,请确保将其放在同一名称空间中。 在我的例子中,我将项目命名为CustomEF,并将edmx文件放在EFStuff子文件夹中,因此生成器将自动生成的类放在CustomEF.EFStuff命名空间中。

    上面的示例将允许您获取任何Departments对象的层次结构,例如

     int level = 0; foreach(Departments d in someDepartmentObject.Hierarchy) { Console.WriteLine(d.ID.ToString() + ", " + d.DepartmentID.ToString() + ", " + d.Name +", " +(level++).ToString()); } 
  2. 如果您还需要从具有ID而不是对象的某些代码中获取层次结构,则还可以创建另一个类(* .cs文件),您将在其中扩展自动生成的上下文。

     using System.Collections.Generic; using System.Linq; namespace CustomEF.EFStuff { public partial class YourAutoGeneratedContext { public List<Departments> GetDepartmentHierarchy(int departmentId) { Departments mydep = this.Departments.FirstOrDefault(d => d.ID == departmentId); if (mydep == null) { throw new System.Data.Entity.Core.ObjectNotFoundException("There is no department with ID = " + departmentId.ToString()); } return mydep.Hierarchy; } } } 

    或者,在这种情况下,你可能想移动实现完全Context类,而根本不扩展部门类(你就不必创建上下文中的其他实例,你就会有this使用)。

     using System.Collections.Generic; using System.Linq; namespace CustomEF.EFStuff { public partial class YourAutoGeneratedContext { public List<Departments> GetDepartmentHierarchy(int departmentId) { Departments tmp = this.Departments.FirstOrDefault(d => d.ID == departmentId); if (tmp == null) { throw new System.Data.Entity.Core.ObjectNotFoundException("There is no department with ID = " + departmentId.ToString()); } List<Departments> retVal = new List<Departments>(); retVal.Add(tmp); while (tmp.DepartmentID != null) { tmp = this.Departments.First(d => d.ID == tmp.DepartmentID); retVal.Add(tmp); } return retVal; } } } 

    作为另一个不成熟的用例:

     YourAutoGeneratedContext ctx = new YourAutoGeneratedContext(); level = 0; foreach (Departments currentHier in ctx.GetDepartmentHierarchy(10)) { Console.WriteLine(currentHier.ID.ToString() + ", " + currentHier.DepartmentID.ToString() + ", " + currentHier.Name + ", " + (level++).ToString()); } 

我不知道你可以信任多少数据库中的数据。 您可能希望实现一些检查,包括交叉引用部门以防止无限循环。

请注意,正式的术语“扩展类”可能适用于扩展方法,而不适用于partial类。 我从缺乏更好的一个词中使用了这个词。 如果由于某种原因,您需要返回EF本机DbSet<>而不是List<>方法/属性,则可能需要使用扩展方法。 在这种情况下,您可能需要查看: https//shelakel.co.za/entity-framework-repository-pattern/

EF6中的示例,以使所有父节点到达根节点。

public class Department
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string Name { get; set; }

    public int? ParentId { get; set; }

    public virtual Department Parent { get; set; }

    public virtual ICollection<Department> Children { get; set; }

    private IList<Department> allParentsList = new List<Department>();

    public IEnumerable<Department> AllParents()
    {
        var parent = Parent;
        while (!(parent is null))
        {
            allParentsList.Add(parent);
            parent = parent.Parent;
        }
        return allParentsList;
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM