I've created a custom entity level validation function, very similar to the one in the documentation (http://www.breezejs.com/documentation/validation). When I call getValidationErrors(), I get the following error:
Cannot read property 'name' of undefined
The error is coming from:
proto.getValidationErrors = function (property) {
assertParam(property, "property").isOptional().isEntityProperty().or().isString().check();
var result = __getOwnPropertyValues(this._validationErrors);
if (property) {
var propertyName = typeof (property) === 'string' ? property : property.name;
result = result.filter(function (ve) {
**return (ve.property.name === propertyName);**
});
}
return result;
};
There is no 'property' field in the custom entity level validator context. I'm using Breeze 1.4.5. Is this a bug? It seems to me the code above should check for 've.property', before trying to access the name.
Update
This has been fixed as of Breeze 1.4.7, available now.
Previous Post:
This is a bug that has already been fixed on GitHub and will be part of the next release (Breeze 1.4.7) out sometime next week. Or you can pull the breeze.xxx.js file from GitHub now if you need the fix earlier.
Related
I have an MVC app in which I have just updated my model, and now I'm getting this strange error message that I can't figure out. Alls I did was update the model and save it, so something has updated something.
The error is:
'int' does not contain a definition for 'ToArray' and no extension method 'ToArray' accepting a first argument of type 'int' could be found
Here is the line that's causing the error:
viewMaster.properties = _dbLazy.spPropertyByBuildingLookup(propString, null).ToArray();
And here is the spPropertyByBuildingLookup method (of course, it's based on a SP of the same name):
public virtual int spPropertyByBuildingLookup(string propertyInclude, string propertyExclude)
{
var propertyIncludeParameter = propertyInclude != null ?
new ObjectParameter("propertyInclude", propertyInclude) :
new ObjectParameter("propertyInclude", typeof(string));
var propertyExcludeParameter = propertyExclude != null ?
new ObjectParameter("propertyExclude", propertyExclude) :
new ObjectParameter("propertyExclude", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("spPropertyByBuildingLookup", propertyIncludeParameter, propertyExcludeParameter);
}
So, essentially what I ended up doing was reverting the Model back to the way it was (we use GitHub for source control). What happened was, I updated a field in the Property table from varchar to bit. I had to delete the table from the model and re add it. When I did, it defaulted the spPropertyByBuildingLookup method to return an int (it had to remove references to the Property model that was deleted). I found this out by going into the CBA.Context.cs file after it was reverted to see what had changed.
Breeze Version: 1.5.3
I'm experiencing something similiar to some older questions on SO but it seems like this "bug" is reoccurring:
I have a 1-To-Many unidirectional navigation property which is not populated. I have checked the metadata and the response from the server. I've even debugged into breeze and the node (or rawEntity) seems to be perfect.
I've tried to track it down and came to the conclusion, that it happens, because no "inverse"-Property is found for my Navigation Property and the mergeRelatedEntities-Function returning without updating the target Entity:
function mergeRelatedEntities(mc, navigationProperty, targetEntity, rawEntity) {
var relatedEntities = mergeRelatedEntitiesCore(mc, rawEntity, navigationProperty);
if (relatedEntities == null) return;
var inverseProperty = navigationProperty.inverse;
if (!inverseProperty) return;
var originalRelatedEntities = targetEntity.getProperty(navigationProperty.name);
originalRelatedEntities.wasLoaded = true;
relatedEntities.forEach(function (relatedEntity) {
if (typeof relatedEntity === 'function') {
mc.deferredFns.push(function () {
relatedEntity = relatedEntity();
updateRelatedEntityInCollection(relatedEntity, originalRelatedEntities, targetEntity, inverseProperty);
});
} else {
updateRelatedEntityInCollection(relatedEntity, originalRelatedEntities, targetEntity, inverseProperty);
}
});
}
Older Posts:
Non scalar navigation properties are not populating with "nodb" conception
and
Breeze (1.4.5) unidirectional one-to-many: navigation collection not populated
Edited 11. May 2015
Okay I start to understand what Ward meant with the unmapped properties (by finding a similar question from 2 years ago: Handling calculated properties with breezejs and web api)
What I have so far:
function iUIConfigConstructorTool() {
this.ConfigToCurrentUSetting = null;
};
function iUIConfigConstructorAppl() {
this.ConfigToCurrentUSetting = null;
};
function iUIConfigConstructorWidget() {
this.ConfigToCurrentUSetting = null;
};
function iUIConfigInitializer(uiConfigObject) {
// initializing other properties
};
this.manager.metadataStore.registerEntityTypeCtor("Tool", iUIConfigConstructorTool, iUIConfigInitializer);
this.manager.metadataStore.registerEntityTypeCtor("Appl", iUIConfigConstructorAppl, iUIConfigInitializer);
this.manager.metadataStore.registerEntityTypeCtor("Widget", iUIConfigConstructorWidget, iUIConfigInitializer);
This does what I want. Is there a way, to do this over the metamodel from the server? Because I define my calculated properties on the server and the metamodel is delivered by the server, I don't want to change the client-side implementation if I add a new Navigation-Property. So I'd need something like a flag in the metamodel to tell breeze, that this property needs to be filled as it comes over the wire without ForeignKeys etc.
Maybe in other words: We are doing "sub queries" on the server side (e.g. find Customers with it's Orders but only up to a certain Date) for each queried object and deliver this to breeze (in a separate property than the real orders-property of the Customer). Our problem is: How do we unpack this sub-query because there is no direct connection in metadata but we need the connection for the logic.
Please update your question with:
how you obtained/created metadata
metadata for the two endpoints (just the nav props, pk prop, and fk props will do)
the exact query expression
Of course a repro in plunker would be most welcome.
update 7 May 2015
If I understand your comment correctly, the navigation property (properties) in question are to be maintained by you (w/ server-supplied info), not by Breeze.
That leads me to suggest that you maintain them as unmapped properties rather than mapped, navigation properties. Does that make sense?
I've noticed that in the metadata there's an object entityType but also an object enumType.
We use manager.metadataStore.getEntityType() to access the metadata of an Entity.
How can we do it for a given enum ? How would I create the enum on the client side out of the metadata ?
Also, when I assign an enum value to a property, I'd like to to it by name instead of by value.
For instance, assuming that Status is of type myEnum:
myEntity.Status = myEnum.Valid;
instead of
myEntity.Status = 1;
Does breeze have any helper function to access the values of an enum ?
This issue is still open as I write. But you might want to take a look at the work-around described in the answer to this SO question.
I am assuming that you are talking about data properties that are defined as .NET enums on the server, and you want additional metadata about these properties to be made available on the Breeze client.
Unfortunately, Breeze does not yet support any metadata on enum types other than the name of the .NET type backing the enum value. This is the 'enumType' property that will appear on any dataProperty that is backed by an .NET Enum on the server. (We do need to document this better)
Please add a feature request for this to the Breeze User Voice. It's a good idea and we do take these suggestions very seriously.
Well this is not exact solution to your question but definitely can help people who are generating metadata offline.
I am using NancyFx(No EF) + Breeze + AngularJS for my web project and generating breeze metadata offline(using EF methods at development) and then using it in js file.
I also encountered similar situation where I want to get all Enum values to bind dropdowns and to display EnumName corresponding to EnumValue(Id). I searched over net but there was not much as per my scenario.
So I have written raw JS methods
1. To extract all enums and their values(Id & Name) in a JS dictionary(associated array) from metadata.
var enumDictionary = {};
JSON.parse(window.app.metadata).schema.enumType.forEach(function (enumType) {
var newEnumValues = [];
enumType.member.forEach(function (enumValue) {
var newEnumValue = { id: enumValue.value, name: enumValue.name };
newEnumValues.push(newEnumValue);
});
enumDictionary[enumType.name] = newEnumValues;
});
I created a method to get all enum values for a specific enum. This will be used for binding a dropdown.
function GetEnumDictionary(enumName) {
return enumDictionary[enumName];
}
Another method I created to get specific Enum name on basis of value.
function GetEnumDictionaryValue(enumName, enumValueId) {
var result = null;
enumDictionary[enumName].some(function (enumValue) {
if (enumValue.id == enumValueId) {
result = enumValue.name;
return;
}
});
return result;
}
I'm having some difficulties understanding the Concurrency problem using Update store procedures. I'm following Julie Lerman's Programming Entity Framework and she gives the following code in an example:
using (var context = new BAEntities())
{
var payment = context.Payments.First();
if (payment.PaymentDate != null)
{
payment.PaymentDate = payment.PaymentDate.Value.AddDays(1);
}
var origRowVersion = payment.RowVersion;
try
{ //BREAKPOINT #1
context.SaveChanges();
var newRowVersion = payment.RowVersion;
if (newRowVersion == origRowVersion)
{
Console.WriteLine("RowVersion not updated");
}
else
{
Console.WriteLine("RowVersion updated");
}
}
catch (OptimisticConcurrencyException)
{
Console.WriteLine("Concurrency Exception was thrown");
}
}
The Update SP looks like:
UPDATE payments
SET paymentdate=#date,reservationID=#reservationID,amount=#amount, modifieddate=#modifiedDate
WHERE
paymentid=#paymentid AND ROWVERSION=#rowversion
IF ##ROWCOUNT>0
SELECT RowVersion AS newTimeStamp FROM payments WHERE paymentid=#paymentid
and the "Use original value" checkbox is ticked in the mapping, which looks like this:
https://dl.dropboxusercontent.com/u/135754/updatemapping.png
Now, when I try to:
run the code as it is, then the newRowVersion inspected in the debugger is same as origRowversion, but the app enters 'else' clause (why is it the same in the first place, I have just changed it? is it debugger issue?)
run the code, but in the BREAKPOINT #1 I update the payment object in Management Studio, the SaveChanges throws OptimisticConcurrencyException. I assume this is expected result.
Each time when I look in the SQL Profiler, the original version of timestamp is sent to the server.
Then, when I untick the "Use original value" in the SP mappings for the timestamp value, everything works the same way as described above... I don't get the idea of it. Am I testing it wrong? When is the app supposed to enter the 'if' clause?
Thanks in advance, cheers!
EDIT:
I added newTimeStamp as the return value for the Update SP mapping. Now I can see that the updated value of RowVersion is correctly taken from the DB. But I still cannot see the difference between having "Use original value" checked and unchecked...
I think I get it now.
When I try to manually change the rowversion (to a random byte[]) before calling savechanges then:
Use Original Value unchecked: the 'random byte[]' is sent to the DB and used in the update stored procedure (in WHERE clause), causing OptimisticConcurrencyException
Use Original Value checked: the value that rowversion had when it was originally downloaded from DB is sent and used in the update stored procedure (in WHERE clause)
I guess this is what Use Original Value is for... It just seems a little weird to me, who would change it manually in the same dbcontext?
I am running into problem, where i extend the Entity to expose hasValidationError. Without that it works fine. Also i found that if I supply the ID before adding the entity it works fine as well. Why is the ID field not auto generating once the entity is extended on the client.
I am now using little different version of the code(i find it more intuitive to extend the entity this way), but it still errors out in the same way.
var Country = function () {
console.log("Country initialized");
var self = this;
self.Country_ID = ko.observable("");
self.Country_Code = ko.observable("");
self.Country_Name = ko.observable().extend({
validation: {
validator: function (val, someOtherVal) {
return false;//val === someOtherVal;
},
message: 'Invalid Value!',
params: 5
}
});
var prop = ko.observable(false);
var onChange = function () {
var hasError = self.entityAspect.getValidationErrors().length > 0;
if (prop() === hasError) {
// collection changed even though entity net error state is unchanged
prop.valueHasMutated(); // force notification
} else {
prop(hasError); // change the value and notify
}
};
// observable property is wired up; now add it to the entity
self.hasValidationErrors = prop;
//dummy property to wireup event
//should not be used for any other purpose
self.hasError = ko.computed(
{
read: function () {
self.entityAspect // ... and when errors collection changes
.validationErrorsChanged.subscribe(onChange);
},
// required because entityAspect property will not be available till Query
// return some data
deferEvaluation: true
});
self.fullName = ko.computed(
function () {
return self.Country_Code() + " --- " + self.Country_Name();
});
};
store.registerEntityTypeCtor("Country", Country);
and then in the button click i am using the following code to create new entity.
var countryType = manager.metadataStore.getEntityType("Country");
var newCountry = countryType.createEntity();
//newCountry.Country_ID(200); //if i add this line no errors occurs
newCountry.Country_Code("India");
self.list.push(newCountry);
manager.addEntity(newCountry); // validation error occurs right after this line
self.selectedItem(newCountry);
self.list.valueHasMutated();
Entities will only get their own autogenerated key if the metadata for their type specifies that this is supported. i.e.
if (myEntityType.autoGeneratedKeyType === AutoGeneratedKeyType.Identity)
This setting means that the key property of the entity is automatically generated by the server, typically for an 'Identity' column on your database.
or
if (myEntityType.autoGeneratedKeyType === AutoGeneratedKeyType.KeyGenerated)
This setting means that you have a server side KeyGenerator that can generate the key for you.
By default, however, myEntityType.autoGeneratedKeyType will equal AutoGeneratedKeyType.None.
In either of the other two cases, breeze will generate a temporary key on the client and then fix it up after a save completes with a 'real' key generated on the server.
If you do not need this capability, then simply create your own ctor for your type that generates a unique key and set it there. see MetadataStore.registerEntityTypeCtor for more details on how to register your ctor.
We are planning on improving our documentation in this area but haven't yet gotten there. I hope this helps.
Perhaps there is nothing wrong at all. How do you know that the id generation is failing? Is it a negative number after adding the newCountry to the manager? It should be.
What is the validation error that you are getting? Does it relate to Country_ID? Perhaps you have a validation constraint (e.g., minimum value) on Country_ID?
The addhasValidationErrorsProperty entity initializer works as intended. I just added a teaching test to the DocCode sample (see "Can create employee after registering addhasValidationErrorsProperty initializer" in entityExtensionTests.js). We haven't deployed it as I write this but you can get it from GitHub.
It follows your example as best I can with the Northwind Employee entity which has an identity id (Employee_ID). The test shows adding the initializer that I wrote in the previous post (not as you may have rewritten it). It shows that the new Employee's id is zero before adding to the manager and becomes -1 after adding to the manager. The -1 is the temporary id of the new employee; it receives a permanent value after save.
The default validationOptions of an EntityManager are set so to validate an entity when it is attached (or added) to the manager. You can change that behavior to suit your needs.
The new employee is in an invalid state when created; it lacks the required First and Last name values (the test displays these errors). Therefore the hasValidationErrors observable becomes true after adding the new employee to the manager and the hasValidationErrors observable raises a change notification that a Knockout UI would hear; these test shows these two points.