データ収集、パート3

以前の投稿( パート1パート2 )で、インターネットからデータをHTMLとして取得する方法、通常のデータの読み込み用に簡単なサービスを設定する方法、HTMLを調整してCLRオブジェクトに読み込む方法について説明しました。 この投稿では、データベースにデータを保存および更新する方法について説明します。 スクレイピングプロセスの詳細も説明します。


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の実行を拒否しました。

完全なプロセスの説明


典型的なスクレーパーを作成するプロセスを完全に説明するときです。 つまり、一般的な実装では、次のアクションを実行します。


そのようなもの。 もちろん、確かに簡単な方法があります。 繰り返しになりますが、誰かが書いたように、マッピングの代わりに直接Linqを使用できます。これは単純なスクリプトで非常にうまく機能します。 頑張って

Source: https://habr.com/ru/post/J94742/


All Articles