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?