I am creating a breeze entity using createEntity of entitymanager. Upon creation I find that only 1 of its navigation property is null whereas others are assigned.
var chargeEntity = {
ClientId: data.ClientId,
PatientId: data.PatientId,
AssessmentLogId: data.AssessmentLogId,
MedicalRecordNbr: data.Patient.MedicalRecordNbr,
AssessmentTypeCd: data.AssessmentType.AssessmentTypeCd,
ReviewDate: new Date(),
Qty: 1,
InsertDate: new Date(),
ProductId: data.ProductId
};
var charge = datacontext.createEntity('Charge', chargeEntity);
I find that in the charge entity, AssessmentLog navigation property is null where as others like Client, Patient and Product are assigned.
What could be the reason ?
After debugging for a while, I found that in breeze.debug.js, in the function below:
proto._findEntityGroup = function (entityType) {
return this._entityGroupMap[entityType.name];
};
_entityGroupMap does not contain an entry for the navigation property AssessmentLog, hence that property is being set as null.
The entity AssessmentLog was not in cache for the AssessmentLogId, that's why the navigation property was being set as null.
I executed a breeze query for the AssessmentLogId, and then the charge entity's AssessmentLog was correctly set.
Related
I have custom OData action that is called from my client. The results from this action are a list of JSON objects which need to be merged back into the Breeze cache as entities. How do I convert a JSON object into a Breeze entity and merge that entity back into the entityManager's cache? Some code:
$http.post('/odata/MyEntityType/MyCustomAction/', {
'someData': JSON.stringify(element1),
'SomeOtherData': JSON.stringify(element2)
})
.success(function (results) {
//results.value is an array of *MyEntityType* JSON objects. `//Is there a way to convert these to breeze entities?`
});
Some things I have tried:
manager.importEntities("MyEntityType", theJsonForAnEntity); //just stabbing in the dark here
manager.createEntity("MyEntityType", theJsonForAnEntity); //error: A MergeStrategy of 'Disallowed' does not allow you to attach an entity when an entity with the same key is already attached"
createEntity won't work
Sorry, Jeremy, but that isn't going to work. Here's a test that shows why:
// Failing test
test("merge customer date into existing cached entity using `createEntity`", function () {
var em = newEm();
// Create an unchanged entity in cache as if it had been queried
em.createEntity('Customer', {
CustomerID: dummyCustID,
CompanyName: 'Foo Co',
ContactName: 'Ima Kiddin'
}, UNCHGD); // creates the entity in the Unchanged state
// Try to merge some changes into that entity using createEntity.
var changes = {
CustomerID: dummyCustID,
CompanyName: 'Bar Co',
}
var cust = em.createEntity('Customer', changes,
UNCHGD, breeze.MergeStrategy.OverwriteChanges);
ok(cust.entityAspect.entityState.isUnchanged(), "cust should be 'Unchanged'");
// using Knockout; it's simpler if using Angular
equal(cust.CompanyName(), 'Bar Co', "Company name should be updated by merge'");
equal(cust.ContactName(), 'Ima Kiddin', "Contact name should be unchanged after merge'");
});
The third assert fails because the createEntity method overwrites every property, not just the one data value of interest (CompanyName in this case). That means that ContactName is null.
importEntities won't work either
For the same reason. When you import an entity, you import the entire entity, not just some portion of it. So that too would wipe out the ContactName in this example.
Manual merge
I think if you want to blend the results with the entity in cache, you'll have to do that by hand. You'll have to iterate over the results and update the cached equivalents.
Imagine that the changes variable above is result of your POST. The following test does pass:
// Passing test
test("merge customer date into existing cached entity", function () {
var em = newEm();
// Create an unchanged entity in cache as if it had been queried
em.createEntity('Customer', {
CustomerID: dummyCustID,
CompanyName: 'Foo Co',
ContactName: 'Ima Kiddin'
}, UNCHGD); // creates the entity in the Unchanged state
// Merge some changes into that entity
// Imagine that `changes` came from your server as a result of your POST
var changes = {
CustomerID: dummyCustID,
CompanyName: 'Bar Co',
}
// First find the customer in cache.
// Here I'm assuming it will always be there; you should be careful
var cust = em.getEntityByKey('Customer', changes.CustomerID);
// using Knockout; it's a little simpler in Angular
for (var p in changes) { cust[p](changes[p]); }
cust.entityAspect.setUnchanged(); // cuz the previous updates changed the state to "Modified"
ok(cust.entityAspect.entityState.isUnchanged(), "cust should be 'Unchanged'");
equal(cust.CompanyName(), 'Bar Co', "Company name should be updated by merge'");
equal(cust.ContactName(), 'Ima Kiddin', "Contact name should be unchanged after merge'");
});
Feature Request
I can imagine a future in which Breeze could automate this for you with all of the appropriate error checks. If you think such a "JSON entity merge" feature would be a valuable enhancement to Breeze, please add that to User Voice. I would be quick to agree but we can't add features unless people want them.
I think you're onto something with the manager.createEntity approach. Try specifying a merge strategy (the default is "Disallowed" which is causing problems for your use case):
manager.createEntity("MyEntityType", thePocoEntityFromTheCustomODataAction, breeze.MergeStrategy.PreserveChanges);
Merge strategies: http://www.breezejs.com/sites/all/apidocs/classes/MergeStrategy.html
createEntity: http://www.breezejs.com/sites/all/apidocs/classes/EntityManager.html#method_createEntity
I am developing a Single Page App using Hot Towel Template, using breeze...and I have come across a peculiar problem, and I am unable to figure out the internal working which causes it...
I have a Programmes table, and the Programmes table has a foreign key to Responses, so the structure of Programmes is:
Id, ResponseID, Name and Date
and the Page has Name and Date, the foreign comes from RouteData.
and for one ResponseId in Programmes table, I want to save only on Programme.
So, when user comes to this page, it check the Programmes table that if it has an Entry for that particular Response Id, if yes, it goes in Edit case and if not it goes to Add a new entry case.
To achieve this, what I am doing is below:
var objTempProgramme = ko.observable();
var objProgramme = ko.observable();
function activate(routeData) {
responseId = parseInt(routeData.responseId);
// Create a Programme Entity
objProgramme(datacontext.createProgramme());
// Fill in a Temporary Observable with Programmes data
datacontext.getEntitiesDetailsByRelativeId('responseID', responseId , 'Programmes', objTempProgramme, true).then(function(){
// Check if Programmes Exists
if (objTempProgramme() != null && objTempProgramme() != undefined) {
// What I am doing here is filling my Programmes Entity with data coming from database (if it is there)
objProgramme(objTempProgramme());
} else {
// The Else Part assigns the Foreign Key (ResponseId) to my Entity Created above
objProgramme().responseID(responseId);
}
});
}
In datacontext.js:
var createProgramme = function () {
return manager.createEntity(entityNames.programme);
}
var getEntitiesDetailsByRelativeId = function (relativeIdName, relativeId, lookupEntity, observable, forceRefresh) {
var query = entityQuery.from(lookupEntity).where(relativeIdName, "==", relativeId);
return executeGetQuery(query, observable, forceRefresh);
};
Now when I call manager.saveChanes on my page, I would Expect objProgramme to be saved, in any case, be it edit or be it save,
but what breeze is doing here is that though it is filling objTempProgramme in objProgramme, but it is also leaving the entity objProgramme unreferenced with its manager, so that when I call save, it tries to save that entity too (2 entities in total, objProramme and one unreferenced one), but that entity does not have foreign key set and it fails..but my question is why?
Assigning one entity to another does not mean all its properties get assigned to another? And why is that unreferenced entity present?
There appears to be two ways to update a disconnected Entity Framework entity using the "attach" method.
Method One is to simply set the disconnected entity's state as modified:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.SaveChanges();
This will save all fields on the "dog" object. But say you are doing this from an mvc web page where you only allow editing of Dog.Name, and the only Dog property contained on the page is Name. Then one could do Method Two:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).Property(o => o.Name).CurrentValue = dog.Name;
myDbContext.Entry(dog).Property(o => o.Name).IsModified = true;
myDbContext.SaveChanges();
Method Two could get quite verbose when there are a lot of properties to update. This prompted me to attempt Method Three, setting IsModified = false on the properties I don't want to change. This does not work, throwing the runtime error "Setting IsModified to false for a modified property is not supported":
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.Entry(dog).Property(o => o.Owner).IsModified = false;
myDbContext.SaveChanges();
I'd much prefer to use Method One everywhere, but there are many instances where my asp.net mvc view does not contain every scalar property of the Dog class.
My questions are:
Are there any attributes I could use on the POCO class that would tell Entity Framework that I never want the property to up updated? Eg, [NeverUpdate]. I am aware of the [NotMapped] attribute, but that is not what I need.
Failing that, is there any way I can use Method One above (myDbContext.Entry(dog).State = EntityState.Modified;
) and exclude fields that I don't want updated?
P.S. I am aware of another way, to not use "attach" and simply fetch a fresh object from the database, update the desired properties, and save. That is what I am doing, but I'm curious if there is a way to use "attach," thus avoiding that extra trip to the database, but do it in a way that is not so verbose as Method Two above. By "fetch a fresh object" I mean:
Dog dbDog = myDbContext.Dogs.FirstOrDefault(d => d.ID = dog.ID);
dbDog.Name = dog.Name;
myDbContext.SaveChanges();
The following may work works.
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
var objectContext = ((IObjectContextAdapter) myDbContext).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(Dogs)))
{
// You need to give Foreign Key Property name
// instead of Navigation Property name
entry.RejectPropertyChanges("OwnerID");
}
myDbContext.SaveChanges();
If you want to do it in a single line, use the following extension method:
public static void DontUpdateProperty<TEntity>(this DbContext context, string propertyName)
{
var objectContext = ((IObjectContextAdapter) context).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(TEntity)))
{
entry.RejectPropertyChanges(propertyName);
}
}
And use it like this
// After you modify some POCOs
myDbContext.DontUpdateProperty<Dogs>("OwnerID");
myDbContext.SaveChanges();
As you can see, you can modify this solution to fit your needs, e.g. use string[] properties instead of string propertyName as the argument.
Suggested Approach
A better solution would be to use an Attribute as you suggested ([NeverUpdate]). To make it work, you need to use SavingChanges event (check my blog):
void ObjectContext_SavingChanges(object sender, System.Data.Objects.SavingChangesEventArgs e)
{
ObjectContext context = sender as ObjectContext;
if(context != null)
{
foreach(ObjectStateEntry entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
var type = typeof(entry.Entity);
var properties = type.GetProperties();
foreach( var property in properties )
{
var attributes = property.GetCustomAttributes(typeof(NeverUpdateAttribute), false);
if(attributes.Length > 0)
entry.RejectPropertyChanges(property.Name);
}
}
}
}
// Check Microsoft documentation on how to create custom attributes:
// http://msdn.microsoft.com/en-us/library/sw480ze8(v=vs.80).aspx
public class NeverUpdateAttribute: SystemAttribute
{
}
//In your POCO
public class Dogs
{
[NeverUpdate]
public int OwnerID { get; set; }
}
Warning: I did not compile this code. I'm not at home :/
Warning 2: I have just read the MSDN documentation and it says:
ObjectStateEntry.RejectPropertyChanges Method
Rejects any changes made to the property with the given name since the
property was last loaded, attached, saved, or changes were accepted.
The orginal value of the property is stored and the property will no
longer be marked as modified.
I am not sure what its behavior would be in the case of attaching a modified entity. I will try this tomorrow.
Warning 3: I have tried it now. This solution works. Property that is rejected with RejectPropertyChanges() method are not updated in the persistence unit (database).
HOWEVER, if the entity that is updated is attached by calling Attach(), the current context remains dirty after SaveChanges(). Assume that the following row exists in the database:
Dogs
ID: 1
Name: Max
OwnerID: 1
Consider the following code:
var myDog = new Dogs();
myDog.ID = 1;
myDog.Name = Achilles;
myDog.OwnerID = 2;
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
The current state of database after SaveChanges():
Dogs:
ID: 1
Name: Achilles
OwnerID: 1
The current state of myDbContext after SaveChanges():
var ownerId = myDog.OwnerID; // it is 2
var status = myDbContext.Entry(myDog).State; // it is Unchanged
So what you should do? Detach it after SaveChanges():
Dogs myDog = new Dogs();
//Set properties
...
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
myDbContext.Entry(myDog).State = EntityState.Detached;
I want to add an Entity to my Manager.
The manager is saved in my MasterViewmodel, and I'm working with knockout.
Error: Cannot attach an object to an EntityManager without first setting its key or setting its entityType 'AutoGeneratedKeyType' property to something other than 'None'
My code:
var item = {
GuidUser: masterViewModel.UserID,
GuidProject: ProjectID,
Start: startTime,
End: stopTime,
Description: workDone,
IdCustomer: null,
timestampId: null,
Charged: false,
ToCharge: toCharge,
};
var item2 = masterViewModel.breezeProjectManager().createEntity('Timestamp', item);
masterViewModel.breezeProjectManager().addEntity(item2);
But how to set a Key?
Try the following:
var item2 = masterViewModel.breezeProjectManager()
.createEntity('item', { timestampId: breeze.core.getUuid() });
When any EntityType is first defined it should have at least one DataProperty that has it's 'isPartOfKey' property set to true. The metadata for any EntityType is either materialized as a result of the metadata being returned from the server or is created on the client. In either case, the "EntityType' metadata must be created before any entities of the type can be created.
My guess is that you have not yet either fetched the metadata or created it, when you first tried to create your entity. In your case, you can make a fetchMetadata call before you try to create your entity. i.e.
breezeProjectManager().metadataStore.fetchMetadata().then(function() {
.. perform your creation and add logic here.
}
The other possibility is that whichever field ( or fields) are defined with 'isPartOfKey' = true, have not been set to a nonnull value before calling 'addEntity'.
I'm not sure if this is a bug in the DefaultModelBinder class or what.
But UpdateModel usually doesn't change any values of the model except the ones it found a match for.
Take a look at the following:
[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Edit(List<int> Ids)
{
// Load list of persons from the database
List<Person> people = GetFromDatabase(Ids);
// shouldn't this update only the Name & Age properties of each Person object
// in the collection and leave the rest of the properties (e.g. Id, Address)
// with their original value (whatever they were when retrieved from the db)
UpdateModel(people, "myPersonPrefix", new string[] { "Name", "Age" });
// ...
}
What happens is UpdateModel creates new Person objects, assign their Name & Age properties from the ValueProvider and put them in the argument List<>, which makes the rest of the properties set to their default initial value (e.g. Id = 0)
so what is going on here?
UPDATE:
I stepped through mvc source code (particularly DefaultModelBinder class) and here is what I found:
The class determines we are trying to bind a collection so it calls the method: UpdateCollection(...) which creates an inner ModelBindingContext that has a null Model property. Afterwards, that context is sent to the method BindComplexModel(...) which checks the Model property for null and creates a new instance of the model type if that is the case.
That's what causes the values to be reset.
And so, only the values that are coming through the form/query string/route data are populated, the rest remains in its initialized state.
I was able to make very few changes to UpdateCollection(...) to fix this problem.
Here is the method with my changes:
internal object UpdateCollection(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType) {
IModelBinder elementBinder = Binders.GetBinder(elementType);
// build up a list of items from the request
List<object> modelList = new List<object>();
for (int currentIndex = 0; ; currentIndex++) {
string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
if (!DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, subIndexKey)) {
// we ran out of elements to pull
break;
}
// **********************************************************
// The DefaultModelBinder shouldn't always create a new
// instance of elementType in the collection we are updating here.
// If an instance already exists, then we should update it, not create a new one.
// **********************************************************
IList containerModel = bindingContext.Model as IList;
object elementModel = null;
if (containerModel != null && currentIndex < containerModel.Count)
{
elementModel = containerModel[currentIndex];
}
//*****************************************************
ModelBindingContext innerContext = new ModelBindingContext() {
Model = elementModel, // assign the Model property
ModelName = subIndexKey,
ModelState = bindingContext.ModelState,
ModelType = elementType,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
object thisElement = elementBinder.BindModel(controllerContext, innerContext);
// we need to merge model errors up
VerifyValueUsability(controllerContext, bindingContext.ModelState, subIndexKey, elementType, thisElement);
modelList.Add(thisElement);
}
// if there weren't any elements at all in the request, just return
if (modelList.Count == 0) {
return null;
}
// replace the original collection
object collection = bindingContext.Model;
CollectionHelpers.ReplaceCollection(elementType, collection, modelList);
return collection;
}
Rudi Breedenraed just wrote an excellent post describing this problem and a very helpful solution. He overrides the DefaultModelBinder and then when it comes across a collection to update, it actually updates the item instead of creating it new like the default MVC behavior. With this, UpdateModel() and TryUpdateModel() behavior is consistent with both the root model and any collections.
You just gave me an idea to dig into ASP.NET MVC 2 source code.
I have been struggling with this for two weeks now. I found out that your solution will not work with nested lists. I put a breakpoint in the UpdateCollection method ,and it never gets hit. It seems like the root level of model needs to be a list for this method to be called
This is in short the model I have..I also have one more level of generic lists, but this is just a quick sample..
public class Borrowers
{
public string FirstName{get;set;}
public string LastName{get;set;}
public List<Address> Addresses{get;set;}
}
I guess that, I will need to dig deeper to find out what is going on.
UPDATE:
The UpdateCollection still gets called in asp.net mvc 2, but the problem with the fix above is related to this HERE