2016-08-09 1 views
2

Я пытаюсь реализовать шаблон минимального общего репозитория в своем приложении. У меня есть очень маленький интерфейс для запросов и сохранения данных:Реализация минимального репозитория с использованием 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, и я беспокоюсь о возможных проблемах с производительностью или о вещах, которые могут ошибиться при использовании такого репозитория.

Примечание: Я пытаюсь сделать все это по 2 причинам:

  • Для целей тестирования, так что я могу создать макет репозитория в моих модульных тестах проектов
  • Вполне возможно, что я В будущем мне придется переключиться на другую ORM, и я бы хотел сделать этот переход максимально простым.
+0

Возможно, стоит рассмотреть вопрос о том, является ли EF правильным ORM для использования, если вы после легкого касания и имеете проблемы с производительностью.Из личного опыта я не нашел EF очень эффективным в любом из проектов, в которых я его использовал, и он немного неуклюже, сохраняя его сопоставления в соответствии с БД или Кодом (в зависимости от того, какой путь вы выбираете, код или DB сначала). Любой, кто знает, кто работает в условиях высокой пропускной способности, физически вздрагивает всякий раз, когда кто-либо упоминает EF. –

+5

Вопрос в том, что: EF * уже * реализует репозиторий ('DbSet ') и шаблоны единицы работы ('DbContext') - зачем изобретать колесо с еще одним слоем поверх этого? –

+0

@marc_s, у меня есть 2 причины :) Пожалуйста, см. Мое редактирование –

ответ

2

В первую очередь репозитории являются спорными. Есть много людей против и много используют его (или привыкли к нему?) По разным причинам. Там много статей через Интернет с бесконечными дискуссиями о плюсах и минусах. Вам решать, действительно ли вам нужен шаблон репозитория в вашем проекте - давайте не будем фокусироваться на этом, когда вы спрашиваете «как это сделать на 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(); 
    } 
} 
+0

_you не может эффективно создать транзакцию, охватывающую более одного репозитория_ - это не совсем правда. Я могу создать «TransactionScope» и обернуть его вокруг всех необходимых вызовов репозитория. По умолчанию (без области транзакции) моя реализация репозитория будет фиксировать все изменения при каждом вызове 'Save', и это поведение по умолчанию - это то, что мне нужно в большинстве случаев. –

+0

Ваш 'Save()' будет отображать все измененные сущности из всех репозиториев, используя тот же 'DbContext', независимо от того, на каком репо вы его назовете. –