UPSERT自動生成(MERGE DML)による繰り返しの回避
少なくとも何かをデータベースに書き込むサービスがある場合、繰り返しを避けることが重要です。 記録の複製。 解決策は、UPSERTプロシージャを作成することです。 アップサートは、すでにレコードがあるかどうかに応じて、更新または挿入です。 エントリがない場合は、追加できます。 既に存在する場合は、更新できます。
SQL Server 2008では、トリガーや他の倒錯の代わりに、UPSERT動作を実装するために特別に作成されたMERGEステートメントを使用できます。 1つの問題-この命令自体はひどいように見えるため、既存のエンティティから自動生成するのが最善です。
MERGE DMLを生成するための私のアプローチは次のようなものです:ORMはどの要素が一致するかに関する情報を保存しないため、本当に更新されて挿入されないため、このファイルを制御する最も簡単な方法は手動です。 一方、私は間違いなく1つまたは別のモデルを持っているので、それを使用して初期データを生成したいと思います。
EF4.0を使用してこれがどのように行われるかを見てみましょう。 EFにはEDMX拡張子のファイルがあり、それを
Edmx/Runtime/ConceptualModels/Schema
のXPathに深く掘り下げると、すべてのエンティティの説明を取得します。 それらを何かにマップするには、最初に
System.Data.Resources.CSDLSchema_2.xsd
スキームを見つける必要があります。これは、Studioがインストールされているのと同じ場所、
\xml\Schemas
。
エンティティの場合、いくつかの理由により、EDMXをすぐにSQLに変換することはできません。まず、EDMXスキームをマップできません。 コンポジットであり、解析されません。マッピングした場合でも、作成されたSQLを編集して、「ジェネレータ」である比較を削除する必要があります。 今、私は何が何であるかを説明します。
それでは、典型的なケースを取り上げましょう-エンティティ
Person { Name, Age }
を更新(年齢の変更)するか、新しい名前を追加する(名前が新しい場合)
最初に行うことは、概念図から
<Schmema>
セクションを
<Schmema>
です。 次のことを取得します。
<Schema><br/>
<EntityContainer Name= "ModelContainer" annotation:LazyLoadingEnabled= "true" ><br/>
<EntitySet Name= "People" EntityType= "Model.Person" /><br/>
</EntityContainer><br/>
<EntityType Name= "Person" ><br/>
<Key><br/>
<PropertyRef Name= "Id" /><br/>
</Key><br/>
<Property Type= "Int32" Name= "Id" Nullable= "false" annotation:StoreGeneratedPattern= "Identity" /><br/>
<Property Type= "String" Name= "Name" Nullable= "false" /><br/>
<Property Type= "Int32" Name= "Age" Nullable= "false" /><br/>
</EntityType><br/>
</Schema><br/>
次に、このXMLを(比較的)単純なXMLに変換するマッピングを作成します。このマッピングでは、変更できるフィールドと変更できないフィールドをマークできます。
変換の結果、次のようなドキュメントが得られます。
<tables><br/>
<table name= "Person" ><br/>
<field type= "String" name= "Name" /><br/>
<field type= "Int32" name= "Age" /><br/>
</table><br/>
</tables><br/>
フィールド
Id
はここに到達しませんでした、なぜなら Upsert操作では、IDで比較しません。 (一方で、生成されたプロシージャでは
SCOPE_IDENTITY()
を返すため、
uniqueidentifier
ような
Id
型は取得されないことに注意してください。)次に、このドキュメントは別のXSLT(既に何年も前のことです)で変換され、結果はまさに必要なものです。すなわち:
/* Check that the stored procedure does not exist, and erase if it does. */<br/>
if object_id ( 'dbo.PersonUpsert' , 'P' ) is not null <br/>
drop procedure [dbo].[PersonUpsert];<br/>
go <br/>
/* Upserts an entry into the 'Person' table . */<br/>
create procedure [dbo].[PersonUpsert](<br/>
@Id int output ,<br/>
@Name nvarchar( max ),<br/>
@Age int )<br/>
as <br/>
begin <br/>
merge People as tbl<br/>
using ( select <br/>
@Name as Name,<br/>
@Age as Age) as row <br/>
on <br/>
tbl.Name = row .Name<br/>
when not matched then <br/>
insert (Name,Age)<br/>
values ( row .Name, row .Age)<br/>
when matched then <br/>
update set <br/>
@Id = tbl.Id,<br/>
tbl.Name = row .Name,<br/>
tbl.Age = row .Age<br/>
;<br/>
if @Id is null <br/>
set @Id = SCOPE_IDENTITY()<br/>
return @Id<br/>
end <br/>
これで、このストアドプロシージャはEF、Linq2Sql、または他のORMにマップされ、使用できるようになりました。 EF4の例を次に示します。
var op = new ObjectParameter( "Id" , typeof (Int32));<br/>
using ( var mc = new ModelContainer())<br/>
{<br/>
// add me
mc.PersonUpsert(op, "Dmitri" , 25);<br/>
mc.SaveChanges();<br/>
}<br/>
上記の例では、新しいオブジェクトが追加されたか古いオブジェクトが更新されたかを確認することもできます。いずれの場合でも、後で使用するためにオブジェクトの
Id
取得できます。 もちろん、典型的なユースケースでは、これらのプロセスはすべて、Repository / UnitOfWorkを介して、すべての種類の
TransactionScope
とそのilkを使用して実装されます。
XSLTを使用した「ダブルジャンプ」の代わりに、自分ですべてを行う1つのT4ファイルを作成することは非常に可能ですが、これは退屈な作業であり、原則として説明したように簡単です。 もちろん、EDMXから
<Schema>
を削除する必要があるという事実も不完全ですが、今のところは機能します。 ちなみに、不明な理由(または見栄えが悪いかもしれません)で、XMLをTXTにマッピングし、同時にXSLT変換を実行できるマッパーがいないことにも注意してください。 FlexTextを見ましたが、このプログラムでは行に挿入できず、MapForceはそれを使用してC#のみを生成し、XSLTの実行を拒否しました。
完全なプロセスの説明
典型的なスクレーパーを作成するプロセスを完全に説明するときです。 つまり、一般的な実装では、次のアクションを実行します。
- 処理する必要があるページを見つけ、FireBugを使用してそれらを調べます
- ダウンロードページ-複雑な認証またはユーザーからの入力が必要な場合は
WebRequest
使用し、そうでない場合はWebRequest
などを使用します。 - ページで必要な要素を見つけ、
- 要素を変換してXML互換にする
- このXMLからのデータを保存するエンティティを作成する
- このエンティティのコレクションクラス
Collection<T>
を作成します -
xsd -t:MyCollection MyAssembly.exe
を使用して、コレクションクラスに適切なXSDを生成します。 - ソースHTMLでXSDを自動生成
- 1つのXSDから別のXSDへのマッピング
- コードでは、処理されたHTMLからXMLにマッピングします
- 結果のXMLからエンティティまたはエンティティのコレクションを読み取ります
- Upsertプロシージャを作成します(例):
- WSDLから
<Schema>
要素を引き裂く - 要素を単純化された形式に変換する
- 結果のXMLをSQLに変換して、ストアドプロシージャを作成します
- データベースにストアドプロシージャを作成する
- ストアドプロシージャをORMにインポートする
- WSDLから
- エンティティを作成した後、データベースに書き込みます(更新または新しいエンティティを作成します)
そのようなもの。 もちろん、確かに簡単な方法があります。 繰り返しになりますが、誰かが書いたように、マッピングの代わりに直接Linqを使用できます。これは単純なスクリプトで非常にうまく機能します。 頑張って