У меня есть объект User и объект Role в отношениях «многие ко многим».Они внедряются в экземпляры репозитория, чтобы иметь возможность выполнять отложенную загрузку после удаления DbContext (т. Е. Вне уровня репозитория) следующим образом:
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
IRepository repository = new Repository();
ICollection users = repository.GetAllUsers();
foreach (User user in users)
{
foreach (Role role in user.Roles)
{
...
}
}
Проблема возникает независимо от того, включена ли EF Lazy Loading и не помечено ли свойство User.Roles как виртуальное.
Но если я не буду загружать роли в Repository.GetAllUsers (), как показано ниже, и пусть лениво загруженное свойство Roles вызывает 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 (), которое приводит к тому, что коллекция ролей заполняется дважды.
Я подтвердил, что использованиеСвойство r.Roles заполняется дважды при перечислении объекта IQueryable.например, при звонке .ToList()
.Это означает, что для обхода этого поведения нет способа избежать внесения изменений в тело get () свойства Roles.Что означает размещение специфической логики EF на вашем доменном уровне и больше не делает ее независимой от данных.
Есть ли способ предотвратить это?Или есть лучший способ добиться отложенной загрузки после удаления DbContext (вне слоя Repository).