问题描述
我很困惑如何在实体框架中设置DbSet的值。
假设我在DataContext类(我们自己的类继承自DbContext类)中有以下代码片段?
public DbSet<Values> Values {get; set;}
然后我实际上可以通过具有依赖项注入的控制器通过表(值表)检索值,如下所示。现在我的问题是DbSet如何设置值?在从中获取值属性之前,我看不到如何设置其值。
this.context.Values.FirstOrDefaultAsync(list => list.id == id);
解决方法
谈论DbSet的工作方式时有两个主要问题:
- 查询数据:使用界面
IQueryable<...>
- 更改数据:添加/删除/更新
IQueryable
实现IQueryable<TSource>
的类的对象不代表相似项目的序列,而是代表获得可枚举相似项目序列的潜力。
为此,一个IQueryable具有两个属性:Expression
和Provider
。表达式以某种通用格式保存以下信息:必须获取哪些数据,提供者知道谁必须获取数据(通常是数据库管理系统DBMS)以及用于与DBMS通信的语言(通常是SQL)。
在最低级别,要获取可枚举的序列,您需要调用IQueryable.GetEnumerator()
。这会将表达式发送给提供程序,该提供程序会将表达式转换为DBMS可以理解的格式。提供程序将执行查询并以Enumerable<TResult>
的形式返回获取的数据。
要访问获取的项目,请重复调用MoveNext()
,只要它返回true,就可以使用属性Current
来获取获取的TResult。
要访问数据库,DbSet对象知道它属于哪个DbContext。 DbContext具有属性Database
,可以执行转换后的SQL语句。
大多数人很少使用GetEnumerator / MoveNext / Current,而使用foreach
,它在内部深处使用此方法枚举元素。
如果您仔细观察LINQ,您会发现有两组IQueryable方法。返回IQueryable<...>
的人和其他人。
返回IQueryable<...>
的方法将不执行查询。这些方法使用延迟执行或延迟执行:仅更改表达式。这些方法速度很快,几乎不会花费任何处理能力。
其他方法(例如ToList()/ ToDictionary()/ FirstOrDefault()/ Sum()/ Any())将实际执行查询:将Expression发送到数据库,并相应地返回获取的数据。
ToList 将如下所示:
List<TSource> ToList<TSource>(this IQueryable<TSource> source)
{
List<TSource> result = new List<TSource>();
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
while (enumerator.MoveNext())
{
// There is still an item; add it to the list
TSource item = enumerator.Current;
result.Add(item);
}
return result;
}
任何
bool Any<TSource>(this IQueryable<TSource> source)
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
return enumerator.MoveNext(); // I only need to know if I can get the first element
}
}
您现在应该已经可以理解,如果您仍然有IQueryable<...>
并处理上下文,为什么代码不起作用:表达式已经生成,但是尚未执行。处置dbContext之后,无法再次打开与数据库的连接:
IQueryable<Customer> newYorkCustomers;
using (var dbContext = new CustomerDbContext(...))
{
newYorkCustomers = dbContext.Customers.Where(customer => customer.City == "New York");
}
var result = newYorkCustomers.ToList();
// Expect exception: DbContext is disposed
您现在还应该了解,如果您有一个要多次枚举的查询,将它设为列表是明智的,否则该查询将被执行两次
更改数据库中的项目
每个DbSet都知道它在哪个DbContext中。每个DbContext都有一个ChangeTracker
,他会跟踪所有提取的项目以及对其所做的所有更改。
如果您使用Find
查找项目,或使用查询获取完整的项目,则这些项目的原始值存储在ChangeTracker中。
changeTracker包含所有三个客户和customer1。您可以使用以下代码访问它们:
using (var dbContext = new CustomerDbContext(...))
{
var customersWithoutOrders = dbContext.Customers
.Where(customer => !customer.Orders.Any())
.ToList();
Customer customer = dbContext.Customers.Find(1);
var changeTracker = dbContext.ChangeTracker;
var fetchedCustomers = changeTracker.Entries<Customer>();
fetchedCustomers将为每个没有订单的客户和该客户包含DbEntityEntry
。
每个DbEntityEntry
都为每个属性保留原始值和当前值。如果您询问DbEntityEntry的状态,它将检查原始值和当前值以确定其是否已更改。
从数据库中添加/删除项目
如果要从数据库中删除项目,则首先必须获取它。这样可以确保它也在DbChangeTracker中。
dbContext.Customers.Remove(fetchedCustomer);
这会将fetchedCustomer的DbEntityEntry的状态设置为Deleted
。
添加的项目也在DbChangeTracker中。它们的状态等于Added
DbContext.SaveChanges
SaveChanges将获取所有DbEntityEntries,以查看添加/删除/更改了哪些项目,并将执行所需的SQL语句。
由于DbEntityEntry知道每个属性的原始值以及当前值,因此它知道要更新的值。
,如果克隆Entity Framework Core源代码,您将看到该过程如何工作。之所以没有“看到”设置属性的位置,是因为该代码被隐藏在Microsoft.EntityFrameworkCore.dll
库中。