Minimal Repository implementation using Entity Framework

大城市里の小女人 提交于 2019-12-24 00:23:19

问题


I'm trying to implement a minimal generic repository pattern in my application. I have a really small interface for querying and saving data:

public interface IRepository
{
    IQueryable<TEntity> Query<TEntity>() 
        where TEntity: BaseEntity;

    void Save<TEntity>(TEntity entity) 
        where TEntity : BaseEntity;
}

BaseEntity is a base class for all the objects I will store in my repository:

public abstract class BaseEntity
{
    public Guid Id { get; set; }    
    public DateTime CreatedDate { get; set; }
    public DateTime UpdatedDate { get; set; }
}

I was trying to find a working implementation of such a simple repository using Entity Framework, but it was surprisingly hard to find (people are using UnitOfWork and other things that make the implementation more complex than I want).

So I created the absolutely minimal implementation I could come up with:

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...
}

Now, my question is if such implementation is correct. I'm asking because I'm new to Entity Framework and I'm worried about possible performance issues or things that could possibly go wrong while using such repository.

Note: I'm trying to do all this for 2 reasons:

  • For testing purposes, so that I can create a mock of the repository in my unit tests projects
  • It's possible that I'll have to switch to another ORM in the future, and I'd like to make this transition as easy as possible.

回答1:


First of all repositories are controversial. There are lots of people strongly against and lots using it (or used to it?) for various reasons. There many articles over the internet with endless discussions about pros and cons. It's up to you to decide if you actually need the repository pattern in your project - let's not focus on that as you asked "how to do that in C#?" not "should i do that?".

Your repository implementation extends DbContext. This means you cannot effectively create a transaction spanning more than one repository (more than one entity type) because each repository will have it's own DbContext (as it IS the context). Under the hood DbContext tracks changes made to the entities. If you have more than one context and try to save both at the same time they won't know about each other. This leaves us with a problem - if the first call of SaveChanges() succeeds and second fails how to rollback the first one? It was already saved?. That's where the unit of work appears.

So first you need a repository interface - acting as a collection of entities:

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);
}

And unit of work:

public interface IUnitOfWork : IDisposable
{
    // Commit all the changes 
    void Complete();

    // Concrete implementation -> IRepository<Foo>
    // Add all your repositories here:
    IFooRepository Foos {get;}
}

Base classes could look as follows:

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);
        }
    }

}

And Unit of work implementation:

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();
    }
}


来源:https://stackoverflow.com/questions/38853944/minimal-repository-implementation-using-entity-framework

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!