Best way to handle complex entities (relational) with Generic CRUD functions












0














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)
{
// ...
}
}
}


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.










share|improve this question





























    0














    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)
    {
    // ...
    }
    }
    }


    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.










    share|improve this question



























      0












      0








      0







      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)
      {
      // ...
      }
      }
      }


      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.










      share|improve this question















      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)
      {
      // ...
      }
      }
      }


      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.







      entity-framework generics repository-pattern crud






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 18 '18 at 19:12









      marc_s

      571k12811031252




      571k12811031252










      asked Nov 15 '18 at 7:44









      DimitriDimitri

      163212




      163212
























          1 Answer
          1






          active

          oldest

          votes


















          1














          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:



          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
          }


          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
          }


          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-)
          }


          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
          }


          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
          }


          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
          }


          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)



          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.



          Hope this helps,






          share|improve this answer





















          • I have no doubt that this is a very decent way of handling a certain structure but is there a simpler way because I am kind of lost with everything you posted, its above my knowledge I am afraid ... (Thank you for provided answer and example! Its really appreciated)
            – Dimitri
            Nov 15 '18 at 12:36











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53314580%2fbest-way-to-handle-complex-entities-relational-with-generic-crud-functions%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          1














          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:



          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
          }


          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
          }


          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-)
          }


          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
          }


          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
          }


          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
          }


          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)



          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.



          Hope this helps,






          share|improve this answer





















          • I have no doubt that this is a very decent way of handling a certain structure but is there a simpler way because I am kind of lost with everything you posted, its above my knowledge I am afraid ... (Thank you for provided answer and example! Its really appreciated)
            – Dimitri
            Nov 15 '18 at 12:36
















          1














          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:



          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
          }


          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
          }


          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-)
          }


          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
          }


          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
          }


          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
          }


          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)



          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.



          Hope this helps,






          share|improve this answer





















          • I have no doubt that this is a very decent way of handling a certain structure but is there a simpler way because I am kind of lost with everything you posted, its above my knowledge I am afraid ... (Thank you for provided answer and example! Its really appreciated)
            – Dimitri
            Nov 15 '18 at 12:36














          1












          1








          1






          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:



          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
          }


          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
          }


          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-)
          }


          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
          }


          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
          }


          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
          }


          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)



          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.



          Hope this helps,






          share|improve this answer












          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:



          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
          }


          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
          }


          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-)
          }


          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
          }


          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
          }


          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
          }


          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)



          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.



          Hope this helps,







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 15 '18 at 9:54









          Karoui HaithemKaroui Haithem

          555421




          555421












          • I have no doubt that this is a very decent way of handling a certain structure but is there a simpler way because I am kind of lost with everything you posted, its above my knowledge I am afraid ... (Thank you for provided answer and example! Its really appreciated)
            – Dimitri
            Nov 15 '18 at 12:36


















          • I have no doubt that this is a very decent way of handling a certain structure but is there a simpler way because I am kind of lost with everything you posted, its above my knowledge I am afraid ... (Thank you for provided answer and example! Its really appreciated)
            – Dimitri
            Nov 15 '18 at 12:36
















          I have no doubt that this is a very decent way of handling a certain structure but is there a simpler way because I am kind of lost with everything you posted, its above my knowledge I am afraid ... (Thank you for provided answer and example! Its really appreciated)
          – Dimitri
          Nov 15 '18 at 12:36




          I have no doubt that this is a very decent way of handling a certain structure but is there a simpler way because I am kind of lost with everything you posted, its above my knowledge I am afraid ... (Thank you for provided answer and example! Its really appreciated)
          – Dimitri
          Nov 15 '18 at 12:36


















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53314580%2fbest-way-to-handle-complex-entities-relational-with-generic-crud-functions%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Guess what letter conforming each word

          Port of Spain

          Run scheduled task as local user group (not BUILTIN)