我还有一组用户和用户权限,其中在此表中定义的每个用户都可以访问BusinessUnit和层次结构中的所有子业务单元.用户不应该访问所引用的BusinessUnit(如果存在).
如何处理这个自引用关系树并返回用户可以访问的所有业务单元(包含子单元)?是否可以在一个查询中执行此操作,还是需要自行手动构建一个循环的树?
我以这种方式从节点到父节点看到了模式的引用,这是否意味着我必须从最远的子节点开始,一次由一个父项构建树?
提前致谢,
克里斯
class BusinessUnit { int BusinessUnitID {get;set;} public string BusinessName {get;set;} BusinessUnit ParentBusinessUnit {get;set;} } class User { int UserID {get;set;} string Firstname {get;set;} } class UserPermissions { [Key,ForeignKey("BusinessUnit"),Column(Order = 0)] BusinessUnit BusinessUnit {get;set;} [Key,ForeignKey("User"),Column(Order = 1)] User User {get;set;} } IEnumerable<BusinessUnit> GetUnitsForWhichUserHasAccess(User user) { /* Example 1 given: BusinessUnitA (ID 1) -> BusinessUnitB (ID 2) -> BusinessUnitC (ID 3) with user with ID 1: and UserPermissions with an entry: BusinessUnit(2),User(1) the list { BusinessUnitB,BusinessUnitC } should be returned */ /* Example 2 given: BusinessUnitA (ID 1) -> BusinessUnitB (ID 2) -> BusinessUnitC (ID 3) with user with ID 1: and UserPermissions with an entry: BusinessUnit(1),User(1) the list { BusinessUnitA,BusinessUnitB,BusinessUnitC } should be returned */ }
解决方法
如果您愿意,使用几个注释,您可以省去edmx文件.
public partial class BusinessUnit { public BusinessUnit() { this.ChlidBusinessUnits = new HashSet<BusinessUnit>(); this.UserPermissions = new HashSet<UserPermissions>(); } public int BusinessUnitID { get; set; } public string BusinessName { get; set; } public int ParentBusinessUnitID { get; set; } public virtual ICollection<BusinessUnit> ChlidBusinessUnits { get; set; } public virtual BusinessUnit ParentBusinessUnit { get; set; } public virtual ICollection<UserPermissions> UserPermissions { get; set; } } public partial class User { public User() { this.UserPermissions = new HashSet<UserPermissions>(); } public int UserID { get; set; } public string FirstName { get; set; } public virtual ICollection<UserPermissions> UserPermissions { get; set; } } public partial class UserPermissions { public int UserPermissionsID { get; set; } public int BusinessUnitID { get; set; } public int UserID { get; set; } public virtual BusinessUnit BusinessUnit { get; set; } public virtual User User { get; set; } } public partial class BusinessModelContainer : DbContext { public BusinessModelContainer() : base("name=BusinessModelContainer") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { throw new UnintentionalCodeFirstException(); } public DbSet<BusinessUnit> BusinessUnits { get; set; } public DbSet<User> Users { get; set; } public DbSet<UserPermissions> UserPermissions { get; set; } }
@Chase大奖章是正确的,因为我们无法编写递归LINQ(甚至实体sql)查询.
选项1:Lazy Loading
启用延迟加载后,您可以执行此操作…
private static IEnumerable<BusinessUnit> UnitsForUser(BusinessModelContainer container,User user) { var distinctTopLevelBusinessUnits = (from u in container.BusinessUnits where u.UserPermissions.Any(p => p.UserID == user.UserID) select u).Distinct().ToList(); List<BusinessUnit> allBusinessUnits = new List<BusinessUnit>(); foreach (BusinessUnit bu in distinctTopLevelBusinessUnits) { allBusinessUnits.Add(bu); allBusinessUnits.AddRange(GetChildren(container,bu)); } return (from bu in allBusinessUnits group bu by bu.BusinessUnitID into d select d.First()).ToList(); } private static IEnumerable<BusinessUnit> GetChildren(BusinessModelContainer container,BusinessUnit unit) { var eligibleChildren = (from u in unit.ChlidBusinessUnits select u).Distinct().ToList(); foreach (BusinessUnit child in eligibleChildren) { yield return child; foreach (BusinessUnit grandchild in child.ChlidBusinessUnits) { yield return grandchild; } } }
选项2:预加载实体
但是,有一些方法可以优化此操作,以避免重复访问服务器.如果数据库中只有少量的业务单位,您可以加载整个列表.然后,由于EFs能够自动修复关系,只需加载用户和他的数据库权限就可以让我们所有的需要.
要澄清:这个方法意味着你加载所有的BusinessUnit实体;即使是用户没有权限的那些.但是,由于它大大减少了sql Server的“喋喋不休”,所以它仍然可以比上面的选项1更好.与下面的选项3不同,这是“纯”EF,而不依赖于特定的提供商.
using (BusinessModelContainer bm = new BusinessModelContainer()) { List<BusinessUnit> allBusinessUnits = bm.BusinessUnits.ToList(); var userWithPermissions = (from u in bm.Users.Include("UserPermissions") where u.UserID == 1234 select u).Single(); List<BusinessUnit> unitsForUser = new List<BusinessUnit>(); var explicitlyPermittedUnits = from p in userWithPermissions.UserPermissions select p.BusinessUnit; foreach (var bu in explicitlyPermittedUnits) { unitsForUser.Add(bu); unitsForUser.AddRange(GetChildren(bm,bu)); } var distinctUnitsForUser = (from bu in unitsForUser group bu by bu.BusinessUnitID into q select q.First()).ToList(); }
请注意,上述两个例子可以改进,但可以作为一个例子让你走.
选项3:使用通用表表达式定制SQL查询
如果您有大量业务单位,您可能需要尝试最有效的方法.那就是执行使用层次结构公用表表达式的自定义sql来获取一次命中的信息.这当然会将实现与一个提供商(可能是sql Server)相结合.
你的sql将是这样的:
WITH UserBusinessUnits (BusinessUnitID,BusinessName,ParentBusinessUnitID) AS (SELECT Bu.BusinessUnitId,Bu.BusinessName,CAST(NULL AS integer) FROM Users U INNER JOIN UserPermissions P ON P.UserID = U.UserID INNER JOIN BusinessUnits Bu ON Bu.BusinessUnitId = P.BusinessUnitId WHERE U.UserId = ? UNION ALL SELECT Bu.BusinessUnitId,Bu.ParentBusinessUnitId FROM UserBusinessUnits Uu INNER JOIN BusinessUnits Bu ON Bu.ParentBusinessUnitID = Uu.BusinessUnitId) SELECT DISTINCT BusinessUnitID,ParentBusinessUnitID FROM UserBusinessUnits
您将使用以下代码实现用户具有权限的BusinessUnit对象集合.
bm.BusinessUnits.sqlQuery(MysqLString,userId);
上述行与@Jeffrey建议的非常相似的代码之间存在细微差别.上述使用DbSet.SqlQuery(),而他的使用Database.SqlQuery.后者产生不被上下文跟踪的实体,而前者返回(默认情况下)跟踪的实体.跟踪的实体使您能够创建和保存更改,并自动修复导航属性.如果您不需要这些功能,请禁用更改跟踪(使用.AsNoTracking()或使用Database.SqlQuery).
概要
没有什么比实际的数据集测试,以确定哪种方法是最有效的.使用手工制作的sql代码(选项3)总是可能性能最好,但代价是使用更复杂的代码不太可移植(因为它与底层数据库技术相关).
请注意,您可以使用的选项取决于您使用的EF的“风味”,当然还有您选择的数据库平台.如果您想了解更多的具体指导,请使用额外的信息更新您的问题.
>你使用什么数据库?>您是否使用EDMX文件或首先编写代码?>如果使用EDMX,是否使用默认(EntityObject)代码生成技术或T4模板?