使用通用CRUD函数处理复杂实体(关系)的最佳方法 [英] Best way to handle complex entities (relational) with Generic CRUD functions

查看:111
本文介绍了使用通用CRUD函数处理复杂实体(关系)的最佳方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have tried using this generic functions to insert-update Entities but I always thought that maybe I am doing this totally wrong so therefore I would like to have your opinions/suggestions.

这些是我的插入&更新功能:

These are my Insert & Update functions:

 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动作的最简单,最易管理的方式感到非常好奇。

I have learned that i can use ctx.Entry(item).State = EntityState.Modified; and I have seen so many ways of inserting-updating entities that I am very curious on what is the easiest most manageable way of performing CRUD actions ?

我了解存储库模式等,但是我对接口没有太多经验,或者我似乎不太了解所用的内容,因此我宁愿在完全使用之前不使用它得到它。

I know about the repository pattern and so on but i don't have much experience with interfaces or I don't seem to fully understand whats used so I prefer not to use it till I fully get it.

推荐答案

我的方法是使用IRepository模式包装CRUD,并使应用程序中的依赖项注入更加容易,这里是一个示例关于我的操作方法:

my approach for that is to use IRepository pattern to wrap CRUD and to make dependencies injection easier in my application, here an example on how i do it:

定义您的合同,如下所示:
(我正在简化示例,并承认所有表都具有整数id -i表示

Define your contract like following: (i am simplifying the example and admitting that all your tables have an integer id -i mean it is not guid or string or whatever- )

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
}

然后执行: / p>

and then the implementation:

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

I am admitting that your model classes are already created and decorated. After this , you need to create your entityRepository like following : this is an example of managing entity called 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 类,它使我们可以统一进入数据库上下文,并使用依赖项注入为我们提供按需存储库的实例

After this comes the UnitOfWork class which allows us to unify entry to the database context and provides us an instance of repositories on demand using dependency injection

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类,该类将由所有业务服务实现。

Now for the last part we move to our business service layer and make a ServiceBase class which will be implemented by all business services

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并使用业务规则(我使用属性注入不是最好的方法,因此我建议将其更改并改用构造函数注入)

and finally one example of business service class and how to use your CRUD and play with your business rules (i am using properties injection which is not the best to do so i suggest to change it and use constructor injection instead)

    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
}

最后,
i建议您使用异步方法重做此操作(异步等待,因为它可以更好地管理Web服务器中的服务池)

Finally, i suggest that you redo this using asynchronous methods (async await since it allows a better management of service pools in the web server)

请注意,这是我自己使用EF管理CRUD的方式和Unity。您可以找到很多可以激发您灵感的其他实现方式。

Note that this is my own way of managing my CRUD with EF and Unity. you can find a lot of other implementations that can inspire you.

希望这会有所帮助,

这篇关于使用通用CRUD函数处理复杂实体(关系)的最佳方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆