はS/O上の次のような質問がたくさんありますが、これは私が対処見ていない特定の問題があります。リポジトリパターン+依存性の注入+のUnitOfWork + EF
これは、MVCアプリケーションです。 Dependancy injection(Simple Injector、私はそれは無関係だと思うが)を使用しています。それはPer Web Requestを注入します。
私が持っている主な問題は、Webリクエストごとに私のUoWが注入されるため、私が最近必要だったデータを追加するときに失敗し続けてしまうことです。
次のコードは示しています
データ層
public abstract RepositoryBase<TEntity>
{
private readonly MyDbContext _context;
//fields set from contrstuctor injection
protected RepositoryBase(MyDbContext context)
{
_context = context;
}
public IList<TEntity> GetAll()
{
return _context.Set<TEntity>().ToList();
}
public TEntity GetById(Int32 id)
{
_context.Set<TEntity>().Find(id);
}
public TEntity Insert(TEntity entity)
{
_context.Set<TEntity>().Add(entity);
}
}
public UserRepository : RepositoryBase<User>, IUserRepository
{
//constructor injection
public UserRepository(MyDbContext c) : base(c) {}
public Update(Int32 id, String name, String email, Int32 ageYears)
{
var entity = GetById(id);
entity.Name = name;
entity.Email = email;
entity.Age = ageYears;
}
public UpdateName(Int32 id, String name)
{
var entity = GetById(id);
entity.Name = name;
}
}
public AddressRepository : RepositoryBase<Address>, IAddressRepository
{
//constructor injection
public AddressRepository(MyDbContext c) : base(c) {}
public Update(Int32 id, String street, String city)
{
var entity = GetById(id);
entity.Street = street;
entity.City = city;
}
public Address GetForUser(Int32 userId)
{
return _context.Adresses.FirstOrDefault(x => x.UserId = userId);
}
}
public DocumentRepository : RepositoryBase<Document>, IDocumentRepository
{
//constructor injection
public DocumentRepository(MyDbContext c) : base(c) {}
public Update(Int32 id, String newTitle, String newContent)
{
var entity.GetById(id);
entity.Title = newTitle;
entity.Content = newContent;
}
public IList<Document> GetForUser(Int32 userId)
{
return _context.Documents.Where(x => x.UserId == userId).ToList();
}
}
public UnitOfWork : IUnitOfWork
{
private readonly MyDbContext _context;
//fields set from contrstuctor injection
public UnitOfWork(MyDbContext context)
{
_context = context;
}
public Int32 Save()
{
return _context.SaveChanges();
}
public ITransaction StartTransaction()
{
return new Transaction(_context.Database.BeginTransaction(IsolationLevel.ReadUncommitted));
}
}
public Transaction : ITransaction
{
private readonly DbContextTransaction _transaction;
public Transaction(DbContextTransaction t)
{
_transaction = t;
State = TransactionState.Open;
}
public void Dispose()
{
if (_transaction != null)
{
if (State == TransactionState.Open)
{
Rollback();
}
_transaction.Dispose();
}
}
public TransactionState State { get; private set; }
public void Commit()
{
try
{
_transaction.Commit();
State = TransactionState.Committed;
}
catch (Exception)
{
State = TransactionState.FailedCommitRolledback;
throw;
}
}
public void Rollback()
{
if (_transaction.UnderlyingTransaction.Connection != null)
{
_transaction.Rollback();
}
State = TransactionState.Rolledback;
}
}
サービス層
public DocumentService : IDocumentService
{
//fields set from contrstuctor injection
private readonly IDocumentRepository _docRepo;
private readonly IUnitOfWork _unitOfWork;
public void AuthorNameChangeAddendum(Int32 userId, String newAuthorName)
{
//this works ok if error thrown
foreach(var doc in _docRepo.GetForUser(userId))
{
var addendum = $"\nAddendum: As of {DateTime.Now} the author will be known as {newAuthorName}.";
_docRepo.Update(documentId, doc.Title + "-Processed", doc.Content + addendum);
}
_unitOfWork.Save();
}
}
public UserService
{
//fields set from contrstuctor injection
private readonly IUserRepository _userRepo;
private readonly IAddressRepository _addressRepo;
private readonly IUnitOfWork _unitOfWork;
private readonly IDocumentService _documentService;
public void ChangeUser(Int32 userId, String newName, String newStreet, String newCity)
{
//this works ok if error thrown
_userRepo.UpdateName(userId, newName);
var address = _addressRepo.GetForUser(userId);
_addressRepo.Update(address.AddressId, newStreet, newCity);
_unitOfWork.Save();
}
public void ChangeUserAndProcessDocs(Int32 userId, String newName, Int32)
{
//this is ok because of transaction
using(var transaction = _unitOfWork.StartTransaction())
{
_documentService.AuthorNameChangeAddendum(userId, newName); //this function calls save() on uow
//possible exception here could leave docs with an inaccurate addendum, so transaction needed
var x = 1/0;
_userRepo.UpdateName(userId, newName);
_unitOfWork.Save();
transaction.Commit();
}
}
//THE PROBLEM:
public IList<String> AddLastNameToAll(String lastName)
{
var results = new List<String>();
foreach(var u in _userRepo.GetAll())
{
try
{
var newName = $"{lastName}, {u.Name}";
_userRepo.UpdateName(u.UserId, newName);
_unitOfWork.Save(); //throws validation exception
results.Add($"Changed name from {u.Name} to {newName}.");
}
catch(DbValidationException e)
{
results.Add($"Error adding last name to {u.Name}: {e.Message}");
//but all subsequeqnet name changes will fail because the invalid entity will be stuck in the context
}
}
return results;
}
}
あなたは、UOWの実装がChangeUser()
を処理するUserServiceの中で見ることができますし、 ChangeUserAndProcessDocs()
の潜在的な問題明示的なトランザクションの使用によって処理されます。
しかし、AddLastNameToAll()
では、名前の列が新しい名前を処理するのに十分な長さではないため、100人のユーザーが更新され、3番目のユーザーが失敗した場合、結果3〜100のすべてに同じ検証メッセージが表示されますそれら。これを解決する唯一の方法は、forループの各パスに対して新しいUnitOf Work(DbContext)を使用することです。これは私の実装では実際には不可能です。
私のUoW + Repo実装は、EFが他のレイヤーに漏れることを防ぎ、他のレイヤーにトランザクションを作成する機能を提供します。しかし、AサービスがBサービスを呼び出すと、BサービスはAが準備される前にSave()を呼び出すことができるということはいつも不思議に思っています。スコープされたトランザクションはそれを解決しますが、まだ少し奇妙に感じます。
私はUoWパターンの廃棄を考えていましたが、すべてのリポジトリアクションを即座にコミットするだけですが、2つの異なるエンティティタイプを更新して2回目の更新を失敗させるという大きな問題は残しましたが、今(ChangeUserAndProcessDocs()
は一例で参照してください。
だから私は、注入された文脈を無視し、それが自分だ作成UserRepository UpdateNameImmediately()
の特別UpdateName()
機能させることが残っています。
public void UpdateNameImmediately(Int32 id, String newName)
{
using(var mySingleUseContext = new MyDbContext())
{
var u = mySingleUseContext.Users.Find(id);
u.Name = newName;
mySingleUseContext.SaveChanges();
}
}
これは奇妙に感じ、今、このため、関数はq私の他のすべてのリポジトリのアクションと違って、トランザクションに従わないということです。
これを解決するUoW + EF +リポジトリパターン+ DIの実装はありますか?どのように動作する
EFはデフォルトでUoWとRepoパターンを実装していますか? DbContextはUoWで、DbSetはReposです。私のポイント; EFにさらに抽象化を加えることは時間の無駄です。 –
私はDbContextがUoWであることを知りました。私はEFをサービスレイヤーに漏らさないために、その上に薄いラッパーしかありません。その周りのレポ・ラッパーは、いくつかのリポジトリ・アクションを通してすべての私を援助し、悪い結合などを警察するのに役立ちます。これらのことを達成するためのよりよい方法があれば、私はそれを聞いています。 – sheamus
AddLastNameToAll()に示された問題は、実際にはほとんどすべての一括処理を行うきわめて特化したサービスで発生します。私はUoWパターンを使用しない特殊なImmediateRepositoriesを作成することに傾いています。私はこれが最も簡単だと思います。 – sheamus