我有用户和角色模型。两个模型之间生成多对多关系,由两端的集合表示:
ICollection<User> Users {get;set:} in the Role table
ICollection<Role> Roles {get;set:} in the User table
根据文档,默认情况下 EF 在 Sql Server DB 或其他引擎中生成中间表,其中包含两个表的外键。
当我尝试进行查询时,例如:
var user = await context.Users.Include(x=> x.Roles).SingleOrDefault(x=> x.UserId == 1);
它不会加载与用户关联的角色集合。我想澄清一下,用户 1 在数据库中分配了多个角色。
但是如果我运行以下代码:
var user = await context.Users
.AsNoTracking()
.Include(x=> x.Roles).SingleOrDefault(x=> x.UserId == 1);
角色集合确实带来了分配给用户的角色。我需要查询不是将其发送到视图,而是与从数据库检索的用户和角色数据进行交互,添加新角色,或删除其中之一,或编辑一些用户数据等。 我不知道是否有人有相同的经历,但是 EF 8.0.10 的这种行为对我来说似乎很奇怪。
如果有人能在这个问题上帮助我,我将非常感激。
我使用EntityFramework版本7.0.11,测试也不起作用。 我回到 8.0.10 版本并尝试显式加载集合:
await context.Entry ( user ).Collection ( x => x.Roles ).LoadAsync ();
它也不起作用。它不加载数据。
我只需要通过安装代理来尝试延迟加载,但这不是我需要的。
我相信我发现了问题。我正在开发的应用程序使用带有 DDD 的干净架构来丰富模型。对于包含多个属性的 User 实体,我将它们全部定义为 ObjectValues。例如,对于Id,我创建了一个名为UserId的类;对于 name 属性,我创建了一个名为 UserName 的类,等等。我对 Role 实体应用了相同的方法,为 Id 创建 RoleId 类,为角色名称创建 UserRole。
// 对象值/用户
public class UserId
{
public Guid Value { get; }
private UserId(Guid value) => Value = value;
public static UserId Of(Guid value)
{
ArgumentNullException.ThrowIfNull(value);
if (value == Guid.Empty)
{
throw new Exception("UserId cannot be empty");
}
return new UserId(value);
}
}
public class UserName
{
public string Value { get; }
private UserName(string value) => Value = value;
public static UserName Of(string value)
{
ArgumentNullException.ThrowIfNull(value);
if (string.IsNullOrWhiteSpace(value))
{
throw new Exception("UserName cannot be empty");
}
return new UserName(value);
}
}
// Object Values / Role
public class RoleId
{
public Guid Value { get; }
private RoleId(Guid value) => Value = value;
public static RoleId Of(Guid value)
{
ArgumentNullException.ThrowIfNull(value);
return new RoleId(value);
}
}
public class RoleName
{
public string Value { get; }
private RoleName(string value) => Value = value;
public static RoleName Of(string value)
{
ArgumentNullException.ThrowIfNull(value);
if (string.IsNullOrWhiteSpace(value))
{
throw new Exception("RoleName cannot be empty");
}
return new RoleName(value);
}
}
接下来,在每个用户和角色模型中,我创建了各自的导航属性。在 User 实体中,我创建了 Roles 属性,如下所示:
public List<Role> {get;set;}.
在角色实体中,我创建了用户导航属性:
public List<User> Users {get;set;}.
public class User : Entity<UserId>
{
public UserName? UserName { get; set; }
public List<Role> Roles { get; set; } = [];
public User() { }
public User(Guid id, string userName, string userEmail)
{
Id = UserId.Of(id);
UserName = UserName.Of(userName);
}
public static User Create(Guid id, string userName)
{
return new User(id, userName);
}
}
public class Role : Entity<RoleId>
{
public RoleName RoleName { get; set; } = default!;
public List<User> Users { get; } = [];
public Role() { }
public Role(Guid id, string roleName)
{
Id = RoleId.Of(id);
RoleName = RoleName.Of(roleName);
}
public static Role Create(Guid id, string roleName)
{
return new Role(id, roleName);
}
}
在数据库上下文 (SQL Server) 的 OnModelCreate 方法中,我定义了表名称、主键,并将 ObjectValue 属性转换为原始值,如下所示: 例如,对于 User 实体 Id,我这样做了:
builder.Property(u => u.Id).HasConversion(id => id.Value, value => new UserId(value));
。我将相同的逻辑应用于其余的用户属性以及角色实体。最后我将User和Role的关系定义为多对多,如下图:
// 用户
builder.ToTable("Users");
builder.HasKey(u => u.Id);
builder.Property(u => u.Id).HasConversion(id => id.Value, value => UserId.Of(value));
builder.Property(u => u.UserName).HasConversion(prop => prop!.Value, value => UserName.Of(value));
// Roles
builder.ToTable("Roles");
builder.HasKey(r => r.Id);
builder.Property(r => r.Id).HasConversion(id => id.Value, value => RoleId.Of(value));
builder.Property(r => r.RoleName).HasConversion(prop => prop!.Value, value => RoleName.Of(value));
// Many-to-Many Relationship
modelBuilder.Entity<User>()
.HasMany(u => u.Roles)
.WithMany(ur => ur.Users);
然后,通过控制器方法中注入的上下文,我尝试执行一个简单的查询来检索由 Id =“58c49479-ec65-4de2-86e7-033c546291aa”标识的用户及其分配的角色,如下所示:
var user = await _context.Users
.Include(user => user.Roles)
.Where(user => user.Id == UserId.Of("58c49479-ec65-4de2-86e7-033c546291aa"))
.SingleOrDefaultAsync();
执行此查询时,它没有返回用户的关联角色(确实存在),并且生成了一个异常,表明查询返回了多个与过滤器匹配的记录,但事实并非如此,因为只有一个用户在数据库中具有单一角色。经过多次试验和错误后,我决定用原始值(在本例中为 Guid)替换用作用户和角色实体标识符的 ObjectValues。我在 OnModelCreate 中为用户和角色删除了 ObjectValue-to-Primitive 转换行,从而产生以下设置:
// 用户
builder.ToTable("Users");
builder.HasKey(u => u.Id);
builder.Property(u => u.UserName).HasConversion(prop => prop!.Value, value => new UserName(value));
// Roles
builder.ToTable("Roles");
builder.HasKey(r => r.Id);
builder.Property(r => r.RoleName).HasConversion(prop => prop!.Value, value => new RoleName(value));
// Many-to-Many Relationship
modelBuilder.Entity<User>()
.HasMany(u => u.Roles)
.WithMany(ur => ur.Users);
我还修改了用户和角色实体:
public class User : Entity<Guid>
{
public UserName? UserName { get; set; }
public List<Role> Roles { get; set; } = [];
public User() { }
public User(Guid id, string userName)
{
Id = id;
UserName = UserName.Of(userName);
}
public static User Create(Guid id, string userName)
{
return new User(id, userName);
}
}
public class Role : Entity<Guid>
{
public RoleName RoleName { get; set; } = default!;
public List<User> Users { get; } = [];
public Role() { }
public Role(Guid id, string roleName)
{
Id = id;
RoleName = RoleName.Of(roleName);
}
public static Role Create(Guid id, string roleName)
{
return new Role(id, roleName);
}
}
重新配置实体后,我再次运行查询,瞧!用户现在按预期返回关联的角色。我不确定 EFC 8 对于 ObjectValue 类型的实体标识符会发生什么,但它似乎不能很好地处理它们。目前,我更喜欢使用标识符的原始数据类型来避免这些问题。如果这可以帮助别人,那就太好了。或者,如果有人知道如何解决或解决这个问题,我很想听听。干杯!