IMO cả phần Repository
trừu tượng và phần UnitOfWork
trừu tượng đều có một vị trí rất giá trị trong bất kỳ sự phát triển có ý nghĩa nào. Mọi người sẽ tranh cãi về chi tiết thực hiện, nhưng cũng giống như có nhiều cách để lột da mèo, có nhiều cách để thực hiện một cách trừu tượng.
Câu hỏi của bạn là sử dụng hay không sử dụng và tại sao.
Như bạn không nghi ngờ gì khi nhận ra rằng bạn đã có cả hai mẫu này được xây dựng trong Entity Framework, DbContext
là UnitOfWork
và DbSet
là Repository
. Nói chung, bạn không cần phải kiểm tra đơn vị UnitOfWork
hoặc Repository
chính chúng vì chúng chỉ đơn giản là tạo điều kiện giữa các lớp của bạn và các triển khai truy cập dữ liệu cơ bản. Những gì bạn sẽ thấy mình cần phải làm, lặp đi lặp lại, là mô phỏng hai điều trừu tượng này khi đơn vị kiểm tra tính logic của các dịch vụ của bạn.
Bạn có thể giả mạo, giả mạo hoặc bất cứ điều gì bằng các thư viện bên ngoài thêm các lớp mã phụ thuộc (mà bạn không kiểm soát) giữa logic đang thực hiện kiểm tra và logic đang được kiểm tra.
Vì vậy, một điểm nhỏ là có sự trừu tượng của riêng bạn UnitOfWork
và Repository
cho phép bạn kiểm soát và linh hoạt tối đa khi bắt chước các bài kiểm tra đơn vị của bạn.
Tất cả đều rất tốt, nhưng đối với tôi, sức mạnh thực sự của những sự trừu tượng này là chúng cung cấp một cách đơn giản để áp dụng các kỹ thuật Lập trình hướng theo khía cạnh và tuân thủ các nguyên tắc SOLID .
Vì vậy, bạn có IRepository
:
public interface IRepository<T>
where T : class
{
T Add(T entity);
void Delete(T entity);
IQueryable<T> AsQueryable();
}
Và việc thực hiện nó:
public class Repository<T> : IRepository<T>
where T : class
{
private readonly IDbSet<T> _dbSet;
public Repository(PPContext context)
{
_dbSet = context.Set<T>();
}
public T Add(T entity)
{
return _dbSet.Add(entity);
}
public void Delete(T entity)
{
_dbSet.Remove(entity);
}
public IQueryable<T> AsQueryable()
{
return _dbSet.AsQueryable();
}
}
Không có gì khác thường cho đến nay nhưng bây giờ chúng tôi muốn thêm một số ghi nhật ký - dễ dàng với Trình trang trí ghi nhật ký .
public class RepositoryLoggerDecorator<T> : IRepository<T>
where T : class
{
Logger logger = LogManager.GetCurrentClassLogger();
private readonly IRepository<T> _decorated;
public RepositoryLoggerDecorator(IRepository<T> decorated)
{
_decorated = decorated;
}
public T Add(T entity)
{
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
T added = _decorated.Add(entity);
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
return added;
}
public void Delete(T entity)
{
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
_decorated.Delete(entity);
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
}
public IQueryable<T> AsQueryable()
{
return _decorated.AsQueryable();
}
}
Tất cả được thực hiện và không có thay đổi đối với mã hiện có của chúng tôi . Có rất nhiều mối quan tâm xuyên suốt khác mà chúng tôi có thể thêm vào, chẳng hạn như xử lý ngoại lệ, bộ nhớ đệm dữ liệu, xác thực dữ liệu hoặc bất cứ điều gì và trong suốt quá trình thiết kế và xây dựng của chúng tôi, điều có giá trị nhất cho phép chúng tôi thêm các tính năng đơn giản mà không thay đổi bất kỳ mã hiện có nào của chúng tôi là sự IRepository
trừu tượng của chúng ta .
Bây giờ, nhiều lần tôi đã thấy câu hỏi này trên StackOverflow - “làm cách nào để bạn làm cho Entity Framework hoạt động trong môi trường nhiều người thuê?”.
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
Nếu bạn có một sự Repository
trừu tượng thì câu trả lời là "thật dễ dàng thêm một người trang trí"
public class RepositoryTennantFilterDecorator<T> : IRepository<T>
where T : class
{
//public for Unit Test example
public readonly IRepository<T> _decorated;
public RepositoryTennantFilterDecorator(IRepository<T> decorated)
{
_decorated = decorated;
}
public T Add(T entity)
{
return _decorated.Add(entity);
}
public void Delete(T entity)
{
_decorated.Delete(entity);
}
public IQueryable<T> AsQueryable()
{
return _decorated.AsQueryable().Where(o => true);
}
}
IMO, bạn nên luôn đặt một phần trừu tượng đơn giản lên bất kỳ thành phần nào của bên thứ 3 sẽ được tham chiếu ở nhiều nơi. Từ góc độ này, ORM là ứng cử viên hoàn hảo vì nó được tham chiếu trong rất nhiều mã của chúng tôi.
Câu trả lời thường xuất hiện trong đầu khi ai đó nói "tại sao tôi nên có một sự trừu tượng (ví dụ Repository
) đối với thư viện này hoặc thư viện của bên thứ 3 đó" là "tại sao bạn lại không?"
PS Decorator cực kỳ đơn giản để áp dụng bằng IoC Container, chẳng hạn như SimpleInjector .
[TestFixture]
public class IRepositoryTesting
{
[Test]
public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
{
Container container = new Container();
container.RegisterLifetimeScope<PPContext>();
container.RegisterOpenGeneric(
typeof(IRepository<>),
typeof(Repository<>));
container.RegisterDecorator(
typeof(IRepository<>),
typeof(RepositoryLoggerDecorator<>));
container.RegisterDecorator(
typeof(IRepository<>),
typeof(RepositoryTennantFilterDecorator<>));
container.Verify();
using (container.BeginLifetimeScope())
{
var result = container.GetInstance<IRepository<Image>>();
Assert.That(
result,
Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
Assert.That(
(result as RepositoryTennantFilterDecorator<Image>)._decorated,
Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
}
}
}