Реализация минимального репозитория с использованием Entity Framework
Я пытаюсь реализовать минимальный общий шаблон репозитория в моем приложении. У меня действительно маленький интерфейс для запросов и сохранения данных:
public interface IRepository
{
IQueryable<TEntity> Query<TEntity>()
where TEntity: BaseEntity;
void Save<TEntity>(TEntity entity)
where TEntity : BaseEntity;
}
BaseEntity
базовый класс для всех объектов, которые я буду хранить в своем хранилище:
public abstract class BaseEntity
{
public Guid Id { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
}
Я пытался найти работающую реализацию такого простого репозитория с использованием Entity Framework, но его было на удивление трудно найти (люди используют UnitOfWork
и другие вещи, которые делают реализацию более сложной, чем я хочу).
Поэтому я создал абсолютно минимальную реализацию, которую смог придумать:
public class EfRepository : DbContext, IRepository
{
public IQueryable<TEntity> Query<TEntity>() where TEntity : BaseEntity
{
return this.Set<TEntity>();
}
public void Save<TEntity>(TEntity entity) where TEntity : BaseEntity
{
if (entity.Id == default(Guid))
{
entity.Id = Guid.NewGuid();
this.Set<TEntity>().Add(entity);
}
else
{
this.Entry(entity).State = EntityState.Modified;
}
this.SaveChanges();
}
public DbSet<User> Users { get; set; } // User is a subclass of BaseEntity
//Other DbSet's...
}
Теперь мой вопрос, является ли такая реализация правильной. Я спрашиваю, потому что я новичок в Entity Framework и беспокоюсь о возможных проблемах с производительностью или вещах, которые могут пойти не так при использовании такого хранилища.
Примечание: я пытаюсь сделать все это по двум причинам:
- В целях тестирования, чтобы я мог создать макет хранилища в своих проектах модульных тестов
- Вполне возможно, что в будущем мне придется перейти на другой ORM, и я бы хотел сделать этот переход максимально простым.
1 ответ
Прежде всего, хранилища противоречивы. Многие люди категорически против, и многие используют его (или привыкли к нему?) По разным причинам. В интернете много статей с бесконечными дискуссиями о плюсах и минусах. Вам решать, нужен ли вам шаблон репозитория в вашем проекте - давайте не будем зацикливаться на этом, когда вы спросили: "Как это сделать в C#?" не "я должен сделать это?".
Ваша реализация репозитория расширяется DbContext
, Это означает, что вы не можете эффективно создать транзакцию, охватывающую более одного репозитория (более одного типа сущности), потому что у каждого репозитория будет свой DbContext
(как это контекст). Под капотом DbContext
отслеживает изменения, внесенные в объекты. Если у вас более одного контекста и вы пытаетесь сохранить оба одновременно, они не будут знать друг о друге. Это оставляет нас с проблемой - если первый вызов SaveChanges()
успешно, а второй не получается, как откатить первый? Уже был сохранен? Вот где появляется единица работы.
Итак, сначала вам нужен интерфейс хранилища - действующий как набор сущностей:
public interface IRepository<TEntity>
{
TEntity Get(Expression<Func<TEntity, bool>> predicate);
IEnumerable<TEntity> GetAll();
IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> predicate);
void Add(TEntity entity);
void AddAll(IEnumerable<TEntity> entities);
void Remove(TEntity entity);
void RemoveAll(IEnumerable<TEntity> entities);
}
И единица работы:
public interface IUnitOfWork : IDisposable
{
// Commit all the changes
void Complete();
// Concrete implementation -> IRepository<Foo>
// Add all your repositories here:
IFooRepository Foos {get;}
}
Базовые классы могут выглядеть следующим образом:
public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected DbContext Context { get; private set; }
public BaseRepository(DbContext dbContext)
{
Context = dbContext;
}
public virtual TEntity Get(Expression<Func<TEntity, bool>> predicate)
{
return Context.Set<TEntity>().Where(predicate).FirstOrDefault();
}
public virtual IEnumerable<TEntity> GetAll()
{
return Context.Set<TEntity>().ToList();
}
public virtual IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> predicate)
{
return Context.Set<TEntity>().Where(predicate).ToList();
}
public void Add(TEntity entity)
{
var entry = Context.Entry(entity);
if(entry.State == EntityState.Detached)
{
Context.Set<TEntity>().Add(entity);
}
else
{
entry.State = EntityState.Modified;
}
}
public void AddAll(IEnumerable<TEntity> entities)
{
foreach(var entity in entities)
{
Add(entity);
}
}
public void Remove(TEntity entity)
{
var entry = Context.Entry(entity);
if (entry.State == EntityState.Detached)
{
Context.Set<TEntity>().Attach(entity);
}
Context.Entry<TEntity>(entity).State = EntityState.Deleted;
}
public void RemoveAll(IEnumerable<TEntity> entities)
{
foreach (var entity in entities)
{
Remove(entity);
}
}
}
И Единица выполнения работ:
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _dbContext;
private IFooRepository _fooRepo;
public UnitOfWork(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
// Each repo will share the db context:
_fooRepo = new FooRepository(_dbContext);
}
public IFooRepository Foos
{
get
{
return _fooRepo;
}
}
public void Complete()
{
_dbContext.SaveChanges();
}
public void Dispose()
{
_dbContext.Dispose();
}
}