g18*_*18c 13 .net c# entity-framework entity-framework-4
我正在使用Entity Framework并有一个BusinessUnits表,它可以引用相同类型的另一个记录来形成子父层次结构.
我还有一组用户和用户权限,其中此表中定义的每个用户都应该可以访问BusinessUnit和层次结构中的所有子业务单位.用户不应该访问引用的BusinessUnit(如果存在).
如何构建LINQ查询来处理此自引用关系树并返回此用户可以访问的所有业务单位(具有子单元)?是否可以在一个查询中执行,或者我是否需要使用for循环手动构建树?
我已经看到了schema从节点到父节点的方式引用,这是否意味着我必须从最远的子节点开始一次由一个父节点构建树?
提前致谢,
克里斯
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
*/
}
Run Code Online (Sandbox Code Playgroud)
Oll*_*lly 10
好的,这里有一些东西.我们可以通过向模型添加更多属性来使这更容易.这是一个选择吗?如果是,请向实体添加集合属性.现在,我不知道您正在使用哪个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; }
}
Run Code Online (Sandbox Code Playgroud)
@Chase medallion是正确的,因为我们无法编写递归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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
选项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();
}
Run Code Online (Sandbox Code Playgroud)
请注意,上面两个例子可以改进,但作为一个例子,让你去.
选项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.BusinessName,
Bu.ParentBusinessUnitId
FROM UserBusinessUnits Uu
INNER JOIN BusinessUnits Bu ON Bu.ParentBusinessUnitID = Uu.BusinessUnitId)
SELECT DISTINCT
BusinessUnitID,
BusinessName,
ParentBusinessUnitID
FROM UserBusinessUnits
Run Code Online (Sandbox Code Playgroud)
您将使用以下代码来实现用户具有权限的BusinessUnit对象集合.
bm.BusinessUnits.SqlQuery(mySqlString, userId);
Run Code Online (Sandbox Code Playgroud)
上面的行和@Jeffrey建议的非常相似的代码之间有一个细微的区别.以上使用DbSet.SqlQuery(),而他使用Database.SqlQuery.后者生成未被上下文跟踪的实体,而前者返回(默认情况下)跟踪的实体.通过跟踪的实体,您可以创建和保存更改,并自动修复导航属性.如果您不需要这些功能,请禁用更改跟踪(使用.AsNoTracking()或使用Database.SqlQuery).
摘要
没有什么比用现实数据集测试更能确定哪种方法最有效.使用手工制作的SQL代码(选项3)总是可能表现最佳,但代价是更复杂的代码不太便携(因为它与底层的数据库技术联系在一起).
另请注意,您可以使用的选项取决于您正在使用的EF的"风味",当然,还取决于您选择的数据库平台.如果您想要一些更具体的指导说明,请使用额外信息更新您的问题.
EntityObject
)代码生成技术还是T4模板? 归档时间: |
|
查看次数: |
17568 次 |
最近记录: |