I am not very experienced in Entity Framework, I previously used ADO.Net and Dapper for data persistence. For now, I only read Microsoft docs and watched a few video tutorials. I tried to stick to well accepted advice and have built a generic repository infrastructure with the help of from here and there.

IEntity

public interface IEntity
{
    object Id { get; set; }

    string CreatedBy { get; set; }

    DateTime CreatedOn { get; set; }

    string ModifiedBy { get; set; }

    DateTime? ModifiedOn { get; set; }
}

public interface IEntity<T> : IEntity
{
    new T Id { get; set; }
}

BaseEntity

public abstract class BaseEntity<T> : IEntity<T>
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public T Id { get; set; }

    object IEntity.Id { get { return Id; } set { } }

    public string CreatedBy { get; set; }

    private DateTime? createdOn;
    [DataType(DataType.DateTime)]
    public DateTime CreatedOn
    {
        get { return createdOn ?? DateTime.UtcNow; }
        set { createdOn = value; }
    }

    public string ModifiedBy { get; set; }

    [DataType(DataType.DateTime)]
    public DateTime? ModifiedOn { get; set; }
}

User Entity

public class User : BaseEntity<int>
{
    public string FullName { get; set; }
    public string Email { get; set; }
    public UserType UserType { get; set; }
    public string Password { get; set; }

    public virtual ICollection<Comment> Comments { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
}

IRepository

public interface IRepository<TEntity> where TEntity: IEntity
{
    IEnumerable<TEntity> GetAll(Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
                      string includeProperties = null,
                      int? skip = null,
                      int? take = null);

    TEntity GetFirst(Expression<Func<TEntity, bool>> filter = null,
                     Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
                     string includeProperties = null);

    TEntity GetById(object id);

    TEntity GetOne(Expression<Func<TEntity, bool>> filter = null,
                   string includeProperties = null);

    void Create(TEntity entity, string createdBy = null);

    void Update(TEntity entity, string modifiedBy = null);

    void Delete(TEntity entity);

    void Delete(object id);

    void Save();
}

EFRepository

public class EFRepository<TEntity> : IRepository<TEntity>
    where TEntity : class, IEntity
{
    private DbContext _context;

    public EFRepository(BlogContext context)
    {
        _context = context;
    }

    protected virtual IEnumerable<TEntity> GetQueryable(
    Expression<Func<TEntity, bool>> filter = null,
    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
    string includeProperties = null,
    int? skip = null,
    int? take = null)
    {
        includeProperties = includeProperties ?? string.Empty;
        IQueryable<TEntity> query = _context.Set<TEntity>();

        if (filter != null)
        {
            query = query.Where(filter);
        }

        foreach (var includeProperty in includeProperties.Split
            (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }

        if (orderBy != null)
        {
            query = orderBy(query);
        }

        if (skip.HasValue)
        {
            query = query.Skip(skip.Value);
        }

        if (take.HasValue)
        {
            query = query.Take(take.Value);
        }

        return query.AsEnumerable();
    }

    public IEnumerable<TEntity> GetAll(Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = null, int? skip = null, int? take = null)
    {
        return GetQueryable(orderBy: orderBy, includeProperties: includeProperties, skip: skip, take: take);
    }

    public TEntity GetById(object id)
    {
        return _context.Set<TEntity>().Find(id);
    }

    public TEntity GetFirst(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = null)
    {
        return GetQueryable(filter: filter, orderBy: orderBy, includeProperties: includeProperties).FirstOrDefault();
    }

    public TEntity GetOne(Expression<Func<TEntity, bool>> filter = null, string includeProperties = null)
    {
        return GetQueryable(filter: filter, includeProperties: includeProperties).SingleOrDefault();
    }

    public void Save()
    {
        try
        {
            _context.SaveChanges();
        }
        catch (Exception e)
        {
        }
    }

    public void Create(TEntity entity, string createdBy = null)
    {
        entity.CreatedOn = DateTime.UtcNow;
        entity.CreatedBy = createdBy;
        _context.Add<TEntity>(entity);
    }

    public void Update(TEntity entity, string modifiedBy = null)
    {
        entity.CreatedOn = DateTime.UtcNow;
        entity.CreatedBy = modifiedBy;
        _context.Set<TEntity>().Attach(entity);
        _context.Entry(entity).State = EntityState.Modified;
    }

    public void Delete(TEntity entity)
    {
        var dbSet = _context.Set<TEntity>();

        if (_context.Entry(entity).State == EntityState.Detached)
            dbSet.Attach(entity);

        dbSet.Remove(entity);
    }

    public void Delete(object id)
    {
        TEntity entity = _context.Set<TEntity>().Find(id);
        Delete(entity);
    }
}

IService

public interface IService
{
    IRepository<T> GetRepository<T>(DbContext context) where T : class, IEntity;
}

BaseService

public abstract class BaseService : IService
{
    public BaseService(BlogContext context, ILogger<BaseService> logger)
    {
        _context = context;
        _logger = logger;
    }

    protected DbContext _context { get; set; }

    protected readonly ILogger<BaseService> _logger;

    public IRepository<T> GetRepository<T>(DbContext context) where T : class, IEntity
    {
        return new EFRepository<T>((BlogContext)context);
    }
    public IRepository<T> GetRepository<T>() where T : class, IEntity
    {
        return GetRepository<T>(_context);
    }
}

IUserService

public interface IAuthorService
{
    bool DeleteAuthorWithEntireStuff(int userId);
}

UserService

public class UserService : BaseService, IAuthorService
{
    public UserService(BlogContext context, ILogger<UserService> logger) : base(context, logger) { }

    public bool DeleteAuthorWithEntireStuff(int id)
    {
        try
        {
            var userRepo = GetRepository<User>();
            var commentRepo = GetRepository<Comment>();
            var postRepo = GetRepository<Post>();

            var user = userRepo.GetAll().Where(u => u.Id == id).FirstOrDefault();

            if (user != null)
            {
                var commentList = commentRepo.GetAll().Where(c => c.UserId == id).ToList();
                var postList = postRepo.GetAll().Where(p => p.AuthorId == id).ToList();

                foreach (var item in commentList)
                    commentRepo.Delete(item);

                foreach (var item in postList)
                    postRepo.Delete(item);

                userRepo.Delete(user);

                _context.SaveChanges();
            }

            return true;
        }
        catch (Exception)
        {
            throw;
        }
    }
}

And finally controller

public class UserController : Controller
{
    private readonly IRepository<User> _repository;
    private readonly IUserService _authorService;

    public UserController(IRepository<User> repository, IUserService authorService)
    {
        _repository = repository;
        _authorService = authorService;
    }

    [HttpPost]
    public IActionResult Create([FromBody]User model)
    {
        try
        {
            _repository.Create(model);
            _repository.Save();
            return Ok(model);
        }
        catch(Exception ex)
        {
            return BadRequest(ex);
        }

    }
    [HttpPost]
    [Route("DeletUserEntirely")]
    public IActionResult DeleteUserEntirely([FromBody]User model)
    {
        try
        {
            if (_authorService.DeleteUserEntirely(model.Id))
                return Ok();
            else
                return BadRequest();
        }
        catch (Exception ex)
        {
            return BadRequest(ex);
        }

    }
}

As you see in the UserService code when I need to run multiple commands in one transaction, I make use of the ef's unit of work (_context.SaveChanges()).

I did not create any extra unit of work classes as suggested at few places or wrap my repos in transaction scope. As discussed here, some people defend unit of work implementations for transaction integrity that I do not still get it why while ef implementing uow pattern already.

I haven't tested above code so much but till now it seems ok.

Btw, I used Chris Pratt's Generic Repository approach. Any problems with this approach, especially with the perspective of transactions in generic repository use entity framework?

  • 2
    The repository-pattern is like a virus. It's spreading everywhere and causes only harm ;-) – t3chb0t Sep 14 at 13:47
  • You should really avoid the repository approach, and instead look at something like mehdi.me/ambient-dbcontext-in-ef6 . – BCdotWEB Nov 13 at 15:38

For whom interested in this subject, my searching on this has ended up with the clean architecture repository and its implemented sample reference project eshoponweb.

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.