我們公司提供了一套用於操縱數據庫中數據的各種應用程序。每個應用程序都有其特定的業務邏輯,但所有應用程序共享業務規則的一個共同子集。常見的東西被封裝在一堆使用「經典ADO」(他們通常調用存儲過程,有時使用動態SQL)的C++中編寫的遺留COM DLL中。大多數這些DLL都有基於XML的方法(更不用說基於專有格式的方法了!)來創建,編輯,刪除和檢索對象,還有一些額外的操作,例如快速複製和轉換許多實體的方法。在實體框架中覆蓋SaveChanges 5代碼首先複製舊的舊式庫的行爲
中間件DLL現在已經很舊了,我們的應用程序開發人員需要一個可以被C#應用程序輕鬆使用的新的面向對象(不是面向xml的)中間件。 該公司的許多人都說我們應該忘記舊的範例,並轉向新酷酷的東西,例如Entity Framework。他們對POCO的簡單性很感興趣,他們希望使用LINQ來檢索數據(DLL的基於Xml的查詢方法不易使用,並且永遠不會像LINQ那樣靈活)。
所以我試圖爲一個簡化的場景創建一個模型(真實的場景要複雜得多,在這裏我只會發佈一個簡化的場景簡化子集!)。我使用的是Visual Studio 2010,實體框架5 Code First,SQL Server 2008 R2。 如果我犯了愚蠢的錯誤,請保持憐憫,我是實體框架的新手。 由於我有很多不同的疑問,我會在單獨的線程中發佈它們。 這是第一個。舊版XML的方法有這樣的簽名:
bool Edit(string xmlstring, out string errorMessage)
有了這樣的格式:
<ORDER>
<ID>234</ID>
<NAME>SuperFastCar</NAME>
<QUANTITY>3</QUANTITY>
<LABEL>abc</LABEL>
</ORDER>
編輯方法來實現以下業務邏輯:當數量改變,「自動縮放」絕應用於具有相同標籤的所有訂單。 例如有三個訂單:OrderA的數量= 3,標籤= X. OrderB的數量= 4,標籤= X. OrderC的數量= 5,標籤= Y. 我調用Edit方法爲OrderA提供一個新的數量= 6,即,我正在將訂單A的數量翻倍。然後,根據業務邏輯,OrderB的數量必須自動加倍,並且必須變爲8,因爲OrderB和OrderA具有相同的標籤。 OrderC不能更改,因爲它具有不同的標籤。
我該如何複製這與POCO類和實體框架?這是一個問題,因爲舊的Edit方法一次只能更改一個訂單,而當調用SaveChanges時,實體框架可以更改許多Orders。此外,一次調用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()」失敗之前更改數據庫 會怎麼樣?
有沒有不同的方法?我錯過了什麼嗎?
非常感謝!
Demetrio
p.s.順便說一下,覆蓋SaveChanges和註冊到「SavingChanges」事件有什麼區別?我讀這個文件,但它並不能說明是否有區別: http://msdn.microsoft.com/en-us/library/cc716714(v=vs.100).aspx
這篇文章: Entity Framework SaveChanges - Customize Behavior?
說:「首要的SaveChanges的時候,你可以把自定義邏輯之前和之後調用base.SaveChanges」。但是還有其他的警告/優點/缺點嗎?
還是這個嗎? – NSGaga 2013-04-07 12:40:41
目前原型項目被困住了,我的老闆讓我回到了「遺留」的東西上:T-SQL存儲過程,原生C++ DLL,基於RCW的極薄C#圖層等等(我稱之爲「傳統」庫,但他們一直需要新功能和擴展,所以對於管理他們還不是「傳統」)。但遲早我將不得不再次面對實體框架。所以是的,這個問題仍然存在...... – Demetrio78 2013-04-08 06:44:40
您認爲代碼優先是這種情況下最好的方法嗎?首先,模型不適合你的案例嗎?您可以直接調用存儲過程而不是使用保存更改。爲每個實體分配一個可以使用調用相應商店的Save()方法保存的部分類。 – KinSlayerUY 2013-04-08 14:04:44