0
votes

Background:

I'm using the SandboxManager format described in the documentation to export my entities to a second manager for changes, and then the entity is imported back into the main manager. I'm creating the Sandbox on page initialization from a copy of the main manger using createEmptyCopy(), giving the Sandbox the same metadata and no entities.

During the export, I pass the entity over this way:

function exportEntityToSandbox(entity) {

    var exportData = STEP.EntityManager.exportEntities([entity], false);
    var result = STEP.SandboxManager.importEntities(exportData,
        { mergeStrategy: breeze.MergeStrategy.OverwriteChanges });

    // ImportEntities changes the service name so revert it back
    STEP.SandboxManager.setProperties({
        dataService: new breeze.DataService({
            serviceName: 'api/Sandbox',
            hasServerMetadata: false
        })
    });
    return result.entities[0];
};

I'm currently working with this entity:

public class License
{
    public int ID { get; set; }
    public int LicenseTypeID { get; set; }
    public virtual LicenseType LicenseType { get; set; }
    public int ExternalProductID { get; set; }
    public virtual ExternalProduct ExternalProduct { get; set; }
    public int? LicensesPurchased { get; set; }
    public int? LicensesAllocated { get; set; }
    public string AllocationDescription { get; set; }
    public bool DeletedFlag { get; set; }

}

And the map for this entity:

public LicenseMap()
    {
        this.HasKey(t => t.ID);

        this.Property(t => t.ID)
            .HasColumnName("ID")
            .HasColumnType("int")
            .IsRequired();
        this.Property(t => t.LicenseTypeID)
            .HasColumnName("LICENSE_TYPE_ID")
            .HasColumnType("int")
            .IsRequired();
        this.Property(t => t.ExternalProductID)
            .HasColumnName("PRODUCT_ID")
            .HasColumnType("int")
            .IsRequired();
        this.Property(t => t.LicensesPurchased)
            .HasColumnName("LICENSES_PURCHASED")
            .HasColumnType("int")
            .IsOptional();
        this.Property(t => t.LicensesAllocated)
            .HasColumnName("LICENSES_ALLOCATED")
            .HasColumnType("int")
            .IsOptional();
        this.Property(t => t.AllocationDescription)
            .HasColumnName("ALLOCATION_DESCRIPTION")
            .HasColumnType("varchar")
            .HasMaxLength(Int32.MaxValue)
            .IsOptional();
        this.Property(t => t.DeletedFlag)
            .HasColumnName("DELETED_FLAG")
            .HasColumnType("bit")
            .IsRequired();

        this.ToTable("LICENSE");

    }
}

I'm only passing in the License entity during export--the ExternalProduct and LicenseType navigation entities are not passed to the Sandbox. However, ALL ExternalProducts & LicenseTypes are loaded into the Main manager on page initialization. So in the workflow, I select from a list of dropdowns the ExternalProduct and update ONLY the ExternalProductId (as the ExternalProduct itself is not on the Sandbox).

Issue:

The problem I'm facing is when the entity is exported back into the Main Manager:

function save(id, manager, tag) {
    manager = manager || STEP.SandboxManager;
    return manager.saveChanges()
        .then(saveSucceeded)
        .fail(saveFailed);

    function saveSucceeded(data) {
        var exportData = STEP.SandboxManager.exportEntities(data.entities, false);
        STEP.EntityManager.importEntities(exportData,
            { mergeStrategy: breeze.MergeStrategy.OverwriteChanges });

        // ImportEntities changes the service name
        // Revert it back
        STEP.EntityManager.setProperties({
            dataService: new breeze.DataService({
                serviceName: 'api/Datamart',
                hasServerMetadata: false
            })
        });
        // Get a reference to the same entity in the Sandbox and update observable
        var entityKey = data.entities[0].entityAspect.getKey();
        var type = entityKey.entityType.shortName;
        var entityManagerEntity = STEP.EntityManager.getEntityByKey(type, id);
    };
    function saveFailed(msg) {
        // Do stuff
    };
};

The entity has a new ExternalProductId (changed during edit), but still has the old ExternalProduct navigation entity. The new navigation entity is not wired up to License even though I know it's in the cache. The navigation property still points to the old entity (property License.ExternalProductId does not equal License.ExternalProduct.ID).

So am I expecting too much of Breeze to do this rewiring upon import and I will need to do this manually every time?

I thought this may a problem with my EF definition and have tried adding each of these to the LicenseMap with no success:

this.HasRequired(m => m.ExternalProduct)
    .WithOptional()
    .Map(m => m.MapKey("ExternalProductID"));

this.HasRequired(t => t.ExternalProduct
    .WithMany()
    .HasForeignKey(t => t.ExternalProductID)
    .WillCascadeOnDelete(false);

This is a required relationship with the navigation property on the License entity only. I'm using Breeze v1.4.11

Edit:

I just made sure this was in the entity map:

this.HasRequired(t => t.ExternalProduct)
    .WithMany()
    .HasForeignKey(t => t.ExternalProductID);

And tested this simple coding snippet:

license.ExternalProductID(816);
var test = license.ExternalProduct();

And the test variable of navigation entity ExternalProduct still does not change after setting the ID directly. According to Breeze documentation the navigation entity should be updated unless I'm doing something wrong?

2

2 Answers

0
votes

You have found a bug. Setting the FK to a value should cause the corresponding navigation property to be refreshed. That works properly if the corresponding entity is in cache. Unfortunately, if the corresponding entity is NOT in cache, Breeze v.1.5.1 does not null-out the navigation property as it should.

I will be back when that is fixed. We will jump on that. Stay tuned.

0
votes

I got this figured out.

The problem was I didn't have all of the ExternalProducts in cache so when changing the navigation property ID, the associated ExternalProduct entity wasn't available to wire up from the new ID. When I make sure the new ExternalProduct is in cache, changing the ID on License updates the ExternalProduct properly.

I could argue that Breeze should set the ExternalProduct to null when License.ExternalProductID is changed to a value for an entity not in cache but I digress.