我曾尝试使用此通用函数来插入-更新实体,但我一直以为也许我这样做完全不对,所以我想征询您的意见/建议。
这些是我的“插入和更新”功能:
public static bool Insert<T>(T item) where T : class
{
using (ApplicationDbContext ctx = new ApplicationDbContext())
{
try
{
ctx.Set<T>().Add(item);
ctx.SaveChanges();
return true;
}
catch (Exception ex)
{
// ...
}
}
}
public static bool Update<T>(T item) where T : class
{
using (ApplicationDbContext ctx = new ApplicationDbContext())
{
try
{
Type itemType = item.GetType();
// switch statement to perform actions according which type we are working on
ctx.SaveChanges();
return true;
}
catch (Exception ex)
{
// ...
}
}
}
我了解到我可以使用ctx.Entry(item).State = EntityState.Modified;
,并且看到了许多插入和更新实体的方法,我对执行CRUD动作最容易管理的最简单方式感到好奇。 >?
我了解repository pattern等,但是我对接口没有太多经验,或者我似乎不太了解所用的内容,因此我宁愿在完全了解它之前不使用它。
答案 0 :(得分:1)
为此,我的方法是使用IRepository模式包装CRUD,并使我的应用程序中的依赖项注入更加容易,这是一个有关如何执行此操作的示例:
按如下所示定义您的合同: (我正在简化示例,并承认所有表都具有一个整数id -i表示它不是guid或string或其他东西-)
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
}
最后, 我建议您使用异步方法重做一次(异步等待,因为它可以更好地管理Web服务器中的服务池)
请注意,这是我自己使用EF和Unity管理CRUD的方式。您可以找到许多其他可以启发您的实现方式。
希望这会有所帮助,