C# 重写EntityFramework5中的SaveChanges代码首先复制旧遗留库的行为
我们公司提供一套处理数据库中数据的各种应用程序。每个应用程序都有其特定的业务逻辑,但所有应用程序都共享一个通用的业务规则子集。普通的东西被封装在一组遗留的COM DLL中,用C++编写,它们使用通常称为存储过程的经典ADO,有时使用动态SQL。大多数DLL都有基于XML的方法,更不用说基于专有格式的方法了!创建、编辑、删除和检索对象,以及其他操作,如快速复制和转换许多实体的方法 中间件DLL现在已经非常陈旧,我们的应用程序开发人员需要一种新的面向对象而不是面向xml的中间件,它可以方便地被C应用程序使用。 公司里的许多人说,我们应该忘记旧的范例,转而使用新的很酷的东西,比如实体框架。他们对POCOs的简单性很感兴趣,并且希望使用LINQ来检索数据。DLL的基于Xml的查询方法不那么容易使用,也永远不会像LINQ那样灵活 因此,我正在尝试为一个简化的场景创建一个模型,真实的场景要复杂得多,在这里,我将发布简化场景的一个简化子集!。我使用的是VisualStudio2010,实体框架5代码优先,SQLServer2008R2。 如果我犯了愚蠢的错误,请宽恕我,我是实体框架的新手。 由于我有许多不同的疑问,我将在不同的线程中发布它们。 这是第一个。遗留XML方法具有如下签名:C# 重写EntityFramework5中的SaveChanges代码首先复制旧遗留库的行为,c#,entity-framework,entity-framework-5,code-first,savechanges,C#,Entity Framework,Entity Framework 5,Code First,Savechanges,我们公司提供一套处理数据库中数据的各种应用程序。每个应用程序都有其特定的业务逻辑,但所有应用程序都共享一个通用的业务规则子集。普通的东西被封装在一组遗留的COM DLL中,用C++编写,它们使用通常称为存储过程的经典ADO,有时使用动态SQL。大多数DLL都有基于XML的方法,更不用说基于专有格式的方法了!创建、编辑、删除和检索对象,以及其他操作,如快速复制和转换许多实体的方法 中间件DLL现在已经非常陈旧,我们的应用程序开发人员需要一种新的面向对象而不是面向xml的中间件,它可以方便地被C应用
bool Edit(string xmlstring, out string errorMessage)
格式如下:
<ORDER>
<ID>234</ID>
<NAME>SuperFastCar</NAME>
<QUANTITY>3</QUANTITY>
<LABEL>abc</LABEL>
</ORDER>
要创建索引的迁移文件:
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);
}
}
}
当然,它似乎可以阻止bug,但这是一个只有一个类的示例:一个真正的生产应用程序可能有数百个类!
我担心在一个有很多约束和业务逻辑的真实场景中,SaveChanges的覆盖可能会很快变得冗长、混乱并且容易出错。
一些同事也关心绩效。在我们的旧式DLL中,许多业务逻辑(如自动操作)都存在于存储过程中,一些同事担心基于SaveChanges的方法可能会引入太多的往返,从而影响性能。
在SaveChanges的覆盖中,我们也可以调用存储过程,但是事务完整性呢?如果我对数据库做了更改怎么办
在调用base.SaveChanges和base.SaveChanges失败之前
有不同的方法吗?我错过什么了吗
多谢各位
德米特里奥
p、 顺便问一下,覆盖SaveChanges和注册到SaveChanges事件之间有区别吗?我阅读了本文件,但未解释是否存在差异:
本帖:
表示在重写SaveChanges时,可以在调用base.SaveChanges前后放置自定义逻辑。但是还有其他的注意事项/优点/缺点吗?我想说,这个逻辑属于MockOrders.Order类,或者属于使用您的Order类(例如BusinessLogic.Order)的更高层的类,或者属于Label类。听起来你的标签就像是一个连接属性,所以,在不知道细节的情况下,我会说把它拉出来,让它成为自己的一个实体,这将给你导航属性,这样你就可以更自然地访问具有相同标签的所有订单
如果修改DB以使标签正常化不是一件容易的事,那么构建一个视图并将其引入实体模型以实现此目的。我不得不做类似的事情,但我已经创建了一个IPrepForSave接口,并为任何需要在保存前执行一些业务逻辑的实体实现了该接口 VB.NET的接口: 公共接口IPrepForSave 副预储蓄 端接口 dbContext.SaveChanges将覆盖: 公共重载将函数SaveChanges重写为整数 ChangeTracker.DetectChanges “**任何实现IPrepForSave的实体在保存之前都应该调用其PrepForSave方法。 Dim changedEntitiesToPrep=来自IPrepForSave的ChangeTracker.entries中的br 其中br.State=EntityState.Added或lse br.State=EntityState.Modified 选择br.实体 对于changedEntitiesToPrep中的每个br 预先保存 下一个 返回MyBase.SaveChanges 端函数 然后我可以将业务逻辑保存在实体本身中,在实现的PrepForSave方法中: 部分公共类MyEntity 实现IPrepForSave 公共子PrepForSave实现IPrepForSave.PrepForSave “在这里做事。。。 端接头 末级 请注意,我对PrepForSave方法中可以执行的操作设置了一些限制: 对实体的任何更改都不能使实体验证逻辑失败,因为这将在验证逻辑完成后调用 打电话。 数据库访问应该保持在最低限度,并且应该是只读的。 保存前不需要执行业务逻辑的任何实体都不应实现此接口。
目前,原型项目被卡住了,我的老板把我送回了遗留的东西:T-SQL存储过程、本地C++ +DLL、基于RCW的非常薄的C层,等等,我称之为遗留库,但它们需要新的特性和扩展,所以对于管理来说,它们还不是遗留的。但迟早我将不得不再次面对实体框架。所以,是的,问题仍然悬而未决……您认为代码优先是这种情况下的最佳方法吗?模型优先法难道不更适合你的情况吗?您可以直接调用存储过程,而不用保存更改。每个实体都有一个分部类,可以用调用相应存储的Save方法保存。@KinSlayerUY,也许你的想法更好,但不幸的是,模型一开始不支持自动迁移,这是我们想要利用的一个非常有用的功能。听起来你的标签就像是一个连接属性,当然它确实支持,但我发布了一个过于简单的例子,试图将内容集中在一节课上。问题原理与例如在列表中收集订单对象的OrderGroup类没有太大不同。因此,您可以更自然地访问具有相同标签的所有订单问题不是访问具有相同标签的所有订单,问题是:当订单更改时,必须根据特定的业务逻辑自动应用对组中所有订单的更改。此逻辑属于。。。在使用Order类(例如BusinessLogic.Order)的更高层的类中,我开始认为我完全忽略了EF应该如何使用的要点。也许我不应该直接向应用程序公开EF类,比如DbContext派生类,是吗?关于IQueryable有很多争论,比如or,但是DbContext对象呢?我发现了这个,我将阅读相关链接。。。顺便说一句,非常感谢你的回复。因为没有其他评论或答案,我会接受这个。非常感谢。对不起,我从来没有回复过你。我将不再将更高级别的应用程序代码与数据持久性技术耦合。如果你这么做了,比如说,结果发现EF的性能没有达到标准,那么换一种持久性技术会让你头疼。域是这里的王者,如果您定义了数据持久化层需要实现的一些接口,则在域层中使用这些接口,并注入实现类,您将在很长一段时间内保持良好状态。
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);
}
}
}