好的,这里有几件事。我们可以通过向您的模型添加更多属性来简化此操作。这是一个选择吗?如果是这样,请将集合属性添加到实体。现在,我不知道您使用的是哪个 EF API:DbContext(代码优先或 edmx)或 ObjectContext。在我的示例中,我使用了带有 edmx 模型的 DbContext API 来生成这些类。
如果您愿意,只需添加一些注释,您就可以省去 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(甚至 Entity SQL)查询。
选项 1:延迟加载
启用延迟加载后,您可以执行以下操作...
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:预加载实体
但是,有一些方法可以优化它以避免重复访问服务器。如果您在数据库中只有少量的业务单位,您可以加载整个列表。然后,由于 EF 能够自动修复关系,只需从数据库加载用户及其权限即可满足我们的所有需求。
澄清一下:此方法意味着您加载所有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 使用分层公用表表达式一键取回信息。这当然会将实现绑定到一个提供程序,可能是 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.BusinessName,
Bu.ParentBusinessUnitId
FROM UserBusinessUnits Uu
INNER JOIN BusinessUnits Bu ON Bu.ParentBusinessUnitID = Uu.BusinessUnitId)
SELECT DISTINCT
BusinessUnitID,
BusinessName,
ParentBusinessUnitID
FROM UserBusinessUnits
您将使用如下代码来具体化用户有权访问的 BusinessUnit 对象的集合。
bm.BusinessUnits.SqlQuery(mySqlString, userId);
上述行与@Jeffrey 建议的非常相似的代码之间存在细微差别。上面使用DbSet.SqlQuery()而他使用Database.SqlQuery。后者生成不受上下文跟踪的实体,而前者返回(默认情况下)跟踪的实体。跟踪实体使您能够进行和保存更改,以及自动修复导航属性。如果您不需要这些功能,请禁用更改跟踪(使用.AsNoTracking()或使用Database.SqlQuery)。
概括
没有什么能比使用真实数据集进行测试来确定哪种方法最有效。使用手工编写的 SQL 代码(选项 3)总是可能表现最好,但代价是拥有更复杂的代码,移植性较差(因为它与底层数据库技术相关联)。
另请注意,您可用的选项取决于您正在使用的 EF 的“风格”,当然,也取决于您选择的数据库平台。如果您想要一些更具体的指导来说明这一点,请使用额外信息更新您的问题。
- 你用什么数据库?
- 您的项目是使用 EDMX 文件还是先使用代码?
- 如果使用 EDMX,您是使用默认 (
EntityObject
) 代码生成技术还是 T4 模板?