where doese breeze fits into ntier architecture

375 views Asked by At

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

There are 2 answers

0
Jay Traband On BEST ANSWER

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

}
3
Jeremy Danyow On

Much of this question is broad question and answers will be primarily opinion based... that said, here's my two cents: keep it simple. Carefully consider whether you truly need 3, 4 and 5, especially whether you need to implement UoW or the Repository Pattern yourself. The EF DbContext implements both, you could use it in your controllers directly if you wanted.

If you have custom logic that needs to execute prior to savechanges utilize one of the interceptor methods: BeforeSaveEntity or BeforeSaveEntites. Here's the documentation for those methods:

http://www.getbreezenow.com/documentation/contextprovider#BeforeSaveEntity