Лучший способ обрабатывать сложные сущности (реляционные) с помощью общих функций CRUD - PullRequest
0 голосов
/ 15 ноября 2018

Я пытался использовать эти общие функции для вставки-обновления сущностей, но я всегда думал, что, возможно, я делаю это совершенно неправильно, поэтому я хотел бы получить ваши мнения / предложения.

Это мои функции вставки и обновления:

 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 ?

Я знаю о шаблоне репозитория и так далее, но у меня нет большого опыта работы с интерфейсами, или я не совсем понимаю, что использовалось, поэтому я предпочитаю не использовать его, пока не получу полностью Это.

1 Ответ

0 голосов
/ 15 ноября 2018

Мой подход для этого заключается в использовании шаблона 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.Вы можете найти множество других реализаций, которые могут вдохновить вас.

Надеюсь, это поможет,

...