2011-04-07 46 views
0

我有一個多對多關係中的用戶實體和角色實體。它們與庫實例注射要能做到懶加載的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已經被放置(倉庫層以外)。

回答

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; } 
} 

但它是一個醜陋的絕招編程在我的眼前。

+0

是的,看起來像它會工作。但我想我會圍繞API來看看是否有另一種方式。希望這是一個真正的bug,並在最終的4.1版本中得到修復。 – Rudy 2011-04-07 13:40:51