Реализация минимального репозитория с использованием 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();
    }
}
Другие вопросы по тегам