Search code examples
angularjsentity-framework-4breezerepository-patternhottowel

where doese breeze fits into ntier architecture


i am Trying to fit in breezeJS with my existing architecture. I have a structure like

  1. html/JS/Angular :: based view using hot-towel angular.
  2. web api controllers :: whom the view calls.
  3. Services layer :: that is being called from Web api. Any business logic goes here.
  4. Unit of Work :: And (if) business logic requires to talk to data base for CRUDs it calls UOW.
  5. Repository Pattern :: UOW is actually wrapping repositories. and repositores in turn talking to DbContexts.

Uptill now i was able to conver normal repositories implementation into the one using

public EFContextProvider<MyContext> DbContext { get; set; }

instead of just DbContext and i am also exposing MetaData using a string property with in UOW and IQueryables are returned using DbContext.Context.SomeEntity

Question 1 : Am i on right track ?? Question 2 : Most of the breeze examples are suggesting one SaveChanges method that give you all the entities that were changed and it will persist it at once. What if i want to trigger some business logic before Add,Update and Delete. i want to call me AddSomething service method and want to have a particular type of entity being sent to AddSomething and run some business logic before persistence. How can i put it together.

my code looksl ike

  [BreezeController]//This is the controller 
public class BreezeController : ApiController
{
    private readonly ISomeService someService;
    public BreezeController(ISomeService someService)
    {
        this.someService = someService;
    }
    // ~/breeze/todos/Metadata 
    [HttpGet]
    public string Metadata()
    {
        return someService.MetaData();
    }

    // ~/breeze/todos/Todos
    // ~/breeze/todos/Todos?$filter=IsArchived eq false&$orderby=CreatedAt 
    [HttpGet]
    public IQueryable<Node> Nodes()
    {
        return nodesService.GetAllNodes().AsQueryable();
    }

    // ~/breeze/todos/SaveChanges
    //[HttpPost]
    //public SaveResult SaveChanges(JObject saveBundle)
    //{
    //    return _contextProvider.SaveChanges(saveBundle);
    //}

Below is the service

 public class SomeService : BaseService, ISomeService
{
    private readonly IUow Uow;

    public SomeService(IUow Uow)
        : base(Uow)
    {
        this.Uow = Uow;
    }
    public IEnumerable<Something> GetAllNodes()
    {
        return Uow.Somethings.GetAll();
    }
}

every service can expose one property through base. that is actually the meta data

public class BaseService : IBaseService
{
    private readonly IUow Uow;
    public BaseService(IUow Uow)
    {
        this.Uow = Uow;
    }
    public string MetaData()
    {
        return Uow.MetaData;
    }
}

and the my UOW looks like

    public class VNUow : IUow, IDisposable
{
    public VNUow(IRepositoryProvider repositoryProvider)
    {
        CreateDbContext();

        repositoryProvider.DbContext = DbContext;
        RepositoryProvider = repositoryProvider;       
    }

    // Code Camper repositories

    public IRepository<Something> NodeGroup { get { return GetStandardRepo<Something>(); } }
   } }
    public IRepository<Node> Nodes { get { return GetStandardRepo<Node>(); } }
    /// <summary>
    /// Save pending changes to the database
    /// </summary>
    public void Commit()
    {
        //System.Diagnostics.Debug.WriteLine("Committed");
        DbContext.Context.SaveChanges();
    }
    public string MetaData   // the Name property
    {
        get
        {
            return DbContext.Metadata();
        }
    }
    protected void CreateDbContext()
    {
    //    DbContext = new VNContext();

        DbContext = new EFContextProvider<VNContext>();
        // Load navigation properties always if it is true
        DbContext.Context.Configuration.LazyLoadingEnabled = false;


        // Do NOT enable proxied entities, else serialization fails
        DbContext.Context.Configuration.ProxyCreationEnabled = true;

        // Because Web API will perform validation, we don't need/want EF to do so
        DbContext.Context.Configuration.ValidateOnSaveEnabled = false;

        //DbContext.Configuration.AutoDetectChangesEnabled = false;
        // We won't use this performance tweak because we don't need 
        // the extra performance and, when autodetect is false,
        // we'd have to be careful. We're not being that careful.
    }

    protected IRepositoryProvider RepositoryProvider { get; set; }

    private IRepository<T> GetStandardRepo<T>() where T : class
    {
        return RepositoryProvider.GetRepositoryForEntityType<T>();
    }
    private T GetRepo<T>() where T : class
    {
        return RepositoryProvider.GetRepository<T>();
    }

    private EFContextProvider<VNContext> DbContext { get; set; }

    #region IDisposable

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (DbContext != null)
            {
                DbContext.Context.Dispose();
            }
        }
    }

    #endregion
}

in the end Repository Implementaion looks like

   public class EFRepository<T> : IRepository<T> where T : class
{
    public EFRepository(EFContextProvider<VNContext> dbContext)
    {
        if (dbContext == null)
            throw new ArgumentNullException("dbContext");
        DbContext = dbContext;
        DbSet = DbContext.Context.Set<T>();
    }

    protected EFContextProvider<VNContext> DbContext { get; set; }

    protected DbSet<T> DbSet { get; set; }

    public virtual IQueryable<T> GetAll()
    {
        return DbSet;
    }
    public virtual IQueryable<T> GetAllEagerLoad(params Expression<Func<T, object>>[] children)
    {
        children.ToList().ForEach(x => DbSet.Include(x).Load());
        return DbSet;
    }
    public virtual IQueryable<T> GetAllEagerLoadSelective(string[] children)
    {
        foreach (var item in children)
        {
            DbSet.Include(item);
        }
        return DbSet;
    }
    public virtual IQueryable<T> GetAllLazyLoad()
    {
        return DbSet;
    }
    public virtual T GetById(int id)
    {
        //return DbSet.FirstOrDefault(PredicateBuilder.GetByIdPredicate<T>(id));

        return DbSet.Find(id);
    }
    public virtual T GetByIdLazyLoad(int id, params Expression<Func<T, object>>[] children)
    {
        children.ToList().ForEach(x => DbSet.Include(x).Load());

        return DbSet.Find(id);
    }
    public virtual void Add(T entity)
    {
        DbEntityEntry dbEntityEntry = DbContext.Context.Entry(entity);
        if (dbEntityEntry.State != EntityState.Detached)
        {
            dbEntityEntry.State = EntityState.Added;
        }
        else
        {
            DbSet.Add(entity);
        }
    }

    public virtual void Update(T entity)
    {
        DbEntityEntry dbEntityEntry = DbContext.Context.Entry(entity);
        if (dbEntityEntry.State == EntityState.Detached)
        {
            DbSet.Attach(entity);
        }
        dbEntityEntry.State = EntityState.Modified;
    }

    public virtual void Delete(T entity)
    {
        DbEntityEntry dbEntityEntry = DbContext.Context.Entry(entity);
        if (dbEntityEntry.State != EntityState.Deleted)
        {
            dbEntityEntry.State = EntityState.Deleted;
        }
        else
        {
            DbSet.Attach(entity);
            DbSet.Remove(entity);
        }
    }

    public virtual void Delete(int id)
    {
        var entity = GetById(id);
        if (entity == null) return; // not found; assume already deleted.
        Delete(entity);
    }
}

Solution

  • Breeze supports "Named saves" where you specify the name of the specific server endpoint ( i.e. your service method) on a per save basis. See:

    http://www.getbreezenow.com/documentation/saving-changes

    This would look something like this on your client.

    var saveOptions = new SaveOptions({ resourceName: "CustomSave1" }); 
    em.saveChanges(entitiesToSave, saveOptions).then(function (saveResult) {
      // .. do something interesting.
    
    }
    

    and on your server

    [HttpPost]
    public SaveResult CustomSave1(JObject saveBundle) {
      ContextProvider.BeforeSaveEntityDelegate = CustomSave1Interceptor;
      return ContextProvider.SaveChanges(saveBundle);
    }
    
    private Dictionary<Type, List<EntityInfo>> CustomSave1Interceptor(Dictionary<Type, List<EntityInfo>> saveMap) {
      // In this method you can
      //   1) validate entities in the saveMap and optionally throw an exception
      //   2) update any of the entities in the saveMap
      //   3) add new entities to the saveMap
      //   4) delete entities from the save map.
      // For example
      List<EntityInfo> fooInfos;
      if (!saveMap.TryGetValue(typeof(Foo), out fooEntities)) {
         // modify or delete any of the fooEntites    
         // or add new entityInfo instances to the fooEntities list.
      }
    
    }