【发布时间】:2011-04-07 09:31:52
【问题描述】:
我有一个多对多关系中的用户实体和角色实体。它们被注入 Repository 实例,以便能够在 DbContext 被处置后(即在 Repository 层之外)进行延迟加载,如下所示:
public class User
{
public int UserId { get; set; }
public string UserName { get; set; }
// Lazy loaded property
public ICollection<Role> Roles
{
get { return _roles ?? (_roles = Repository.GetRolesByUserId(UserId)); }
set { _roles = value; }
}
private ICollection<Role> _roles;
public IRepository Repository { private get; set; }
}
public class Role
{
public int RoleId { get; set; }
public string Name { get; set; }
// Lazy loaded property
public ICollection<User> Users
{
get { return _users ?? (_users = Repository.GetUsersByRoleId(RoleId)); }
set { _users = value; }
}
private ICollection<User> _users;
public IRepository Repository { private get; set; }
}
public class Repository : IRepository
{
public ICollection<User> GetAllUsers()
{
using (var db = CreateContext())
{
// Using 'Include' to eager load the Roles collection for each User
return db.Users.Include(u => u.Roles).ToList();
}
}
public ICollection<Role> GetRolesByUserId(int userId)
{
using (var db = CreateContext())
{
return db.Roles.Where(r => r.Users.Any(u => u.UserId == userId))
.ToList();
}
}
public ICollection<User> GetUsersByRoleId(int roleId)
{
using (var db = CreateContext())
{
return db.Users.Where(u => u.Roles.Any(r => r.RoleId == roleId))
.ToList();
}
}
private CustomContext CreateContext()
{
var db = new CustomContext();
((IObjectContextAdapter)db).ObjectContext.ObjectMaterialized += OnObjectMaterialized;
return db;
}
private void OnObjectMaterialized(object sender, ObjectMaterializedEventArgs args)
{
if (args.Entity is User)
{
(args.Entity as User).Repository = this;
}
if (args.Entity is Role)
{
(args.Entity as Role).Repository = this;
}
}
}
public class CustomContext : DbContext
{
public CustomContext()
: base()
{
Configuration.LazyLoadingEnabled = false;
}
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
运行以下代码时,对于返回的每个 User 实体,user.Roles 中的每个 Role 实体都有重复的对
IRepository repository = new Repository();
ICollection users = repository.GetAllUsers();
foreach (User user in users)
{
foreach (Role role in user.Roles)
{
...
}
}
无论是否启用 EF 延迟加载,以及是否将 User.Roles 属性标记为虚拟,都会出现此问题。
但是,如果我不急于在 Repository.GetAllUsers() 中加载角色,并让延迟加载的角色属性调用 Repository.GetRolesByUserId(UserId),则不会返回重复的角色实体。
public ICollection<User> GetAllUsers()
{
using (var db = CreateContext())
{
// No eager loading
return db.Users.ToList();
}
}
如果我将 User.Roles 属性更改为始终命中存储库,则不会返回重复的角色实体。
public ICollection<Role> Roles
{
get { return (_roles = Repository.GetRolesByUserId(UserId)); }
set { _roles = value; }
}
看起来调用 db.Users.Include(u => u.Roles) 会触发 User.Roles 属性的 get() 操作,这会导致 Roles 集合被填充两次。
我已经确认,在枚举 IQueryable 对象时,User.Roles 属性实际上被填充了两次。例如拨打.ToList()时。这意味着,为了解决此行为,无法避免对 Roles 属性的 get() 主体进行更改。这意味着将 EF 特定逻辑放在您的域层中,而不再使其与数据无关。
有没有办法防止这种情况发生?或者有没有更好的方法在 DbContext 被释放后(在 Repository 层之外)实现延迟加载。
【问题讨论】:
标签: c# entity-framework repository lazy-loading code-first