使用通用CRUD函数处理复杂实体(关系)的最佳方法

时间:2018-11-15 07:44:51

标签: entity-framework generics repository-pattern crud

我曾尝试使用此通用函数来插入-更新实体,但我一直以为也许我这样做完全不对,所以我想征询您的意见/建议。

这些是我的“插入和更新”功能:

 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等,但是我对接口没有太多经验,或者我似乎不太了解所用的内容,因此我宁愿在完全了解它之前不使用它。

1 个答案:

答案 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的方式。您可以找到许多其他可以启发您的实现方式。

希望这会有所帮助,