10

我們公司提供了一套用於操縱數據庫中數據的各種應用程序。每個應用程序都有其特定的業務邏輯,但所有應用程序共享業務規則的一個共同子集。常見的東西被封裝在一堆使用「經典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」。但是還有其他的警告/優點/缺點嗎?

+0

還是這個嗎? – NSGaga 2013-04-07 12:40:41

+0

目前原型項目被困住了,我的老闆讓我回到了「遺留」的東西上:T-SQL存儲過程,原生C++ DLL,基於RCW的極薄C#圖層等等(我稱之爲「傳統」庫,但他們一直需要新功能和擴展,所以對於管理他們還不是「傳統」)。但遲早我將不得不再次面對實體框架。所以是的,這個問題仍然存在...... – Demetrio78 2013-04-08 06:44:40

+0

您認爲代碼優先是這種情況下最好的方法嗎?首先,模型不適合你的案例嗎?您可以直接調用存儲過程而不是使用保存更改。爲每個實體分配一個可以使用調用相應商店的Save()方法保存的部分類。 – KinSlayerUY 2013-04-08 14:04:44

回答

1

我想說這個邏輯屬於你的MockOrders.Order類,或者來自使用Order類(例如BusinessLogic.Order)或Label類的更高層的類。聽起來像你的標籤充當加入屬性,因此,不知道具體情況,我會說把它拉出來並使它成爲它自己的一個實體,這會給你導航屬性,這樣你就可以更自然地訪問所有具有相同標籤的訂單。

如果修改數據庫以標準化出標籤不是一個流行者,請爲此建立一個視圖並將其引入您的實體模型。

+0

_「聽起來你的標籤聽起來像一個加入屬性」,當然它的確如此,但我發佈了一個過於簡化的示例,試圖將內容集中在一個類中。問題哲學與例如OrderGroup類收集列表中的Order對象。 _「因此您可以更自然地訪問所有具有相同標籤的訂單」_問題不是使用相同標籤訪問所有訂單,問題是:訂單更改時,必須自動應用組中所有訂單的更改根據具體的商業邏輯。 – Demetrio78 2013-04-11 09:49:54

+0

_「這個邏輯屬於(...)來自使用你的Order類的更高層的類(egBusinessLogic.Order)」_我開始認爲我完全忽略了EF如何被假設的觀點也許我不應該直接嚮應用程序公開EF類(例如我的DbContext派生類),應該嗎?有關IQueryable的強烈討論,如(http://mikehadlow.blogspot.de/2009/01/should-my-repository-expose-iqueryable.html)或(http://www.weirdlover.com/2010/05/11/iqueryable-can-kill-your-dog-steal-your-wife-kill-your-will-live-etc /)但是DbContext對象呢? – Demetrio78 2013-04-11 10:01:04

+0

我發現這(http://stackoverflow.com/questions/11658060/should-dbcontext-and-dbset-be-exposed-for-a-pure-poco-application),我會閱讀相關鏈接... BTW , 非常感謝您的回覆。 – Demetrio78 2013-04-11 10:14:39

1

我不得不做類似的事情,但我創建了一個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()方法:

Partial Public Class MyEntity 
    Implements IPrepForSave 

    Public Sub PrepForSave() Implements IPrepForSave.PrepForSave 
     'Do Stuff Here... 
    End Sub 

End Class 

請注意,我把什麼可以在PrepForSave()做一些限制方法:

  1. 對實體的任何更改都不能使實體驗證邏輯失敗,因爲這將在調用驗證邏輯後調用。
  2. 數據庫訪問應該保持在最低限度,並且應該是隻讀的。
  3. 在保存之前不需要執行業務邏輯的任何實體都不應該實現此接口。