entity framework 在模型 C# 中不映射多个列表
entity framework does not map more than one list in model C#
我有一个 class "Project" 有两个列表,一个用于 "Employee",一个用于 "tool",当我只使用其中一个列表构建我的项目时具有正确关系的数据库更新,如果将第二个列表添加到 class entity framework 无法弄清楚如何创建关系。
public class Project
{
public Guid Id { get; set; }
public List<Employee> RequiredEmployees {get;set;}
public List<Tools> RequiredTools {get;set;}
}
Public class Tool
{
public Guid Id { get; set; }
public string Name { get; set; }
public Project Project { get; set; }
public Guid ProjectId { get; set; }
}
我收到错误 "There are no primary or candidate keys in the referenced table"
我已经尝试在 ModelBuilder 中映射但没有成功
modelBuilder.Entity<Employee>()
.HasRequired<Project>(s => s.Project)
.WithMany(g => g.Employee)
.HasForeignKey<Guid>(s => s.ProjectId);
有什么想法吗?
为了确保我们需要查看您的 Tools
class,但我认为问题是 Entity Framework 找不到要使用的 属性作为其中的主键。
Entity Framework 约定确定 class 的主键被命名为 Id
或 <ClassName>Id
(在本例中为 ToolsId
)。如果你想让一个名字不同于这个的 属性 作为 PK,你必须明确指定它,用 [Key]
attribute or with the Fluent API.
您已经解决了几个问题。报告的错误是关于主键的。此外,您在指定一对多关系时遇到问题。
一对多关系
Entity framework 如果您遵循 code first conventions 就很容易了 对于每个偏差,您需要告诉 entity framework 您的偏差。
您计划在 Project
和 Tool
之间设计一对多:每个 Project
有零个或多个 Tools
,每个 Tool
正好属于一个Project
如果您遵循 one-to-many 的惯例,则不必将此关系告知 entity framework; entity framework 将按照约定检测关系:
- 使用 ICollection 而不是 List,毕竟,
RequiredTools[4]
是什么意思?
- 使 ICollection 成为虚拟的。只要你的Project是查询,就不是真正的ICollection,它只包含一个class可以产生一个ICollection
考虑使用正确的命名约定。:
class Project
{
public Guid Id { get; set; }
// a project has zero or more Employees:
public virtual ICollection<Employee> Employees {get;set;}
// a project has zero or more Tools
public virtual ICollectioin<Tool> Tools {get;set;}
}
每个工具只属于一个项目,使用外键:
class Tool
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid ProjectId {get; set;}
public virtual Project Project { get; set; }
}
同样:每个员工都属于一个项目
class Employee
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid ProjectId {get; set;}
public virtual Project Project { get; set; }
}
如果一名员工可以参与多个项目,则使用 many-to-many:
class Employee
{
public Guid Id { get; set; }
public string Name { get; set; }
// an Employee works in zero or more Projects
public virtual ICollection<Project> Projects { get; set; }
}
以及您的 Dbcontext:
class MyDbContext : DbContext
{
public DbSet<Employee> Employees {get; set;}
public DbSet<Project> Projects {get; set;}
public DbSet<Tool> Tools {get; set;}
}
这足以让 entity framework 检测您的关系和 table 姓名。我建议您重新考虑偏离代码优先约定的决定。
但是,如果您真的需要改变您的名字,您必须告诉 entity framework 您的关系和 table 名字:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// a Project has zero or more Tools via property RequiredTools and table TableName:
var entityProject = modelBuilder.Entity<Project>();
entityProject.ToTable(... /* table name */);
// a project has zero or more tools via property RequiredTools
// every tool belongs to one required Project
// using foreign key ProjectId
entityproject.HasMany(project => project.RequiredTools)
.WithRequired(tool => tool.Project)
.HasFreignKey(tool => tool.ProjectId);
}
主键
您选择不使用常规类型作为主键。问题是entity framework不知道怎么填你的Id。您必须告诉 entity framework 您将填写主键的值。
这是通过重写 DbContext.SaveChanges
完成的
还有两个SaveChangesAsync;我们将覆盖它们。这两个过程将调用将生成您的 ID 的相同函数:
public override int SaveChanges()
{
GenerateIds();
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync()
{
GenerateIds();
return base.SaveChangesAsync();
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
this.GenerateIds();
return base.SaveChangesAsync(cancellationToken);
}
函数GenerateIds
将获取所有需要 Id 的项目并为其提供一个 Id。需要Id的对象都是状态为Added的对象。
但是,DbContext中记住了所有需要保存的变化的对象,只知道这些对象是DbEntityEntries。它不知道您的 DbEntityEntry 中的哪个 属性 代表主键。
您可以使用 KeyAttribute 解决这个问题。获取添加的条目,询问其类型,询问具有 KeyAttribute 的 属性 的类型并设置此 属性.
的值
这种方法有几个缺点。反射是一个相当缓慢的过程,它取决于你给所有需要 GUID 添加 KeyAttribute 的 classes,它不是类型安全的,你只会检测到你在 运行-时间。
最好定义所有实体 classes 应该实现定义主键的接口:
public interface IPrimaryKey
{
GUID Id {get; set;}
}
class Tool : IPrimaryKey {...}
class Project : IPrimaryKey {...}
如果您忘记实现您的 Id,您的编译器会警告您。 GenerateIds 现在相当简单:
private void GenerateIds()
{
foreach (DbEntityEntry addedEntry in this.ChangeTracker.Entries()
.Where(entry => entry.State == EntityState.Added))
{
// every entry implements IPrimaryKey. Fill the Id
(IPrimaryKey)entry.Id = this.CreateId
}
}
private GUID CreateId()
{
return Guid.NewGuid(); // or use your own Guid creator
}
我有一个 class "Project" 有两个列表,一个用于 "Employee",一个用于 "tool",当我只使用其中一个列表构建我的项目时具有正确关系的数据库更新,如果将第二个列表添加到 class entity framework 无法弄清楚如何创建关系。
public class Project
{
public Guid Id { get; set; }
public List<Employee> RequiredEmployees {get;set;}
public List<Tools> RequiredTools {get;set;}
}
Public class Tool
{
public Guid Id { get; set; }
public string Name { get; set; }
public Project Project { get; set; }
public Guid ProjectId { get; set; }
}
我收到错误 "There are no primary or candidate keys in the referenced table" 我已经尝试在 ModelBuilder 中映射但没有成功
modelBuilder.Entity<Employee>()
.HasRequired<Project>(s => s.Project)
.WithMany(g => g.Employee)
.HasForeignKey<Guid>(s => s.ProjectId);
有什么想法吗?
为了确保我们需要查看您的 Tools
class,但我认为问题是 Entity Framework 找不到要使用的 属性作为其中的主键。
Entity Framework 约定确定 class 的主键被命名为 Id
或 <ClassName>Id
(在本例中为 ToolsId
)。如果你想让一个名字不同于这个的 属性 作为 PK,你必须明确指定它,用 [Key]
attribute or with the Fluent API.
您已经解决了几个问题。报告的错误是关于主键的。此外,您在指定一对多关系时遇到问题。
一对多关系
Entity framework 如果您遵循 code first conventions 就很容易了 对于每个偏差,您需要告诉 entity framework 您的偏差。
您计划在 Project
和 Tool
之间设计一对多:每个 Project
有零个或多个 Tools
,每个 Tool
正好属于一个Project
如果您遵循 one-to-many 的惯例,则不必将此关系告知 entity framework; entity framework 将按照约定检测关系:
- 使用 ICollection 而不是 List,毕竟,
RequiredTools[4]
是什么意思? - 使 ICollection 成为虚拟的。只要你的Project是查询,就不是真正的ICollection,它只包含一个class可以产生一个ICollection
考虑使用正确的命名约定。:
class Project
{
public Guid Id { get; set; }
// a project has zero or more Employees:
public virtual ICollection<Employee> Employees {get;set;}
// a project has zero or more Tools
public virtual ICollectioin<Tool> Tools {get;set;}
}
每个工具只属于一个项目,使用外键:
class Tool
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid ProjectId {get; set;}
public virtual Project Project { get; set; }
}
同样:每个员工都属于一个项目
class Employee
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid ProjectId {get; set;}
public virtual Project Project { get; set; }
}
如果一名员工可以参与多个项目,则使用 many-to-many:
class Employee
{
public Guid Id { get; set; }
public string Name { get; set; }
// an Employee works in zero or more Projects
public virtual ICollection<Project> Projects { get; set; }
}
以及您的 Dbcontext:
class MyDbContext : DbContext
{
public DbSet<Employee> Employees {get; set;}
public DbSet<Project> Projects {get; set;}
public DbSet<Tool> Tools {get; set;}
}
这足以让 entity framework 检测您的关系和 table 姓名。我建议您重新考虑偏离代码优先约定的决定。
但是,如果您真的需要改变您的名字,您必须告诉 entity framework 您的关系和 table 名字:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// a Project has zero or more Tools via property RequiredTools and table TableName:
var entityProject = modelBuilder.Entity<Project>();
entityProject.ToTable(... /* table name */);
// a project has zero or more tools via property RequiredTools
// every tool belongs to one required Project
// using foreign key ProjectId
entityproject.HasMany(project => project.RequiredTools)
.WithRequired(tool => tool.Project)
.HasFreignKey(tool => tool.ProjectId);
}
主键
您选择不使用常规类型作为主键。问题是entity framework不知道怎么填你的Id。您必须告诉 entity framework 您将填写主键的值。
这是通过重写 DbContext.SaveChanges
完成的还有两个SaveChangesAsync;我们将覆盖它们。这两个过程将调用将生成您的 ID 的相同函数:
public override int SaveChanges()
{
GenerateIds();
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync()
{
GenerateIds();
return base.SaveChangesAsync();
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
this.GenerateIds();
return base.SaveChangesAsync(cancellationToken);
}
函数GenerateIds
将获取所有需要 Id 的项目并为其提供一个 Id。需要Id的对象都是状态为Added的对象。
但是,DbContext中记住了所有需要保存的变化的对象,只知道这些对象是DbEntityEntries。它不知道您的 DbEntityEntry 中的哪个 属性 代表主键。
您可以使用 KeyAttribute 解决这个问题。获取添加的条目,询问其类型,询问具有 KeyAttribute 的 属性 的类型并设置此 属性.
的值这种方法有几个缺点。反射是一个相当缓慢的过程,它取决于你给所有需要 GUID 添加 KeyAttribute 的 classes,它不是类型安全的,你只会检测到你在 运行-时间。
最好定义所有实体 classes 应该实现定义主键的接口:
public interface IPrimaryKey
{
GUID Id {get; set;}
}
class Tool : IPrimaryKey {...}
class Project : IPrimaryKey {...}
如果您忘记实现您的 Id,您的编译器会警告您。 GenerateIds 现在相当简单:
private void GenerateIds()
{
foreach (DbEntityEntry addedEntry in this.ChangeTracker.Entries()
.Where(entry => entry.State == EntityState.Added))
{
// every entry implements IPrimaryKey. Fill the Id
(IPrimaryKey)entry.Id = this.CreateId
}
}
private GUID CreateId()
{
return Guid.NewGuid(); // or use your own Guid creator
}