将.Net Framework中几段使用EF相关的代码移植到.NET的项目中,本想省点事,结果碰到莫名的故障,导致浪费不少时间来解决问题。
有两个模型使用Scarffold-Context从数据库中生成,示例如下(均作了适当简化):
//用户表模型
public partial class User
{
public long Id { get; set; }
public bool Enabled { get; set; }
public string Login { get; set; } = null!;
public string Password { get; set; } = null!;
public string? Display { get; set; }
}
//角色表模型
public partial class Role
{
public long Id { get; set; }
public string Code { get; set; } = null!;
public string Display { get; set; } = null!;
public string? Description { get; set; }
}
//用户与角色的关联表模型
public partial class UserRole
{
public long Id { get; set; }
public long UserId { get; set; }
public long RoleId { get; set; }
}
在使用下面的代码进行Role表的所有数据的访问时,突然出现如图错误:
var roles = DbContext.Roles.ToList();

图1. 奇怪的错误出现:无效的列名'UserId'
然而,我们的Role模型里根本没有涉及任何字段名为UserId,查询SQL Server端,确实在执行这一句查询的时候,发送过去的查询是:
SELECT [r].[ID], [r].[Code], [r].[Description], [r].[Display], [r].[UserId]
FROM [Role] AS [r]
ORDER BY [r].[Code]
看起来是EF在屁股后面加了个[r].[UserId]。
研究了很久,终于找到扩展模型User的分部类里,添加了一个属性:
private ObservableCollection<Role> roles = [];
public ObservableCollection<Role> Roles
{
get
{
if (roles.Count == 0)
{
var db = DbContext ?? AppHelper.DbContext;
var rs = db.Roles.ToList();
foreach (var r in rs)
{
r.IsSelected = db.UserRoles.Any(p => p.UserId == Id && p.RoleId == r.Id);
}
var rls = rs.OrderBy(o => o.Code);
foreach (var role in rls)
{
roles.Add(role);
}
}
return roles;
}
}
要注意的是,在.Net Framework的EF中,并不需要明确声明属性与数据库字段没映射关系,而在.NET中,则需要用[NotMapped]明确声明,这里忘记打上这个标记,所以Scaffold-Context在生成模型的时候,认为User跟Role有一个一对多的映射关系,所以它自动帮我“运作”了一下,给Role模型加了个隐式属性(Shadow Property)UserId。解决办法就是在这个属性前面标上[NotMapped]就解决了。
如下所示:
private ObservableCollection<Role> roles = [];
[NotMapped]
public ObservableCollection<Role> Roles
{
get
{
if (roles.Count == 0)
{
var db = DbContext;
var rs = db.Roles.ToList();
foreach (var r in rs)
{
r.IsSelected = db.UserRoles.Any(p => p.UserId == Id && p.RoleId == r.Id);
}
var rls = rs.OrderBy(o => o.Code);
foreach (var role in rls)
{
roles.Add(role);
}
}
return roles;
}
}