最近、ASP.NET MVCを学びました(大好きです)。私は、各リクエストで依存関係注入を使用してリポジトリインスタンスを読み込む会社と協力しており、そのリポジトリの使用に精通しています。
しかし今、私は自分のMVCアプリケーションをいくつか書いています。私は会社が使用するリポジトリの方法と理由を完全に理解していないため、データアクセスを実装するための最適なアプローチを決定しようとしています。
C#とEntity Framework(すべての最新バージョン)を使用しています。
データアクセスを処理するための3つの一般的なアプローチがあります。
データにアクセスするたびにusingステートメント内の通常のDBコンテキスト。これは簡単で、問題なく動作します。ただし、2つの場所で1つのリクエスト内で同じデータを読み取る必要がある場合、データを2回読み取る必要があります。 (リクエストごとに単一のリポジトリを使用すると、両方の場所で同じインスタンスが使用され、2回目の読み取りでは最初の読み取りからデータが返されるだけです。)
典型的な リポジトリパターン 。私が理解していない理由から、この典型的なパターンには、データベースから使用されるすべてのテーブルのラッパークラスの作成が含まれます。それは私には間違っているようです。実際、インターフェイスとしても実装されているため、技術的には各テーブルに2つのラッパークラスを作成します。 EFは私のためにテーブルを作成します。このアプローチが理にかなっているとは思わない。
generic repository pattern もあり、すべてのエンティティオブジェクトを提供する単一のリポジトリクラスが作成されます。これは私にとってはるかに理にかなっています。しかし、それは他の人にとって意味がありますか?リンクは最良のアプローチの上にありますか?
このトピックについて他の人から意見を聞きたいです。上記のいずれかを使用して独自のリポジトリを作成していますか、それともまったく別のことをしていますか。シェアしてください。
#2と#3のブレンドを使用しましたが、可能であれば厳密な汎用リポジトリー(#3のリンクで提案されているものよりも厳しい)を好みます。 #1は、単体テストではうまく機能しないため、良くありません。
ドメインが小さい場合や、ドメインでクエリを許可するエンティティを制限する必要がある場合は、汎用リポジトリを実装するエンティティ固有のリポジトリインターフェイスを定義する#2または#3が適切だと思います。ただし、クエリを実行するすべてのエンティティに対してインターフェイスと具体的な実装を記述する必要はなく、面倒です。 public interface IFooRepository : IRepository<Foo>
(これも、開発者を許可された集約ルートのセットに制限する必要がない限り)?
Add
、Remove
、Get
、GetDeferred
、Count
、およびFind
メソッドを使用して、汎用リポジトリインターフェイスを定義するだけです(検索はIQueryable
インターフェイス(LINQを許可)、具体的な汎用実装を作成し、1日で呼び出します。私はFind
とLINQに大きく依存しています。特定のクエリを複数回使用する必要がある場合は、拡張メソッドを使用し、LINQを使用してクエリを記述します。
これは、私の永続化ニーズの95%をカバーします。一般的には実行できない何らかの永続化アクションを実行する必要がある場合は、自家製のICommand
APIを使用します。たとえば、NHibernateを使用していて、ドメインの一部として複雑なクエリを実行する必要がある場合や、一括コマンドを実行する必要がある場合があります。 APIはおおよそ次のようになります。
// marker interface, mainly used as a generic constraint
public interface ICommand
{
}
// commands that return no result, or a non-query
public interface ICommandNoResult : ICommand
{
void Execute();
}
// commands that return a result, either a scalar value or record set
public interface ICommandWithResult<TResult> : ICommand
{
TResult Execute();
}
// a query command that executes a record set and returns the resulting entities as an enumeration.
public interface IQuery<TEntity> : ICommandWithResult<IEnumerable<TEntity>>
{
int Count();
}
// used to create commands at runtime, looking up registered commands in an IoC container or service locator
public interface ICommandFactory
{
TCommand Create<TCommand>() where TCommand : ICommand;
}
これで、特定のコマンドを表すインターフェイスを作成できます。
public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance>
{
Decimal MinimumBalance { get; set; }
}
具体的な実装を作成し、生のSQL、NHibernate HQLなどを使用して、サービスロケーターに登録できます。
これで、ビジネスロジックで次のようなことができます。
var query = factory.Create<IAccountsWithBalanceQuery>();
query.MinimumBalance = 100.0;
var overdueAccounts = query.Execute();
仕様パターンをIQuery
とともに使用して、無数の紛らわしいプロパティを持つインターフェースを持たずに、意味のあるユーザー入力駆動のクエリを構築することもできますが、それは仕様パターンがそれ自体で混乱しないことを前提としています;)。
パズルの最後の1つは、リポジトリで特定の事前および事後リポジトリ操作を行う必要がある場合です。これで、特定のエンティティのジェネリックリポジトリの実装を非常に簡単に作成し、関連するメソッドをオーバーライドして必要な処理を実行し、IoCまたはサービスロケーターの登録を更新して実行できます。
ただし、このロジックは横断的であり、リポジトリメソッドをオーバーライドして実装するのが面倒な場合があります。そこで、基本的にイベントシンクであるIRepositoryBehavior
を作成しました。 (以下は私の頭の上の大まかな定義です)
public interface IRepositoryBehavior
{
void OnAdding(CancellableBehaviorContext context);
void OnAdd(BehaviorContext context);
void OnGetting(CancellableBehaviorContext context);
void OnGet(BehaviorContext context);
void OnRemoving(CancellableBehaviorContext context);
void OnRemove(BehaviorContext context);
void OnFinding(CancellableBehaviorContext context);
void OnFind(BehaviorContext context);
bool AppliesToEntityType(Type entityType);
}
現在、これらの動作は何でもかまいません。監査、セキュリティチェック、ソフト削除、ドメイン制約の強制、検証など。動作を作成し、IoCまたはサービスロケーターに登録し、汎用リポジトリーを変更して登録済みIRepositoryBehavior
sのコレクションを取得します。現在のリポジトリタイプに対して各動作をチェックし、適用可能な各動作の操作をpre/postハンドラでラップします。
次に、ソフト削除の動作の例を示します(ソフト削除とは、エンティティの削除を求められたときにエンティティを削除済みとしてマークすることで、それを再度返すことはできませんが、実際に物理的に削除されることはありません)。
public SoftDeleteBehavior : IRepositoryBehavior
{
// omitted
public bool AppliesToEntityType(Type entityType)
{
// check to see if type supports soft deleting
return true;
}
public void OnRemoving(CancellableBehaviorContext context)
{
var entity = context.Entity as ISoftDeletable;
entity.Deleted = true; // when the NHibernate session is flushed, the Deleted column will be updated
context.Cancel = true; // set this to true to make sure the repository doesn't physically delete the entity.
}
}
はい、これは基本的にNHibernateのイベントリスナーの単純化および抽象化された実装ですが、だから私はそれが好きです。 A)NHibernateを画像に含めずに動作を単体テストできますB)NHibernateの外部でこれらの動作を使用できます(リポジトリはRESTサービス呼び出し)をラップするクライアント実装であると言います)C)NHのイベントリスナーはお尻の本当の痛みになる可能性があります;)
いくつかの注意事項がありますが、1番をお勧めします。番号2は最も一般的であるように思われますが、私の経験では、リポジトリはクエリのための厄介なゴミ捨て場になります。汎用リポジトリ(2)を使用する場合、それはDBContextの単なる薄いラッパーであり、ORMの変更(悪い考え)を計画しているのでなければ、まったく意味がありません。
ただし、DBContextに直接アクセスする場合は、Pipes and Filtersパターンを使用して、一般的なロジックを再利用できるようにします。
items = DBContext.Clients
.ByPhoneNumber('1234%')
.ByOrganisation(134);
ByPhoneNumberとBy Organizationは単なる拡張メソッドです。
ここでは、Asp.Net MVCのベストリポジトリパターンに進みます
リポジトリパターンは、アプリケーションのデータ層とドメイン層の間に分離層を追加します。また、アプリケーションのデータアクセス部分のテストが容易になります。
データベースファクトリ(IDatabaseFactory.cs):
public interface IDatabaseFactory : IDisposable
{
Database_DBEntities Get();
}
データベースファクトリの実装(DatabaseFactory.cs):
public class DatabaseFactory : Disposable, IDatabaseFactory
{
private Database_DBEntities dataContext;
public Database_DBEntities Get()
{
return dataContext ?? (dataContext = new Database_DBEntities());
}
protected override void DisposeCore()
{
if (dataContext != null)
dataContext.Dispose();
}
}
ベースインターフェイス(IRepository.cs):
public interface IRepository<T> where T : class
{
void Add(T entity);
void Update(T entity);
void Detach(T entity);
void Delete(T entity);
T GetById(long Id);
T GetById(string Id);
T Get(Expression<Func<T, bool>> where);
IEnumerable<T> GetAll();
IEnumerable<T> GetMany(Expression<Func<T, bool>> where);
void Commit();
}
抽象クラス(Repository.cs):
public abstract class Repository<T> : IRepository<T> where T : class
{
private Database_DBEntities dataContext;
private readonly IDbSet<T> dbset;
protected Repository(IDatabaseFactory databaseFactory)
{
DatabaseFactory = databaseFactory;
dbset = DataContext.Set<T>();
}
/// <summary>
/// Property for the databasefactory instance
/// </summary>
protected IDatabaseFactory DatabaseFactory
{
get;
private set;
}
/// <summary>
/// Property for the datacontext instance
/// </summary>
protected Database_DBEntities DataContext
{
get { return dataContext ?? (dataContext = DatabaseFactory.Get()); }
}
/// <summary>
/// For adding entity
/// </summary>
/// <param name="entity"></param>
public virtual void Add(T entity)
{
try
{
dbset.Add(entity);
// dbset.Attach(entity);
dataContext.Entry(entity).State = EntityState.Added;
int iresult = dataContext.SaveChanges();
}
catch (UpdateException ex)
{
}
catch (DbUpdateException ex) //DbContext
{
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// For updating entity
/// </summary>
/// <param name="entity"></param>
public virtual void Update(T entity)
{
try
{
// dbset.Attach(entity);
dbset.Add(entity);
dataContext.Entry(entity).State = EntityState.Modified;
int iresult = dataContext.SaveChanges();
}
catch (UpdateException ex)
{
throw new ApplicationException(Database_ResourceFile.DuplicateMessage, ex);
}
catch (DbUpdateException ex) //DbContext
{
throw new ApplicationException(Database_ResourceFile.DuplicateMessage, ex);
}
catch (Exception ex) {
throw ex;
}
}
/// <summary>
/// for deleting entity with class
/// </summary>
/// <param name="entity"></param>
public virtual void Delete(T entity)
{
dbset.Remove(entity);
int iresult = dataContext.SaveChanges();
}
//To commit save changes
public void Commit()
{
//still needs modification accordingly
DataContext.SaveChanges();
}
/// <summary>
/// Fetches values as per the int64 id value
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual T GetById(long id)
{
return dbset.Find(id);
}
/// <summary>
/// Fetches values as per the string id input
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual T GetById(string id)
{
return dbset.Find(id);
}
/// <summary>
/// fetches all the records
/// </summary>
/// <returns></returns>
public virtual IEnumerable<T> GetAll()
{
return dbset.AsNoTracking().ToList();
}
/// <summary>
/// Fetches records as per the predicate condition
/// </summary>
/// <param name="where"></param>
/// <returns></returns>
public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
{
return dbset.Where(where).ToList();
}
/// <summary>
///
/// </summary>
/// <param name="entity"></param>
public void Detach(T entity)
{
dataContext.Entry(entity).State = EntityState.Detached;
}
/// <summary>
/// fetches single records as per the predicate condition
/// </summary>
/// <param name="where"></param>
/// <returns></returns>
public T Get(Expression<Func<T, bool>> where)
{
return dbset.Where(where).FirstOrDefault<T>();
}
}
コントローラーでこのリポジトリパターンにアクセスする方法:
1。ユーザーモデルがあります:
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
}
2。ここで、UserModelのリポジトリクラスを作成する必要があります
public class UserRepository : Repository<User>, IUserRepository
{
private Database_DBEntities dataContext;
protected IDatabaseFactory DatabaseFactory
{
get;
private set;
}
public UserRepository(IDatabaseFactory databaseFactory)
: base(databaseFactory)
{
DatabaseFactory = databaseFactory;
}
protected Database_DBEntities DataContext
{
get { return dataContext ?? (dataContext = DatabaseFactory.Get()); }
}
public interface IUserRepository : IRepository<User>
{
}
}
。次に、すべてのCRUDメソッドでUserService Interface(IUserService.cs)を作成する必要があります:
public interface IUserService
{
#region User Details
List<User> GetAllUsers();
int SaveUserDetails(User Usermodel);
int UpdateUserDetails(User Usermodel);
int DeleteUserDetails(int Id);
#endregion
}
4。次に、すべてのCRUDメソッドでUserService Interface(UserService.cs)を作成する必要があります:
public class UserService : IUserService
{
IUserRepository _userRepository;
public UserService() { }
public UserService(IUserRepository userRepository)
{
this._userRepository = userRepository;
}
public List<User> GetAllUsers()
{
try
{
IEnumerable<User> liUser = _userRepository.GetAll();
return liUser.ToList();
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// Saves the User details.
/// </summary>
/// <param name="User">The deptmodel.</param>
/// <returns></returns>
public int SaveUserDetails(User Usermodel)
{
try
{
if (Usermodel != null)
{
_userRepository.Add(Usermodel);
return 1;
}
else
return 0;
}
catch
{
throw;
}
}
/// <summary>
/// Updates the User details.
/// </summary>
/// <param name="User">The deptmodel.</param>
/// <returns></returns>
public int UpdateUserDetails(User Usermodel)
{
try
{
if (Usermodel != null)
{
_userRepository.Update(Usermodel);
return 1;
}
else
return 0;
}
catch
{
throw;
}
}
/// <summary>
/// Deletes the User details.
/// </summary>
/// <param name="Id">The code identifier.</param>
/// <returns></returns>
public int DeleteUserDetails(int Id)
{
try
{
User Usermodel = _userRepository.GetById(Id);
if (Usermodel != null)
{
_userRepository.Delete(Usermodel);
return 1;
}
else
return 0;
}
catch
{
throw;
}
}
}
5。これで、Repository Patternのすべての設定が完了し、ユーザーコントローラーのすべてのデータにアクセスできます:
//Here is the User Controller
public class UserProfileController : Controller
{
IUserService _userservice;
public CustomerProfileController(IUserService userservice)
{
this._userservice = userservice;
}
[HttpPost]
public ActionResult GetAllUsers(int id)
{
User objUser=new User();
objUser = _userservice.GetAllUsers().Where(x => x.Id == id).FirstOrDefault();
}
}
RF-作業単位&(拡張可能/汎用)リポジトリフレームワーク ですぐに使用できるソリューションがあります。時間を大幅に節約できます。彼らは一般的なリポジトリを実装しました(非同期リポジトリもあります)。リポジトリを拡張するために、彼らは次のような拡張機能を使用しました。
public static decimal GetCustomerOrderTotalByYear(this IRepository<Customer> repository, string customerId, int year)
{
return repository
.Queryable()
.Where(c => c.CustomerID == customerId)
.SelectMany(c => c.Orders.Where(o => o.OrderDate != null && o.OrderDate.Value.Year == year))
.SelectMany(c => c.OrderDetails)
.Select(c => c.Quantity*c.UnitPrice)
.Sum();
}
QueryObjectのような一部のクラスは、プロジェクトによっては過負荷になる場合がありますが、過度に作業を開始して実行するのに役立つソリューションです。