Arj*_*non 2 c# entity-framework transactions distributed-transactions
我对实体框架还很陌生,对EF的db.SaveChange也有疑问。从一些帖子和MSDN中,我了解到db.SaveChange默认情况下会进行事务中的所有更改。还有一种方法,我们可以使用db.Database.BeginTransaction()“ db”作为上下文类对象来创建自己的事务。所以我有两个问题:
db.SaveChange()获取@@ identity的其他方法(db.SaveChanges()在用户定义的事务范围内),并且会将db.SaveChanges()我的更改提交到D B是的,如果您将上下文显式包装在诸如.Net的TransactionScope之类的事务中,则可以在调用.SaveChanges()之后从实体中检索自动生成的ID,而无需提交范围限定的事务。
using (var tx = new TransactionScope())
{
using (var context = new MyDbContext())
{
var newEntity = populateNewEntity();
context.MyEntities.Add(newEntity);
context.SaveChanges();
int entityId = newEntity.EntityId; // Fetches the identity value.
}
} // Rolls back the transaction. Entity not committed.
Run Code Online (Sandbox Code Playgroud)
但是,除非绝对必要,否则应谨慎避免此类操作。首先,以上示例是TransactionScope的常用用法,TransactionScope的默认隔离级别为“可序列化”,这在锁定方面是最悲观的。在具有多个并发操作/用户的系统上,即使适度使用此模式,也会由于锁定等待而导致死锁和性能下降。因此,如果使用TransactionScope,请确保指定隔离级别。
DTC在需要协调数据库或其他受Tx绑定的操作之间的提交的方案中很有用。例如,系统A正在保存更改,并且需要通过API与系统B协调更新/插入。A和B需要配置为使用DTC,但是一旦完成,A就可以开始交易,向DTC注册,将DTC令牌附加到B的API的标头中,B可以找到该令牌,并创建一个与之链接的ScopedTransaction令牌和基于A信号的提交/回滚。这产生了间接费用,这意味着两个系统上的事务打开时间都比平时长。如果有必要,那是业务成本。如果没有必要,那就是浪费和潜在的头痛根源。
有人可能会考虑使用显式Tx的另一个原因是,当他们想要更新相关实体中的FK时。创建订单可以选择创建新客户,订单具有客户ID,因此我们需要创建客户,获取要在Order上设置的ID,然后保存订单。如果保存订单失败,则应回滚创建客户。
using (var tx = new TransactionScope())
{
using (var context = new MyDbContext())
{
var newCustomer = createNewCustomer(); // dummy method to indicate creating a customer entity.
context.Customers.Add(newCustomer);
context.SaveChanges();
var newOrder = createNewOrder();
newOrder.CustomerId = newCustomer.CustomerId;
context.Orders.Add(newOrder);
context.SaveChanges();
}
tx.Commit();
}
Run Code Online (Sandbox Code Playgroud)
对于EF,应该通过使用导航属性来缓解这种情况,该属性具有订单和客户之间的关系。这样,您可以创建客户,创建订单,将订单的Customer引用设置为新客户,将订单添加到DbContext以及.SaveChanges()。这使EF能够处理订单,查看被引用的客户,插入订单,将FK与订单相关联,并在一个隐含的Tx中提交更改。
using (var context = new MyDbContext())
{
var newCustomer = createNewCustomer();
var newOrder = createNewOrder();
newOrder.Customer = newCustomer;
context.Orders.Add(newOrder);
context.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)
更新:概述在您的实体中避免FK引用...(多对一)
实体中带有FK的订单的EntityTypeConfiguration:
HasRequired(x => x.Customer)
.WithMany(x => x.Orders) // Links to an element in the Orders collection of the Customer. If Customer does not have/need an Orders collection then .WithMany()
.HasForeignKey(x => x.CustomerId); // Maps Order.Customer to use CustomerId property on Order entity.
Run Code Online (Sandbox Code Playgroud)
实体中没有FK的订单的EntityTypeConfiguration:
HasRequired(x => x.Customer)
.WithMany(x => x.Orders)
.Map(x => x.MapKey("CustomerId")); // Maps Order.Customer to use CustomerId column on underlying Order table. Order entity does not expose a CustomerId.
Run Code Online (Sandbox Code Playgroud)
使用EF Core-从内存中可能需要更新。
HasRequired(x => x.Customer)
.WithMany(x => x.Orders) // Links to an element in the Orders collection of the Customer. If Customer does not have/need an Orders collection then .WithMany()
.HasForeignKey("CustomerId"); // Creates a shadow property where Entity does not have a CustomerId property.
Run Code Online (Sandbox Code Playgroud)
两种方法(具有或不具有映射的FK)的工作原理相同。第二种方法的好处是,代码中没有混淆如何更新或评估订单的客户参考。例如,如果您在订单上既有客户又有客户ID,则更改客户ID并调用SaveChanges不会将订单移动到新客户,仅设置客户参考即可。设置客户引用不会自动更新CustomerId,因此,在刷新实体之前,通过订单上的CustomerId属性“获取” customerId的任何代码仍将检索旧的客户引用。
使用导航属性的重要之处在于延迟执行或有效地加载它们。例如,如果要加载订单列表并包括其客户名称:
using (var myContext = new MyDbContext())
{
var orders = myContext.Orders.Where(x => x.OrderDate >= startDate && x.OrderDate < endDate).ToList();
return orders;
}
Run Code Online (Sandbox Code Playgroud)
**不好:如果这是MVC / Web API,则序列化程序将接管订单集合,并尝试序列化订单,请访问每个导航属性并尝试加载它。这将触发延迟加载调用。因此,如果Order有一个Customer,那将是对数据库的命中“ w.SELECT * FROM Customers WHERE CustomerId = 42”如果Order有Order行,则“ SELECT * FROM OrderLines WHERE OrderLineId = 121”,“ SELECT * FROM OrderLines WHERE OrderLineId = 122英寸...(您可能会想知道要通过OrderId来获取订单行,但是不行!对返回的实体产生巨大的性能影响,只是不要这样做。
using (var myContext = new MyDbContext())
{
var orders = myContext.Orders
.Include(x => x.Customer)
.Include(x => x.OrderLines)
.Where(x => x.OrderDate >= startDate && x.OrderDate < endDate).ToList();
return orders;
}
Run Code Online (Sandbox Code Playgroud)
**更好,但仍然很差。您可能只包括您认为需要的物品,但是序列化程序仍将获取订单上的所有物品。当实体被修改为包括新的数据链接时,这会再次咬住您。即使您包括所有内容,如果您只想要客户名称,这也是浪费的。
using (var myContext = new MyDbContext())
{
var orders = myContext.Orders
.Where(x => x.OrderDate >= startDate && x.OrderDate < endDate)
.Select(x => new OrderLineViewModel
{
OrderId = x.OrderId,
OrderNumber = x.OrderNumber,
OrderAmount = x.OrderAmount,
CustomerName = x.Customer.Name
}).ToList();
return orders;
}
Run Code Online (Sandbox Code Playgroud)
**这是具有导航属性和延迟执行的最佳选择。在数据库上运行的SQL仅从相关数据中返回这4列。没有延迟负载,您只需通过网络发送所需的数据量即可。
有人可能会争辩说,如果您通常需要订单中的CustomerId引用,例如在Order实体上具有CustomerId可以节省对Customer的引用。但是如上所述,该ID可能并不可靠,通过使用延迟执行让EF使用实体来填充您想要的数据,获取订单的客户ID只是包含/选择x.Customer.CustomerId仅包含所需列的问题,没有加载整个实体来获取它。
| 归档时间: |
|
| 查看次数: |
3624 次 |
| 最近记录: |