所以你有一个Users 和一个Assignments 的表,并且用户和分配之间存在一对多的关系:每个用户都有零个或多个分配,每个分配只属于一个用户,即外键所指的User。
您还有一张Rules 的表格。 Assignments 和 Rules 之间存在关系。我不确定这种关系:每个作业都只有一个规则吗? (一对多:规则有零个或多个分配,分配有规则的外键)。
还是存在多对多关系:每个Assignment 都有0 个或多个Rules,每个Rule 都是0 个或多个Assignments 中的一个Rule。
因为这不是你问题的一部分,所以我不会深入探讨。
如果你关注了entity framework conventions,你会有这样的课程:
public class User
{
public int Id {get; set;}
public string Name {get; set;}
... // other properties
// every User has zero or more Assignments (one-to-many)
public virtual ICollection<Assignment> Assignments {get; set;}
}
public class Assignment
{
public int Id {get; set;}
public string Name {get; set;}
public DateTime Date {get; set;}
... // other properties
// every Assignment is the assignment of exactly one user, using foreign key:
public int UserId {get; set;}
public virtual User User {get; set;}
// One-To-Many: Every assignment has one Rule using foreign key
public int RuleId {get; set;}
public virtual Rule Rule {get; set;}
// alternative: many-to-many: every assignment has zero or more Rules
public virtual ICollection<Rule> Rules {get; set;}
}
在实体框架中,非虚拟属性代表表的列;虚拟属性表示表之间的关系(一对多、多对多)。
外键UserId 是表Assignments 中的真实列,因此它是非虚拟的。 User 不是Assignment 中的列,而是与Assignment 的关系,因此它被声明为virtual。
为了完整性,DbContext:
public class WorkDbContext : DbContext
{
public DbSet<User> Users {get; set;}
public DbSet<AssignMent> Assignments {get; set;}
public DbSet<Rule> Rules {get; set;}
...
}
因为我遵循实体框架的约定,这就是实体框架需要检测表、表的列以及表之间的关系(一对多、多对多),包括外键。只有当你想偏离约定,特别是不同的标识符时,你才需要使用 Attributes 或 fluent API。
我正在尝试为每个用户获得 first 分配
问题:first 作业是什么?
- 这是日期最早的作业吗?
- 是 ID 最低的分配吗?
- 是数据库中最先出现的分配吗? (相当不可预测)
使用虚拟 ICollections
当使用实体框架以一对多的关系获取数据时,通常使用虚拟属性更容易。实体框架了解您的关系,并为您创建适当的(Group-)Join。
using (var dbContext = new WorkDbContext())
{
var usersWithTheirFirstAssignment = dbContext.Users.Select(user => new
{
// Select only the User properties that you plan to use
Id = user.Id,
Name = user.Name,
...
// Get the "first" Assignment
FirstAssignment = ... // TODO: implement
})
.ToList();
}
在选择属性之前获取 FirstAssignment,如果它是最旧的日期,OrderBy Date:
FirstAssignment = user.Assignments.OrderBy(assignment => assignment.Date)
.Select(assignment => new {...})
.FirstOrDefault();
或者如果第一个分配是具有最低 ID 的分配:
FirstAssignment = user.Assignments.OrderBy(assignment => assignment.Id)
.Select(assignment => new {...})
.FirstOrDefault();
或者如果第一个分配只是数据库中的第一个分配:
FirstAssignment = user.Assignments.Select(assignment => new {...})
.FirstOrDefault();
如果您有一个一对多的关系,并且您希望每个“one”项都有其“many”子项,请从 Select 中的“one”开始并使用 virtual ICollection 获取“many”子项目。
另一方面,如果您想要项目,每个项目都有其唯一的外键引用的父项,从“多”端开始,并使用虚拟属性选择“单”端属性。
Intermezzo:为什么只选择您计划使用的属性?
数据库管理系统在选择数据方面得到了极大的优化。查询中较慢的部分之一是将所选数据从 DBMS 传输到本地进程。因此,明智的做法是尽可能限制所选数据的数量。
如果您使用Include 选择相关类,则选择完整的相关子类,包括外键。
因此,如果用户 [10] 有 1000 个分配,则每个分配都将有一个外键 UserId,值为 10。您将传输相同的数字 10 超过 1000 次。多么浪费处理能力。
如果您不打算更改获取的数据,使用 Select 的另一个原因是每个 DbContext 都有一个 ChangeTracker。如果您在没有 Select 的情况下获取表行,则获取的对象与副本一起存储在 ChangeTracker 中。您会得到对原始文件的引用。如果更改值,ChangeTracker 中的原始值也会更改。当您使用SaveChanges时,ChangeTracker中的所有Originals都会与副本按值进行比较,以查看需要更新的项目。
因此,如果您获取 1000 个用户,您的 ChangeTracker 将包含 1000 个原件和 1000 个副本。如果在调用 SaveChanges 之前只更改一个,则代码必须检查这 1000 个用户中每个用户的每个非虚拟属性,而只会更改一个用户。多么浪费处理能力!
查询数据时,始终使用 Select 并仅选择您实际计划使用的属性。如果您打算更改获取的数据,请仅获取完整的类或使用 Include。
所以在上面的选择中:
FirstAssignment = user.Assignments.OrderBy(...)
.Select(assignment => new
{
// Select only the Assignment properties that you plan to use
Id = assignment.Id,
Date = assignment.Date,
// don't Select the foreign key, you already know the value:
// UserId = assignment.UserId
Rule = new
{
// you know the drill by now: only the rule properties that you plan to use!
Id = assignment.Rule.Id,
Name = assignment.Rule.Name,
...
},
})
.FirstOrDefault(),
替代方案:在Select中使用外键
虽然在我看来使用虚拟 ICollection 是最直观的解决方案,但您也可以使用外键选择 FirstAssignment:
var usersWithTheirFirstAssignment = dbContext.Users.Select(user => new
{
Id = user.Id,
Name = user.Name,
...
FirstAssignment = dbContext.Assignments
.Where(assignment => assignment.UserId == user.Id)
.Orderby(...)
.Select(...)
.FirstOrDefault(),
}
OrderBy 和 Select 同上。 Entity Framework 将创建一个类似于使用虚拟 ICollection 时的 GroupJoin。
但我想做(组-)加入我自己!
有些人不信任实体框架。他们不使用虚拟 ICollection,但更喜欢自己进行 GroupJoin。如果您想这样做,您需要执行以下操作:
// GroupJoin Users and Assignments:
var usersWithTheirFirstAssignment = dbContext.Users.GroupJoin(dbContext.Assignments,
user => user.Id, // from every user take the primary key
assignment => assignment.UserId, // from every assignment take the foreign key to the user
// parameter resultSelector: from every User and its zero or more assignments, make one new
(user, assignmentsOfThisUser) => new
{
Id = user.Id,
Name = user.Name,
...
// the first assignment: order and select like described above
FirstAssignment = assignmentsOfThisUser.OrderBy(...)
.Select(...)
.FirstOrDefault(),
}