我有一個多對多關係中的用戶實體和角色實體。它們與庫實例注射要能做到懶加載的DbContext已被釋放後(即庫層外),像這樣:EF預先加載包含重複實體
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
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屬性調用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()動作。
我已經確認User.Roles屬性實際上在IQueryable對象被枚舉時被填充兩次。例如當致電.ToList()
。這意味着,爲了解決這個問題,無法避免更改Roles屬性的get()主體。這意味着將EF特定的邏輯放入您的域圖層,並且不再使其與數據無關。
有沒有辦法來防止這種情況發生?還是有一個更好的方式來實現延遲加載後的DbContext已經被放置(倉庫層以外)。
是的,看起來像它會工作。但我想我會圍繞API來看看是否有另一種方式。希望這是一個真正的bug,並在最終的4.1版本中得到修復。 – Rudy 2011-04-07 13:40:51