user210757
user210757

Reputation: 7386

breeze - modify an entity, on the server, based upon it's navigational properties, before saving

Is there any way to get an Entity's navigational property's "current" value in BeforeSaveEntity (or anywhere else before save) in breeze on the server side? By current, I mean what exists in the database, with any incoming changes merged in. This isn't for validation, rather I am computing a value for a parent property (that I don't want on the client) based upon both the parent fields and children fields...

For example,

public class Parent {
  public ICollection<Child> Children{ get; set; }
}

. . .

protected override bool BeforeSaveEntity(EntityInfo entityInfo) {
  if (entityInfo.Entity.GetType() == typeof(Parent) &&
  (entityInfo.EntityState == EntityState.Added || entityInfo.EntityState == EntityState.Updated)) {

   // Lazy load Parent's Children collection out of breeze's context 
   // so items are "current' (existing merged with changes)

   Parent parent = (Parent)entityInfo.Entity;
   Context.Entry(parent).Collection(p => p.Children).Load();

   // this throws exception Member 'Load' cannot be called for property
   // 'Children' because the entity of type 'Parent' does not exist in the context.
  }
}  

I'm thinking they are not in the DBContext yet. All I can think to do is to retrieve the existing children from the database, and hand merge the changes in BeforeSaveEntities, which is a hassle.

Upvotes: 0

Views: 249

Answers (1)

Steve Schmitt
Steve Schmitt

Reputation: 3209

Lazy loading is not enable in the DbContext that Breeze uses for saving. The reason is detailed in this SO answer.

You should load any additional entities in a separate DbContext.


Here's an example of how I did it in a project. Perhaps the MergeEntities and DetachEntities methods should be included with Breeze to make it simpler to do this.

protected override Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap)
{
    // create a separate context for computation, so we don't pollute the main saving context
    using (var newContext = new MyDbContext(EntityConnection, false))
    {
        var parentFromClient = (Parent)saveMap[typeof(Parent)][0].Entity;

        // Load the necessary data into the newContext
        var parentFromDb = newContext.Parents.Where(p => p.ParentId == parentFromClient.ParentId)
            .Include("Children").ToList();

        // ... load whatever else you need...

        // Attach the client entities to the ObjectContext, which merges them and reconnects the navigation properties
        var objectContext = ((IObjectContextAdapter)newContext).ObjectContext;
        var objectStateEntries = MergeEntities(objectContext, saveMap);

        // ... perform your business logic...

        // Remove the entities from the second context, so they can be saved in the original context
        DetachEntities(objectContext, saveMap);
    }
    return saveMap;
}

/// Attach the client entities to the ObjectContext, which merges them and reconnects the navigation properties
Dictionary<ObjectStateEntry, EntityInfo> MergeEntities(ObjectContext oc, Dictionary<Type, List<EntityInfo>> saveMap)
{
    var oseEntityInfo = new Dictionary<ObjectStateEntry, EntityInfo>();
    foreach (var type in saveMap.Keys)
    {
        var entitySet = this.GetEntitySetName(type);
        foreach(var entityInfo in saveMap[type])
        {
            var entityKey = oc.CreateEntityKey(entitySet, entityInfo.Entity);
            ObjectStateEntry ose;
            if (oc.ObjectStateManager.TryGetObjectStateEntry(entityKey, out ose))
            {
                if (ose.State != System.Data.Entity.EntityState.Deleted)
                    ose.ApplyCurrentValues(entityInfo.Entity);
            }
            else
            {
                oc.AttachTo(entitySet, entityInfo.Entity);
                ose = oc.ObjectStateManager.GetObjectStateEntry(entityKey);
            }

            if (entityInfo.EntityState == Breeze.ContextProvider.EntityState.Deleted)
            {
                ose.Delete();
            }
            oseEntityInfo.Add(ose, entityInfo);
        }
    }
    return oseEntityInfo;
}

/// Remove the entities in saveMap from the ObjectContext; this separates their navigation properties
static void DetachEntities(ObjectContext oc, Dictionary<Type, List<EntityInfo>> saveMap)
{
    foreach (var type in saveMap.Keys)
    {
        foreach (var entityInfo in saveMap[type])
        {
            try
            {
                oc.Detach(entityInfo.Entity);
            }
            catch
            { // the object cannot be detached because it is not attached
            }
        }
    }
}

Upvotes: 2

Related Questions