In breeze.debug.js:
function updateConcurrencyProperty(entity, property) {
...
else if (property.datatype === DataType.Binary) {
// best guess - that this is a timestamp column and is computed on the server during save
// - so no need to set it here.
return;
} else {
// this just leaves DataTypes of Boolean, String and Byte - none of which should be the
// type for a concurrency column.
// NOTE: thought about just returning here but would rather be safe for now.
throw new Error("Unable to update the value of concurrency property before saving: " + property.name);
}
i have a timestamp on my models. Why is this not recommended? An EF Tutorial on ASP.Net uses the timestamp.
What would you recommend as Concurrencycheck column?
Not sure where we say it's not recommended; the only issue is that its not terribly portable across databases, SQLServer 'timestamp' columns are not actually time based, ... but they should still work as concurrency columns with breeze. If not, its likely a bug.
For more info: datetime or timestamp
There was a bug involving SQL Server Timestamp columns that is now fixed in v 0.76.4. ... Thanks for finding this, Sascha.
Related
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 have overridden my ApplicationDbContext.SaveChanges() method. This helps me by providing immediately visible error messages on the yellow and white error application error screen.
But when there is a validation error with a DateTime column, the catch clause doesn't seem to be activated.
Why is that? And how might I identify the invalid column?
Override method
public partial class ApplicationDbContext
{
public override int SaveChanges()
{
try
{
return base.SaveChanges(); //**error is thrown here**//
}
catch (DbEntityValidationException ex)
{
var sb = new StringBuilder();
foreach (var failure in ex.EntityValidationErrors)
{
sb.AppendFormat("{0} failed validation\n", failure.Entry.Entity.GetType());
foreach (var error in failure.ValidationErrors)
{
sb.AppendFormat("- {0} : {1}", error.PropertyName, error.ErrorMessage);
sb.AppendLine();
}
}
throw new DbEntityValidationException(
"Entity Validation Failed - errors follow:\n" +
sb.ToString(), ex
);
}
}
}
However the exception isn't thrown with DateTime columns, it appears that the catch block is not being activated.
TL;DR
If you use Sql DATETIME columns, add validation to .Net DateTime properties on persisted entities to ensure values are between 1753 and 9999 prior to .SaveChanges()
Otherwise, change your Sql storage to DATE or DATETIME2, depending on your precision requirements.
In Detail
Given that the Sql Server DateTime data type can only store dates in the range 1753 - 9999, whereas the .Net DateTime struct can store a larger dynamic range, and higher precision, i.e. not all .Net DateTimes can be stored in Sql Server DATETIME.
Especially prone to this error is a .Net DateTime property on an EF entity which hasn't explicitly been assigned, as default(DateTime) is 0001/01/01.
Hence the preference to replace DATETIME columns with DATETIME2 or DATE storage as appropriate.
As a result, on a > Sql 2005 RDBMS, Entity Framework will default to a Sql DATETIME2 datatype as this will allow storage beyond the limited range offered by DATETIME and is closer to the .Net data type.
However, if you do have an existing table with a DATETIME column and you attempt to bind an 'out of band' DateTime value, annoyingly, Sql Server doesn't actually list the miscreant column name. If you are able to run Sql Profiler while you execute the statement (or paste to LinqPad), you can grab the actual Sql. But the likely candidate is a DateTime which hasn't been set to > 1753 (e.g. is unitialized as a default DateTime).
The reason why you aren't catching the Exception is because of the disjoint between catch DbEntityValidationException vs a thrown SqlException.
If I'm not mistaken you are using smalldatetime in MSSQL, which in some cases can throw this error.
Check this thread for the answer.
Try catching on catch(Exception ex) and there you'll be able to see the exact error message.
i'm displaying a server calculated value to the enduser by using propertyChanged event.
i was using breeze 1.4.8 and i'm using the productivity stack (ms sql, web api, ef)
It was working fine.
Recently i've updated to 1.4.12 and i recognized that this event doesn't get fired anymore.
The property "A_ProvisionTotal" gets calculated serverside only.
<snip>
var token = vm.transaction.entityAspect.propertyChanged.subscribe(propertyChanged);
function propertyChanged(propertyChangedArgs) {
var propertyName = propertyChangedArgs.propertyName;
if (vm.transaction.tblEmployees.CalculationMethod == "A" && propertyName == "A_ProvisionTotal")
logSuccess('Provision neuberechnet' + '<br/>' + 'Aktuell: ' + $filter('number')(vm.transaction.Provision, 2), true);
</snip>
Let me know if this is a known regression and if you need more snippets.
A couple of thoughts for how you could accomplish your desired functionality.
The entity could remember the last calculated value in a private field. Then whenever the recalculation gets triggered, you can compare the new value to the last calculated value and if there is no change, ignore the new calculated value.
Alternatively, you could define the properties involved in your calculation as ES5 properties in the entity ctor function and then trigger the calculation in the setter of the relevant properties, when they get set with a new value. More information here: http://www.breezejs.com/documentation/extending-entities#es5-property. ES5 properties are convenient if you want to build behavior such as your calculation into setters.
Update 3
This is not a bug - see the response to this post that describes this as a documented and deliberate behavior.
Update 2 June 2014
I overlooked a key fact in your question ... one that only became clear to me after I looked at the code you included in your comments. Let me extract the key pieces for other readers:
Your test issues a query, then saves an unrelated change to the server (where the property-of-interest is updated server-side), then checks if that telltale property-of-interest raises propertyChanged when the save result is merged back into cache.
var query = EntityQuery.from("Orders").where('id', 'eq', 10248);
query.using(em).execute().then(querySucceeded).then(checkPropertyChanged).fin(start);
// querySucceed receives order 10248, updates an unrelated property (so you can save),
// wires up a propertyChanged listener and saves, returning the saveChanges promise
function checkPropertyChanged(saveResults) {
var saved = saveResults.entities[0];
// this passes because the server-side change to `Freight` was returned
ok(saved && saved.Freight() === 1200.00,
"freight got changed serverside");
// this fails because Breeze did not notify you that the `Freight` had changed
ok(notifications[0].propertyName === "Freight",
"notified serverside change of Freight Property");
}
Summarizing, you expected that a property change on the server would trigger a propertyChanged notification on the client when the entity data are re-retrieved from the server as a by-product of saveChanges.
Do I have that right?
Our documentation was not clear on whether the merge of query, save, and import entity results would raise propertyChanged.
I discussed internally and confirmed that these operations SHOULD raise propertyChanged. I also wrote another (somewhat simpler) test that reveals the bug you discovered: that merged save results may not raise propertyChanged.
We'll look into it and tell you when we've fixed it. Thanks for discovering it.
Original
We have regression tests that show that the Breeze EntityAspect.propertyChanged event is raised in v.1.4.12. For example, you can see it at work in the DocCode sample, "basicTodoTests.js"; scroll to: "Breeze propertyChanged raised when any property changes".
Can you confirm that it really is a Breeze failure? Perhaps the property you are changing is not actually an entity property? Sometimes you think you are changing an entity (e.g, your Transaction entity) but the thing whose property you changed isn't actually an entity. Then the problem is that the data you thought would be mapped to a Transaction was not ... and you can start looking for that quite different problem.
In any case, I suggest that you write a small test to confirm your suspicion ... most importantly for yourself ... and then for us. That will help us discover what is different about your scenario from our scenarios. We'll fix it if you can find it. Thanks.
Actually, I'm not sure that this is a bug. Property change events DO get fired during a save merge but the property name parameter is documented as being 'null' when fired as a result of a save.
http://www.breezejs.com/sites/all/apidocs/classes/EntityAspect.html#event_propertyChanged
From the API Docs for the 'propertyName' parameter returned by EntityAspect.propertyChanged:
The name of the property that changed. This value will be 'null' for operations that replace the entire entity. This includes queries, imports and saves that require a merge. The remaining parameters will not exist in this case either.
What may have happened between 1.4.8 and 1.4.13 is that we actually implemented our design spec more carefully and probably introduced your breaking behavior. ( which we should have documented as such but likely missed).
Update by Ward
I updated the DocCode test which first confirmed the behavior described in your question and then confirmed the documented behavior.
We do regret that we apparently neglected to implement the documented behavior earlier and that we didn't mention the breaking change in our release notes (since updated).
Here's that test:
asyncTest("propertyChanged raised when merged save result changes a property", 3, function () {
var em = newTodosEm();
var todo = em.createEntity('TodoItem', {Description: "Saved description" });
em.saveChanges().then(saveSucceeded).catch(handleFail).finally(start);
ok(todo.entityAspect.isBeingSaved, "new todo is in the act of being saved");
// This change should be overwritten with the server value when the save result is returned
// even though the entity is in an Added state and the MergeStrategy is PreserveChanges
// because save expects to merge server values into an entity it is saving
todo.Description("Changed on client before save returns");
var descriptionChanged = false;
todo.entityAspect.propertyChanged.subscribe(function (changeArgs) {
// Watch carefully! The subscription is called twice during merge
// 1) propertyName === "Id" (assigned with permanent ID)
// 2) propertyName === null (WAT?)
// and not called with propertyName === "Description" as you might have thought.
// Actually 'null' means "merged a lot of properties"
// Documented: http://www.breezejs.com/sites/all/apidocs/classes/EntityAspect.html#event_propertyChanged
// The reason for this: don't want to fire a ton of events on whole entity load
// especially when merging many entities at the same time.
if (changeArgs.propertyName === null || changeArgs.propertyName === 'Description') {
descriptionChanged = true;
}
});
function saveSucceeded(saveResult) {
var saved = saveResult.entities[0];
// passes
equal(saved && saved.Description(), "Saved description",
"the merge after save should have restored the saved description");
// fails
ok(descriptionChanged,
"should have raised propertyChanged after merge/update of 'Description' property");
}
});
I'm setting a nullable datetime field on the server but that field is not being updated in my database.
private bool BeforeSaveLeaseEntry(Lease leaseEntry, EntityInfo info)
{
if (info.EntityState == EntityState.Added)
{
leaseEntry.CreatedDate = DateTime.UtcNow.ToLocalTime();
}
if (info.EntityState == EntityState.Modified)
{
leaseEntry.LastUpdatedDate = DateTime.UtcNow.ToLocalTime();
}
return true;
}
CreatedDate is not nullable and is updated.
LastUpdatedDate is nullable and is never updated.
The code is hit but when I run a tracer on my SQL server that field is never included in the update code.
exec sp_executesql N'update [dbo].[Leases]
set [ContractNo] = #0
where ([LeaseID] = #1)
',N'#0 varchar(25),#1 int',#0='test6',#1=27415
Sorry about this, in Breeze v 1.1.3 we added a the EntityInfo.ForceUpdate boolean property but it never made it into the main Breeze documentation, it only appeared in the release notes.
This property may be used to force a server side update of an entire entity when server side modification has been made to any property of an existing entity. The other approach that may be used is to explicitly update the EntityInfo.OriginalValuesMap.
The idea behind both of these is that on an update Breeze only creates an update statement for those se properties that have been changed. Any client side changes are automatically detected because of Breeze's tracking mechanism which adds an entry into an 'originalValuesMap', but this cannot be done automatically for server side changes because the server side entities are not instrumented to perform notification about property changes.
The "EntityInfo.ForceUpdate" method forces the generation of an update statement for every property on the entity, whereas directly updating the EntityInfo.OriginalValuesMap will only update those properties found in the map.
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?