我在多对多关系中有一个用户实体和角色实体。它们被注入了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.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()中加载Roles,并让延迟加载的Roles属性调用Repository.GetRolesByUserId(UserId),则不会返回重复的Role实体。
public ICollection<User> GetAllUsers()
{
using (var db = CreateContext())
{
// No eager loading
return db.Users.ToList();
}
}
如果我将User.Roles属性更改为始终命中存储库,则不会返回重复的Role实体。
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层之外)有更好的方法来实现延迟加载。
答案 0 :(得分:1)
也许这样的事情可行:
public class Repository : IRepository
{
public bool RunningEagerLoading { get; set; } // false by default
public ICollection<User> GetAllUsers()
{
using (var db = CreateContext())
{
try
{
RunningEagerLoading = true;
return db.Users.Include(u => u.Roles).ToList();
// Materializing (by ToList()) is important here,
// deferred loading would not work
}
finally
// to make sure RunningEagerLoading is reset even after exceptions
{
RunningEagerLoading = false;
}
}
}
// ...
}
public class User
{
// ...
public ICollection<Role> Roles
{
get
{
if (Repository.RunningEagerLoading)
return _roles; // Eager loading cares for creating collection
else
return _roles ?? (_roles = Repository.GetRolesByUserId(UserId));
}
set { _roles = value; }
}
private ICollection<Role> _roles;
public IRepository Repository { private get; set; }
}
但在我看来,这是一个丑陋的技巧编程。