当社は、データベース内のデータを操作するさまざまなアプリケーションのスイートを出荷しています。各アプリケーションには固有のビジネス ロジックがありますが、すべてのアプリケーションはビジネス ルールの共通のサブセットを共有しています。一般的なものは、C++ で記述された一連のレガシー COM DLL にカプセル化されており、「クラシック ADO」を使用します (通常はストアド プロシージャを呼び出し、動的 SQL を使用することもあります)。これらの DLL のほとんどには、オブジェクトを作成、編集、削除、および取得するための XML ベースのメソッド (独自のフォーマット ベースのメソッドは言うまでもありません!) と、多くのエンティティをすばやくコピーおよび変換するメソッドなどの追加のアクションがあります。
ミドルウェア DLL は現在非常に古いため、アプリケーション開発者は、C# アプリケーションで簡単に使用できる新しいオブジェクト指向 (xml 指向ではない) ミドルウェアを必要としています。社内の多くの人は、古いパラダイムを忘れて、Entity Framework などの新しいクールなものに移行する必要があると言っています。彼らは POCO の単純さに興味を持ち、LINQ を使用してデータを取得したいと考えています (DLL の Xml ベースのクエリ メソッドは使いやすくなく、LINQ ほど柔軟ではありません)。
そこで、単純化されたシナリオのモックアップを作成しようとしています (実際のシナリオはもっと複雑です。ここでは、単純化されたシナリオの単純化されたサブセットのみを投稿します!)。Visual Studio 2010、Entity Framework 5 Code First、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 メソッドは、次のビジネス ロジックを実装しました。数量が変更された場合、同じラベルを持つすべての注文に「自動スケーリング」を適用する必要があります。たとえば、次の 3 つの注文があります。OrderA の数量 = 3、ラベル = X。OrderB の数量 = 4、ラベル = X。OrderC の数量 = 5、ラベル = Y。OrderA に新しい数量 = 6 を指定して Edit メソッドを呼び出します。つまり、OrderA の数量を 2 倍にしています。次に、ビジネス ロジックに従って、OrderB と OrderA のラベルが同じであるため、OrderB の数量は自動的に 2 倍になり、8 になる必要があります。OrderC はラベルが異なるため、変更しないでください。
これを POCO クラスと Entity Framework で複製するにはどうすればよいですか? 古い Edit メソッドは一度に 1 つの注文しか変更できないのに対し、Entity Framework は SaveChanges が呼び出されたときに多数の注文を変更できるため、これは問題です。さらに、SaveChanges を 1 回呼び出すだけで、新しい注文を作成することもできます。このテストのためだけの一時的な仮定: 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");
}
}
}
Dbコンテキスト:
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);
}
}
}
動作しているように見えますが (もちろんバグがなければ)、これは 1 つのクラスだけの例でした: 実際の製品アプリケーションには何百ものクラスがあるかもしれません! 残念ながら、実際のシナリオでは、多くの制約とビジネス ロジックを使用すると、SaveChanges のオーバーライドがすぐに長くなり、雑然とし、エラーが発生しやすくなる可能性があります。一部の同僚は、パフォーマンスについても懸念しています。従来の DLL では、多くのビジネス ロジック (「自動」アクションなど) がストアド プロシージャに含まれており、一部の同僚は、SaveChanges ベースのアプローチではラウンドトリップが多すぎてパフォーマンスが低下するのではないかと心配しています。SaveChanges のオーバーライドでは、ストアド プロシージャを呼び出すこともできますが、トランザクションの整合性はどうでしょうか。「base.SaveChanges()」を呼び出す前にデータベースに変更を加え、「base.SaveChanges()」が失敗した場合はどうなりますか?
別のアプローチはありますか?何か不足していますか?
どうもありがとうございました!
デメトリオ
ps ところで、SaveChanges をオーバーライドすることと、「SavingChanges」イベントに登録することの違いはありますか? このドキュメントを読みましたが、違いがあるかどうかは説明されていません: http://msdn.microsoft.com/en-us/library/cc716714(v=vs.100).aspx
この投稿: Entity Framework SaveChanges - 動作をカスタマイズしますか?
「SaveChanges をオーバーライドするときは、カスタム ロジックを base.SaveChanges の呼び出しの前後に配置できます」と述べています。しかし、他の警告/利点/欠点はありますか?