[英]Entity Framework Code First SaveChanges creates a new object
[英]Overriding SaveChanges in Entity Framework 5 Code First to replicate the behavior of an old legacy library
我们公司提供一套操作数据库中数据的各种应用程序。 每个应用程序都有其特定的业务逻辑,但所有应用程序共享业务规则的公共子集。 常见的东西包含在一堆用C ++编写的遗留COM DLL中,它们使用“经典ADO”(它们通常调用存储过程,有时它们使用动态SQL)。 这些DLL中的大多数都具有基于XML的方法(更不用说基于专有格式的方法!)来创建,编辑,删除和检索对象,还有额外的操作,例如快速复制和转换许多实体的方法。
中间件DLL现在已经很老了,我们的应用程序开发人员需要一个新的面向对象(不是面向xml)的中间件,可以很容易地被C#应用程序使用。 该公司的许多人都说我们应该忘记旧的范例,转而采用新的酷炫的东西,比如Entity Framework。 他们对POCO的简单性很感兴趣,他们希望使用LINQ来检索数据(基于Xml的DLL查询方法不是那么容易使用,并且永远不会像LINQ那样灵活)。
所以我正在尝试为简化场景创建一个模型(真实场景要复杂得多,在这里我只发布一个简化场景的简化子集!)。 我正在使用Visual Studio 2010,实体框架5代码优先,SQL Server 2008 R2。 如果我犯了愚蠢的错误,请怜悯,我是Entity Framework的新手。 由于我有许多不同的疑虑,我会将它们分别发布。 这是第一个。 传统的XML方法有这样的签名:
bool Edit(string xmlstring, out string errorMessage)
使用这样的格式:
<ORDER>
<ID>234</ID>
<NAME>SuperFastCar</NAME>
<QUANTITY>3</QUANTITY>
<LABEL>abc</LABEL>
</ORDER>
Edit方法实现了以下业务逻辑:当Quantity更改时,必须对具有相同Label的所有Orders应用“自动缩放”。 例如,有三个订单:OrderA的数量= 3,标签= X.订单B的数量= 4,标签= X.订单C的数量= 5,标签= Y.我调用Edit方法为OrderA提供新的数量= 6,即我将OrderA的数量加倍。 然后,根据业务逻辑,OrderB的数量必须自动加倍,并且必须变为8,因为OrderB和OrderA具有相同的标签。 不得更改OrderC,因为它具有不同的标签。
我如何使用POCO类和实体框架复制它? 这是一个问题,因为旧的Edit方法一次只能更改一个订单,而实体框架可以在调用SaveChanges时更改大量订单。 此外,对SaveChanges的单次调用也可以创建新的订单。 临时假设,仅适用于此测试:1)如果同时更改了许多订单数量,并且所有这些订单数量的比例因子都不相同,则不进行缩放; 2)新添加的订单即使具有相同的缩放订单标签也不会自动缩放。
我试图通过重写SaveChanges来实现它。
POCO课程:
using System;
namespace MockOrders
{
public class Order
{
public Int64 Id { get; set; }
public string Name { get; set; }
public string Label { get; set; }
public decimal Quantity { get; set; }
}
}
迁移文件(创建索引):
namespace MockOrders.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class UniqueIndexes : DbMigration
{
public override void Up()
{
CreateIndex("dbo.Orders", "Name", true /* unique */, "myIndex1_Order_Name_Unique");
CreateIndex("dbo.Orders", "Label", false /* NOT unique */, "myIndex2_Order_Label");
}
public override void Down()
{
DropIndex("dbo.Orders", "myIndex2_Order_Label");
DropIndex("dbo.Orders", "myIndex1_Order_Name_Unique");
}
}
}
的DbContext:
using System;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
namespace MockOrders
{
public class MyContext : DbContext
{
public MyContext() : base(GenerateConnection())
{
}
private static string GenerateConnection()
{
var sqlBuilder = new System.Data.SqlClient.SqlConnectionStringBuilder();
sqlBuilder.DataSource = @"localhost\aaaaaa";
sqlBuilder.InitialCatalog = "aaaaaa";
sqlBuilder.UserID = "aaaaa";
sqlBuilder.Password = "aaaaaaaaa!";
return sqlBuilder.ToString();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new OrderConfig());
}
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
var groupByLabel = from changedEntity in ChangeTracker.Entries<Order>()
where changedEntity.State == System.Data.EntityState.Modified
&& changedEntity.Property(o => o.Quantity).IsModified
&& changedEntity.Property(o => o.Quantity).OriginalValue != 0
&& !String.IsNullOrEmpty(changedEntity.Property(o => o.Label).CurrentValue)
group changedEntity by changedEntity.Property(o => o.Label).CurrentValue into x
select new { Label = x.Key, List = x};
foreach (var labeledGroup in groupByLabel)
{
var withScalingFactor = from changedEntity in labeledGroup.List
select new
{
ChangedEntity = changedEntity,
ScalingFactor = changedEntity.Property(o => o.Quantity).CurrentValue / changedEntity.Property(o => o.Quantity).OriginalValue
};
var groupByScalingFactor = from t in withScalingFactor
group t by t.ScalingFactor into g select g;
// if there are too many scaling factors for this label, skip automatic scaling
if (groupByScalingFactor.Count() == 1)
{
decimal scalingFactor = groupByScalingFactor.First().Key;
if (scalingFactor != 1)
{
var query = from oo in this.AllTheOrders where oo.Label == labeledGroup.Label select oo;
foreach (Order ord in query)
{
if (this.Entry(ord).State != System.Data.EntityState.Modified
&& this.Entry(ord).State != System.Data.EntityState.Added)
{
ord.Quantity = ord.Quantity * scalingFactor;
}
}
}
}
}
return base.SaveChanges();
}
public DbSet<Order> AllTheOrders { get; set; }
}
class OrderConfig : EntityTypeConfiguration<Order>
{
public OrderConfig()
{
Property(o => o.Name).HasMaxLength(200).IsRequired();
Property(o => o.Label).HasMaxLength(400);
}
}
}
它似乎工作(当然除了错误),但这只是一个类的例子:一个真正的生产应用程序可能有数百个类! 我担心在真实场景中,有很多约束和业务逻辑,SaveChanges的覆盖很快就会变得冗长,混乱且容易出错。 有些同事也关注表现。 在我们的遗留DLL中,许多业务逻辑(例如“自动”操作)存在于存储过程中,一些同事担心基于SaveChanges的方法可能会引入太多往返并阻碍性能。 在SaveChanges的覆盖中,我们也可以调用存储过程,但是事务完整性呢? 如果我在调用“base.SaveChanges()”之前对数据库进行了更改,并且“base.SaveChanges()”失败了怎么办?
有不同的方法吗? 我错过了什么吗?
非常感谢你!
德梅特里奥
ps顺便说一下,覆盖SaveChanges和注册到“SavingChanges”事件之间有区别吗? 我读了这篇文章,但它没有解释是否存在差异: http : //msdn.microsoft.com/en-us/library/cc716714(v = vs.100).aspx
这篇文章: 实体框架SaveChanges - 自定义行为?
说“当重写SaveChanges时,你可以在调用base.SaveChanges之前和之后放置自定义逻辑”。 但是还有其他警告/优点/缺点吗?
我会说这个逻辑属于你的MockOrders.Order类,来自更高层的类,它使用你的Order类(例如BusinessLogic.Order)或者属于Label类。 听起来像你的标签作为一个加入属性,所以,在不知道细节的情况下,我会说把它拉出来并使它成为自己的实体,这将为你提供导航属性,这样你就可以更自然地访问所有具有相同标签的订单。
如果修改数据库以规范化标签不是一个观众,那么构建一个视图并将其带入实体模型中。
我必须做类似的事情,但我已经创建了一个IPrepForSave
接口,并为需要在保存之前做一些业务逻辑的任何实体实现了该接口。
界面(原谅VB.NET):
Public Interface IPrepForSave
Sub PrepForSave()
End Interface
dbContext.SaveChanges覆盖:
Public Overloads Overrides Function SaveChanges() As Integer
ChangeTracker.DetectChanges()
'** Any entities that implement IPrepForSave should have their PrepForSave method called before saving.
Dim changedEntitiesToPrep = From br In ChangeTracker.Entries(Of IPrepForSave)()
Where br.State = EntityState.Added OrElse br.State = EntityState.Modified
Select br.Entity
For Each br In changedEntitiesToPrep
br.PrepForSave()
Next
Return MyBase.SaveChanges()
End Function
然后我可以在实现的PrepForSave()
方法中将业务逻辑保留在Entity本身中:
Partial Public Class MyEntity
Implements IPrepForSave
Public Sub PrepForSave() Implements IPrepForSave.PrepForSave
'Do Stuff Here...
End Sub
End Class
请注意,我对PrepForSave()
方法中可以执行的操作设置了一些限制:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.