Мой подход для этого заключается в использовании шаблона IRepository для переноса CRUD и упрощения внедрения зависимостей в моем приложении, вот пример того, как я это делаю:
Определите ваш контракт следующим образом: (я упрощаюпример и признание того, что все ваши таблицы имеют целочисленный идентификатор -i, означает, что это не guid, ни строка, ни что-либо еще)
public interface IGenericRepository<TEntity> where TEntity : class
{
#region ReadOnlyRepository
TEntity GetById(int id);
ICollection<TEntity> GetAll();
ICollection<TEntity> GetAll(params Expression<Func<TEntity, object>>[] includeProperties);
ICollection<TEntity> Query(Expression<Func<TEntity, bool>> expression, params Expression<Func<TEntity, object>>[] includeProperties);
PagedModel<TEntity> Query(Expression<Func<TEntity, bool>> expression, SortOptions sortOptions, PaginateOptions paginateOptions, params Expression<Func<TEntity, object>>[] includeProperties);
int Max(Expression<Func<TEntity, int>> expression);
#endregion
#region PersistRepository
bool Add(TEntity entity);
bool AddRange(IEnumerable<TEntity> items);
bool Update(TEntity entity);
bool Delete(TEntity entity);
bool DeleteById(int id);
#endregion
}
, а затем реализация:
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
#region Fields
protected DbContext CurrentContext { get; private set; }
protected DbSet<TEntity> EntitySet { get; private set; }
#endregion
#region Ctor
public GenericRepository(DbContext context)
{
CurrentContext = context;
EntitySet = CurrentContext.Set<TEntity>();
}
#endregion
#region IReadOnlyRepository Implementation
public virtual TEntity GetById(int id)
{
try
{
//use your logging method (log 4 net used here)
DomainEventSource.Log.Info(string.Format("getting entity {0} with id {1}", typeof(TEntity).Name, id));
return EntitySet.Find(id); //dbcontext manipulation
}
catch (Exception exception)
{
/// example of error handling
DomainEventSource.Log.Error(exception.Message);
var errors = new List<ExceptionDetail> { new ExceptionDetail { ErrorMessage = exception.Message } };
throw new ServerException(errors);// this is specific error formatting class you can do somthing like that to fit your needs
}
}
public virtual ICollection<TEntity> GetAll()
{
try
{
return EntitySet.ToList();
}
catch (Exception exception)
{
//... Do whatever you want
}
}
public virtual ICollection<TEntity> GetAll(params Expression<Func<TEntity, object>>[] includeProperties)
{
try
{
var query = LoadProperties(includeProperties);
return query.ToList();
}
catch (Exception exception)
{
//... Do whatever you want
}
}
public virtual ICollection<TEntity> Query(Expression<Func<TEntity, bool>> expression, params Expression<Func<TEntity, object>>[] includeProperties)
{
try
{
var query = LoadProperties(includeProperties);
return query.Where(expression).ToList();
}
catch (Exception exception)
{
//... Do whatever you want
}
}
// returning paged results for example
public PagedModel<TEntity> Query(Expression<Func<TEntity, bool>> expression,SortOptions sortOptions, PaginateOptions paginateOptions, params Expression<Func<TEntity, object>>[] includeProperties)
{
try
{
var query = EntitySet.AsQueryable().Where(expression);
var count = query.Count();
//Unfortunatly includes can't be covered with a UT and Mocked DbSets...
if (includeProperties.Length != 0)
query = includeProperties.Aggregate(query, (current, prop) => current.Include(prop));
if (paginateOptions == null || paginateOptions.PageSize <= 0 || paginateOptions.CurrentPage <= 0)
return new PagedModel<TEntity> // specific pagination model, you can define yours
{
Results = query.ToList(),
TotalNumberOfRecords = count
};
if (sortOptions != null)
query = query.OrderByPropertyOrField(sortOptions.OrderByProperty, sortOptions.IsAscending);
var skipAmount = paginateOptions.PageSize * (paginateOptions.CurrentPage - 1);
query = query.Skip(skipAmount).Take(paginateOptions.PageSize);
return new PagedModel<TEntity>
{
Results = query.ToList(),
TotalNumberOfRecords = count,
CurrentPage = paginateOptions.CurrentPage,
TotalNumberOfPages = (count / paginateOptions.PageSize) + (count % paginateOptions.PageSize == 0 ? 0 : 1)
};
}
catch (Exception exception)
{
var errors = new List<ExceptionDetail> { new ExceptionDetail { ErrorMessage = exception.Message } };
throw new ServerException(errors);
}
}
#endregion
#region IPersistRepository Repository
public bool Add(TEntity entity)
{
try
{
// you can do some extention methods here to set up creation date when inserting or createdBy etc...
EntitySet.Add(entity);
return true;
}
catch (Exception exception)
{
//DomainEventSource.Log.Failure(ex.Message);
//or
var errors = new List<ExceptionDetail> { new ExceptionDetail { ErrorMessage = exception.Message } };
throw new ServerException(errors);
}
}
public bool AddRange(IEnumerable<TEntity> items)
{
try
{
foreach (var entity in items)
{
Add(entity);
}
}
catch (Exception exception)
{
//DomainEventSource.Log.Failure(ex.Message);
var errors = new List<ExceptionDetail> { new ExceptionDetail { ErrorMessage = exception.Message } };
throw new ServerException(errors);
}
return true;
}
public bool Update(TEntity entity)
{
try
{
CurrentContext.Entry(entity).State = EntityState.Modified;
}
catch (Exception exception)
{
var errors = new List<ExceptionDetail> { new ExceptionDetail { ErrorMessage = exception.Message } };
throw new ServerException(errors);
}
return true;
}
public bool Delete(TEntity entity)
{
try
{
if (CurrentContext.Entry(entity).State == EntityState.Detached)
{
EntitySet.Attach(entity);
}
EntitySet.Remove(entity);
}
catch (Exception exception)
{
var errors = new List<ExceptionDetail> { new ExceptionDetail { ErrorMessage = exception.Message } };
throw new ServerException(errors);
}
return true;
}
public bool DeleteById(TKey id)
{
var entityToDelete = GetById(id);
return Delete(entityToDelete);
}
#endregion
#region Loading dependancies Utilities
private IQueryable<TEntity> LoadProperties(IEnumerable<Expression<Func<TEntity, object>>> includeProperties)
{
return includeProperties.Aggregate<Expression<Func<TEntity, object>>, IQueryable<TEntity>>(EntitySet, (current, includeProperty) => current.Include(includeProperty));
}
#endregion
}
Япризнав, что ваши модельные классы уже созданы и оформлены.После этого вам нужно создать свой entityRepository следующим образом: это пример управления сущностью с именем Ticket.cs
public class TicketRepository : GenericRepository<Ticket>, ITicketRepository
{
// the EntityRepository classes are made in case you have some ticket specific methods that doesn't
//have to be in generic repository
public TicketRepository(DbContext context)
: base(context)
{
}
// Add specific generic ticket methods here (not business methods-business methods will come later-)
}
После этого следует класс UnitOfWork
, который позволяет нам унифицировать запись в контексте базы данных.и предоставляет нам экземпляр репозиториев по требованию с использованием внедрения зависимостей
public class UnitOfwork : IUnitOfWork
{
#region Fields
protected DbContext CurrentContext { get; private set; }
private ITicketRepository _tickets;
#endregion
#region ctor
public UnitOfwork(DbContext context)
{
CurrentContext = context;
}
#endregion
#region UnitOfWorkBaseImplementation
public void Commit()
{
try
{
CurrentContext.SaveChanges();
}
catch (Exception e)
{
/// catch
}
}
public void Rollback()
{
foreach (var entry in CurrentContext.ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Modified:
case EntityState.Deleted:
entry.State = EntityState.Modified; //Revert changes made to deleted entity.
entry.State = EntityState.Unchanged;
break;
case EntityState.Added:
entry.State = EntityState.Detached;
break;
case EntityState.Detached:
break;
case EntityState.Unchanged:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
#region complete RollBack()
private void RejectScalarChanges()
{
foreach (var entry in CurrentContext.ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Modified:
case EntityState.Deleted:
entry.State = EntityState.Modified; //Revert changes made to deleted entity.
entry.State = EntityState.Unchanged;
break;
case EntityState.Added:
entry.State = EntityState.Detached;
break;
case EntityState.Detached:
break;
case EntityState.Unchanged:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
private void RejectNavigationChanges()
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
var deletedRelationships = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).Where(e => e.IsRelationship && !this.RelationshipContainsKeyEntry(e));
var addedRelationships = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Where(e => e.IsRelationship);
foreach (var relationship in addedRelationships)
relationship.Delete();
foreach (var relationship in deletedRelationships)
relationship.ChangeState(EntityState.Unchanged);
}
private bool RelationshipContainsKeyEntry(System.Data.Entity.Core.Objects.ObjectStateEntry stateEntry)
{
//prevent exception: "Cannot change state of a relationship if one of the ends of the relationship is a KeyEntry"
//I haven't been able to find the conditions under which this happens, but it sometimes does.
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
var keys = new[] { stateEntry.OriginalValues[0], stateEntry.OriginalValues[1] };
return keys.Any(key => objectContext.ObjectStateManager.GetObjectStateEntry(key).Entity == null);
}
#endregion
public void Dispose()
{
if (CurrentContext != null)
{
CurrentContext.Dispose();
}
}
#endregion
#region properties
public ITicketRepository Tickets
{
get { return _tickets ?? (_tickets = new TicketRepository(CurrentContext)); }
}
#endregion
}
Теперь для последней части мы переходим на наш уровень бизнес-сервисов и создаем класс ServiceBase, который будет реализован всеми бизнес-сервисами
public class ServiceBase : IServiceBase
{
private bool _disposed;
#region IServiceBase Implementation
[Dependency]
public IUnitOfWork UnitOfWork { protected get; set; }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
var disposableUow = UnitOfWork as IDisposable;
if (disposableUow != null)
disposableUow.Dispose();
}
_disposed = true;
}
#endregion
}
и, наконец, один пример класса бизнес-обслуживания и того, как использовать CRUD и играть с вашими бизнес-правилами (я использую внедрение свойств, которое не лучше всего, поэтому я предлагаю изменить его и вместо этого использовать внедрение конструктора)
public class TicketService : ServiceBase, ITicketService
{
#region fields
private IUserService _userService;
private IAuthorizationService _authorizationService;
#endregion
#region Properties
[Dependency]
public IAuthorizationService AuthorizationService
{
set { _authorizationService = value; }
}
[Dependency]
public IUserService UserService
{
set { _userService = value; }
}
public List<ExceptionDetail> Errors { get; set; }
#endregion
#region Ctor
public TicketService()
{
Errors = new List<ExceptionDetail>();
}
#endregion
#region IServiceBase Implementation
/// <summary>
/// desc
/// </summary>
/// <returns>array of TicketAnomalie</returns>
public ICollection<Ticket> GetAll()
{
return UnitOfWork.Tickets.GetAll();
}
/// <summary>
/// desc
/// </summary>
/// <param name="id"></param>
/// <returns>TicketAnomalie</returns>
public Ticket GetTicketById(int id)
{
return UnitOfWork.Tickets.GetById(id);
}
/// <summary>
/// description here
/// </summary>
/// <returns>Collection of Ticket</returns>
public ICollection<Ticket> GetAllTicketsWithDependencies()
{
return UnitOfWork.Tickets.Query(tick => true, tick => tick.Anomalies);
}
/// <summary>
/// description here
/// </summary>
/// <param name="id"></param>
/// <returns>Ticket</returns>
public Ticket GetTicketWithDependencies(int id)
{
return UnitOfWork.Tickets.Query(tick => tick.Id == id, tick => tick.Anomalies).SingleOrDefault();
}
/// <summary>
/// Add new ticket to DB
/// </summary>
/// <param name="anomalieId"></param>
/// <returns>Boolean</returns>
public bool Add(int anomalieId)
{
var anomalie = UnitOfWork.Anomalies.Query(ano => ano.Id.Equals(anomalieId), ano => ano.Tickets).FirstOrDefault();
var currentUser = WacContext.Current;
var superv = _userService.GetSupervisorUserProfile();
var sup = superv.FirstOrDefault();
if (anomalie != null)
{
var anomalies = new List<Anomalie>();
var anom = UnitOfWork.Anomalies.GetById(anomalieId);
anomalies.Add(anom);
if (anomalie.Tickets.Count == 0 && sup != null)
{
var ticket = new Ticket
{
User = sup.Id,
CreatedBy = currentUser.GivenName,
Anomalies = anomalies,
Path = UnitOfWork.SearchCriterias.GetById(anom.ParcoursId),
ContactPoint = UnitOfWork.ContactPoints.GetById(anom.ContactPointId)
};
UnitOfWork.Tickets.Add(ticket);
UnitOfWork.Commit();
}
}
else
{
Errors.Add(AnomaliesExceptions.AnoNullException);
}
if (Errors.Count != 0) throw new BusinessException(Errors);
return true;
}
public bool Update(Ticket ticket)
{
if (ticket == null)
{
Errors.Add(AnomaliesExceptions.AnoNullException);
}
else
if (!Exists(ticket.Id))
{
Errors.Add(AnomaliesExceptions.AnoToUpdateNotExistException);
}
if (Errors.Count != 0) throw new BusinessException(Errors);
UnitOfWork.Tickets.Update(ticket);
UnitOfWork.Commit();
return true;
}
public bool Exists(int ticketId)
{
var operationDbEntity =
UnitOfWork.Tickets.Query(t => t.Id.Equals(ticketId)).ToList();
return operationDbEntity.Count != 0;
}
#endregion
#region Business Implementation
//play with your buiness :)
#endregion
}
Наконец, я предлагаю вам повторить это, используя асинхронные методы (async await, поскольку это позволяет лучше управлять пулами сервисов на веб-сервере)
Обратите внимание, что это мой собственный способуправления моим CRUD с EF и Unity.Вы можете найти множество других реализаций, которые могут вдохновить вас.
Надеюсь, это поможет,