LINQ to SQL:リポジトリパターン

LINQバー この記事では、LINQ to SQLに基づくリポジトリパターンを実装するためのオプションの1つを検討します。

現在、LINQ to SQLは、オブジェクトリレーショナルマッピングの問題を解決するために設計されたMicrosoftテクノロジーの1つです。 代替のEntity Frameworkテクノロジーはより強力なツールですが、LINQ to SQLには、比較的単純で低レベルという利点があります。

この記事は、LINQ to SQLの長所を実証する試みです。 リポジトリパターンは、LINQ to SQLパラダイムに完全に適合します。

リポジトリ


まず、リポジトリとは何かを思い出しましょう。
public interface IRepository<T> where T: Entity
{
IQueryable<T> GetAll();
bool Save(T entity);
bool Delete( int id);
bool Delete(T entity);
}


* This source code was highlighted with Source Code Highlighter .

リポジトリは、データベースにアクセスするためのファサードです。 リポジトリ外のすべてのアプリケーションコードは、データベースを介してのみ動作します。 したがって、リポジトリはデータベースを操作するロジックをカプセル化します。これは、アプリケーションのオブジェクトリレーショナルマッピングのレイヤーです。 より正確には、リポジトリ、またはストレージは、1つのタイプのデータ(1つのモデルクラス、最も単純な場合は1つのデータベーステーブル)にアクセスするためのインターフェースです。 データへのアクセスは、すべてのリポジトリの全体を通じて整理されます。 リポジトリインターフェイスは、アプリケーションモデルの観点から指定されていることに注意してください。 エンティティは、アプリケーションモデル( POCOオブジェクト)のすべてのクラスの基本クラスです。
public abstract class Entity
{
protected Entity()
{
Id = -1;
}

public int Id { get ; set ; }

public bool IsNew()
{
return Id == -1;
}
}


* This source code was highlighted with Source Code Highlighter .

一般的に、 Id属性はデータベースレベルでのみ必要です。 アプリケーションモデルレベルでは、明示的な識別子を使用せずにオブジェクトの一意性を解決できます。 したがって、提案されたソリューションは、理論的な観点からのオブジェクトリレーショナルマッピングの問題に対する完全に正直なソリューションではありません。 ただし、実際には、アプリケーションモデルで主キー属性を使用すると、多くの場合、さらに柔軟なスキームになります。 提案されたソリューションは、データベース層の抽象化レベルとアーキテクチャの柔軟性の間の妥協案です。

IRepositoryインターフェイスメソッドは、CRUD操作の完全なセットを提供します。

GetAll-データベースに保存されているこのタイプのオブジェクトのセット全体を返します。 IQueryable <T>インターフェイスを使用することで、オブジェクトの選択に関するフィルタリング、ソート、およびその他の操作がより高いレベルで実行されます。 詳細については、「フィルターとコンベヤー」セクションを参照してください。
保存 -モデルオブジェクトをデータベースに保存します。 新規の場合はINSERT操作が実行され、そうでない場合は-UPDATEが実行されます。
削除 -データベースからオブジェクトを削除します。 関数を呼び出すには、削除されたレコードのidパラメーターとアプリケーションモデルクラスオブジェクトのパラメーターの2つのオプションがあります。

実装


1つのCustomersテーブルで構成されるデータベースがあるとします。
CREATE TABLE dbo.Customers
(
[Id] int IDENTITY (1,1) NOT NULL PRIMARY KEY ,
[Name] nvarchar(200) NOT NULL ,
[Address] nvarchar(1000) NULL ,
[Balance] money NOT NULL
)

* This source code was highlighted with Source Code Highlighter .

最初に、データベースモデルオブジェクトのクラスとその表示のプロパティが設定されるプロジェクトにdbmlファイルを追加します。 これを行うには、Visual Studioでソリューションエクスプローラーのコンテキストメニュー( 新しいアイテム...->データ-> LINQ to SQLクラス )を使用します 。 デザイナーウィンドウが表示されたら、サーバーエクスプローラーを開き、Customersテーブルをデザイナーウィンドウにドラッグします。 取得する必要があるものは次のとおりです。

LINQ to SQLデザイナー


その結果、Visual StudioはデータベースモデルのCustomerクラスを生成します。 一般に、アプリケーションのモデル自体はデータベースモデルとは異なりますが、この例では実際に一致しています。 以下は、アプリケーションモデルのCustomerクラスの説明です。
public class Customer : Entity
{
public string Name { get ; set ; }
public string Address { get ; set ; }
public decimal Balance { get ; set ; }
}


* This source code was highlighted with Source Code Highlighter .

CustomerRepository(Customer型のオブジェクトのリポジトリ)の実装を開始します。 モデルの他のクラスのリポジトリを作成するときにコードの重複を避けるために、機能のほとんどは基本クラスに移動されます。
public abstract class RepositoryBase<T, DbT> : IRepository<T>
where T : Entity where DbT : class , IDbEntity, new ()
{
protected readonly DbContext context = new DbContext();

public IQueryable<T> GetAll()
{
return GetTable().Select(GetConverter());
}

public bool Save(T entity)
{
DbT dbEntity;

if (entity.IsNew())
{
dbEntity = new DbT();
}
else
{
dbEntity = GetTable().Where(x => x.Id == entity.Id).SingleOrDefault();
if (dbEntity == null )
{
return false ;
}
}

UpdateEntry(dbEntity, entity);

if (entity.IsNew())
{
GetTable().InsertOnSubmit(dbEntity);
}

context.SubmitChanges();

entity.Id = dbEntity.Id;
return true ;
}

public bool Delete( int id)
{
var dbEntity = GetTable().Where(x => x.Id == id).SingleOrDefault();

if (dbEntity == null )
{
return false ;
}

GetTable().DeleteOnSubmit(dbEntity);

context.SubmitChanges();
return true ;
}

public bool Delete(T entity)
{
return Delete(entity.Id);
}

protected abstract Table<DbT> GetTable();
protected abstract Expression<Func<DbT, T>> GetConverter();
protected abstract void UpdateEntry(DbT dbEntity, T entity);
}


* This source code was highlighted with Source Code Highlighter .

すべてのLINQ to SQLモデルクラスは、共通のIDbEntityインターフェイスを共有します。
public interface IDbEntity
{
int Id { get ; }
}


* This source code was highlighted with Source Code Highlighter .

残念ながら、ビジュアルデザイナでは、LINQ to SQLオブジェクトの基本クラスを指定できません。 これを行うには、XMLエディターでdbmlファイルを開き(...で開く)、 Database要素のEntityBase属性を指定します。
< Database EntityBase ="Data.Db.IDbEntity" ... >

* This source code was highlighted with Source Code Highlighter .

以下は、 CustomersRepositoryクラスの説明です。
public class CustomersRepository : RepositoryBase<Customer, Db.Entities.Customer>
{
protected override Table<Db.Entities.Customer> GetTable()
{
return context.Customers;
}

protected override Expression<Func<Db.Entities.Customer, Customer>> GetConverter()
{
return c => new Customer
{
Id = c.Id,
Name = c.Name,
Address = c.Address,
Balance = c.Balance
};
}

protected override void UpdateEntry(Db.Entities.Customer dbCustomer, Customer customer)
{
dbCustomer.Name = customer.Name;
dbCustomer.Address = customer.Address;
dbCustomer.Balance = customer.Balance;
}
}


* This source code was highlighted with Source Code Highlighter .

フィルターとコンベア


リポジトリのGetAllメソッドは、 IQueryable <T>インターフェイスを実装するオブジェクトを返します。 これにより、フィルタリング操作( Whereメソッド)を、オブジェクトの並べ替え、並べ替え、およびIQueryable <T>で定義されたその他の操作に適用できます。
便宜上、拡張メソッドで頻繁に使用される操作を実行できます。 たとえば、顧客名によるフィルタリング。
public static IQueryable<Customer> WithNameLike( this IQueryable<Customer> q, string name)
{
return q.Where(customer => customer.Name.StartsWith(name));
}


* This source code was highlighted with Source Code Highlighter .

これで、リポジトリを次のように使用できます。
IRepository<Customer> rep = new CustomersRepository();
foreach ( var cust in rep.GetAll().WithNameLike(“Google”).OrderBy(x => x.Name)) { … }


* This source code was highlighted with Source Code Highlighter .

使用するフィルターやその他の操作の複雑さは関係ありません。 それらがいくつあっても。 その結果、正確に1つのデータベースクエリが実行されます。 この原則は遅延実行と呼ばます-最終的なSQLクエリは、最終的な選択を取得したい時点でのみ生成および実行されます。 この場合、これはforeachループの最初の反復の直前に発生します。
アーキテクチャの重要な利点は、リポジトリレイヤを除くアプリケーション全体と同様に、データベースモデルではなくアプリケーションモデルでフィルタが機能することです。

分析


次に、リポジトリで特定の操作を実行するときに、生成されたLINQ to SQLデータベースクエリを分析します。

GetAll 。 例の場合:
rep.GetAll().WithNameLike(“Google”).OrderBy(x => x.Name)

* This source code was highlighted with Source Code Highlighter .

単一の要求が行われます:
exec sp_executesql N 'SELECT [t0].[Name], [t0].[Address], [t0].[Balance], [t0].[Id]
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[Name] LIKE @p0
ORDER BY [t0].[Name]'
,N '@p0 nvarchar(7)' ,@p0=N 'Google%'


* This source code was highlighted with Source Code Highlighter .

新しいオブジェクトのSaveメソッドは、単一のINSERT要求を実行します。 例:
exec sp_executesql N 'INSERT INTO [dbo].[Customers]([Name], [Address], [Balance])
VALUES (@p0, @p1, @p2)
SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value]'
,N '@p0 nvarchar(6),@p1 nvarchar(3),@p2 money' ,@p0=N 'Google' ,@p1=N 'USA' ,@p2=$10000.0000


* This source code was highlighted with Source Code Highlighter .

既存のオブジェクトまたはDeleteに対してSaveが呼び出された場合、2つのクエリが実行されます。 1つ目は、データベースからレコードを取得することです。 例:
exec sp_executesql N 'SELECT [t0].[Id], [t0].[Name], [t0].[Address], [t0].[Balance]
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[Id] = @p0'
,N '@p0 int' ,@p0=29


* This source code was highlighted with Source Code Highlighter .

2番目のクエリは、それぞれUPDATEまたはDELETE操作の直接実行です。 DELETEの例:
exec sp_executesql N 'DELETE FROM [dbo].[Customers] WHERE ([Id] = @p0) AND ([Name] = @p1) AND ([Address] = @p2) AND ([Balance] = @p3)' ,N '@p0 int,@p1 nvarchar(6),@p2 nvarchar(3),@p3 money' ,@p0=29,@p1=N 'Google' ,@p2=N 'USA' ,@p3=$10000.0000

* This source code was highlighted with Source Code Highlighter .

UPDATEおよびDELETEの場合、最初のクエリは冗長ですが、それがないと、標準のLINQ to SQLツールを使用してオブジェクトを保存または削除できません。
不要な要求を取り除く1つの方法は、ストアドプロシージャを使用することです。

おわりに


この記事の主な目標は、リポジトリパターンとLINQ to SQLでのその実装の一般的な考え方を示すことです。 アプローチを適用する考慮された例は単純すぎます。 実際のアプリケーションでは、このアーキテクチャを実装するときに多くの問題が発生します。 ここにそれらのいくつかがあります。
ほとんどの問題は解決可能ですが、これらの問題はこの記事の範囲外です。

記事のソースコード (ASP.NET MVCプロジェクト)。

関連リンク


リポジトリパターン(Martin Fowler)

Scott GuthrieによるLINQ to SQL記事

ストアフロントMVC(スクリーンキャスト):
リポジトリパターン
パイプとフィルター

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


All Articles