I create a new Entity in EntityManager like this:
var newCust = manager.createEntity('Customer', { Name: 'MyNewCustomer' });
it's Id is correctly generated. On a Button-Click I save the Entity on the Server:
manager.saveChanges([newCust]).then(function (saveResult) {
alert('saved!');
}
Now everything works perfect BUT, the Entity in newCust keeps it's EntityState "Added". It doesn't change to "Unchanged".
I've debugged a little and found out, that in the function "mergeEntity(mc, node, meta)" of breeze, where the following code happens - no entity is found (eventhough the key it searches is the same Id - the Id-Fixup worked correctly):
var targetEntity = em.findEntityByKey(entityKey);
So everytime I save my new Entity, a new Entity will be created in my EntityManager. When I reload the page, the entity is flagged correctly and works. The only problem is, that a new entity will be saved everytime I save changes.
Thanks for any help!
Edit 1:
I found the problem and I'm not sure what exactly Breeze expects of me. When my ContextProvider saves the Entity it sends a DB-Update-Command (for all the properties) and then returns. The problem is, that the Id of the saveResult-Object is still the "tempId" and not the real one - so when Breeze on Client-Side receives the object it doesn't find it in the entityManager anymore because the Key-Fixup happened already.
What exactly is Breeze expecting to receive from the saveResult? A representation of the object as it is in the Database at this moment? - would make sense but I don't find it documented.
Edit 2:
It looks like I'm not able to replace the object in the saveWorkState.saveMap (EntityInfo info.Entity is readonly). What I would like to do is create the newly added object and return this object instead of the one that breeze sent me. I have calculated values on the newly created object and the new "real" Id. What the NoDB-Sample seems to do is just overwrite the Id for the newId but any other properties are not changing. Maybe I'm not using this mechanism right?
To make sure, that all EntityInfos that are returned in my SaveResult are the latest representation of the Database I just clear the saveWorkState.SaveMap-Lists
saveWorkState.SaveMap[n].Clear()
and add the new representations
saveWorkState.SaveMap[n].AddRange(newEntityInfos);
and for every Entity that is Added or Modified I create a new EntityInfo with the object that is returned by the Database
private EntityInfo SaveEntity(List<KeyMapping> keyMap, EntityInfo info) {
EntityInfo result = info;
switch (info.EntityState) {
case EntityState.Added: {
// CreateInDatabase
// Possible changes in object properties happen (for calculated values)
...
var newObj = GetObjectAgainFromDatabase(info.Entity);
keyMap.Add(new KeyMapping() { EntityTypeName = bu.RuntimeClass.FullName, TempValue = (MyObject)info.Entity.Id, RealValue = newObj.Id });
result = CreateEntityInfo(newObj, EntityState.Added);
break;
}
case EntityState.Deleted: {
// Delete in Database
// EntityInfo doesn't have to change
break;
}
case EntityState.Modified: {
// Update in Database
result = CreateEntityInfo(bu.WrappedPOCO, EntityState.Modified);
break;
}
default: //EntityState.Detached, EntityState.Unchanged
break;
}
return result;
}
Any comments on this solution are welcome!
Related
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?
I am working on a Hottowel project and I want to format some data passed on from database to Breeze, but it looks like the ctor is not getting registered.
What am I doing wrong?
In datacontext.js:
var manager = configureManager();
function configureManager() {
var mng = new breeze.EntityManager('breeze/data');
breeze.NamingConvention.camelCase.setAsDefault();
model.configureMetadataStore(mng.metadataStore);
return mng;
}
In model.js:
function configureMetadataStore(metadataStore) {
metadataStore.registerEntityTypeCtor
('UserInfo', null, userInfoInitializer);
}
function userInfoInitializer() {
if (this.Email == "")
this.Email = '---';
this.CreateDate = formatDateTime(this.CreateDate);
}
function formatDateTime(DateTime) {
return moment.utc(DateTime).format('DD. MM. YYYY. [у] hh:mm');
}
Datacontext has a reference to model, the data is transferred from database and appears on the screen, but is not formatted. console.log() calls from userInfoInitializer() are not appearing.
When I am constructing an entity, my constructor needs to have an entity to construct. I have not tried your above code but I believe that Breeze passes an entity in and you need to give that entity properties. Using this. MAY work but it is the first thing that stands out to me.
function userInfoInitializer(user) {
if (user.Email == "")
user.Email = "---";
2nd - What is your entity named in your model? Is it UserInfo or just User? You probably already know this but you need to make sure when you are adding a constructor you use the properly named Entity.
3rd - If you are using camelCase then you need to leave the first letter of the property lowercase. Ex . user.email and user.createDate.
Last, I can't tell if you are creating a 'createDate' in the constructor or that is being passed from your model. If it is indeed a property you are creating I would recommend making it an knockout observable or computed property. If it is coming from the database then you need to do something like
if (!user.createDate) { } //set it
Remember that all of the entities being returned from the database will be given that property, so if you have entities that already have a createDate in your example you are overriding that date. If you want to set createDate to now then I would move that into your method where you are creating the object.
Is there an equivalent of Rails ActiveRecord::Callbacks in ASP MVC?
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
I'm in a situation where we are not using identities for our primary key. We do this for reasons specific to our DB sharding design. Because of this we have a lookup table to find the next ID for a specific table. I'd like to automatically get this value and set it in an abstract class whenever a model is created/updated and before it is saved. I also need to update the lookup table with an incremented 'nextID' after the save is successful.
I'm open to other solutions on how to do this without callbacks as well.
So you need the callback just to increment ID in the lookup table? AFAIK there is no equivalent in ASP.NET, may be you could try with Async Controllers (http://msdn.microsoft.com/en-us/library/ee728598%28v=vs.100%29.aspx) and wait for a state change from the successful save, but I would prefer use a service specifically for this like Snowflake (https://github.com/twitter/snowflake/).
I found a solution using overrides as opposed to callbacks. It's my hope that ASP mvc adds support for callbacks as the framework continues to mature because callbacks allow for cleaner code by allowing the OnSave event to exist in the model[s] that the event is concerned with rather than the centralized DbContext class (separation of concerns).
Solution:
The SaveChanges method can be overridden in the Context Class (Entity Framework Power Tools creates the Context class is the 'Models' directory).
public override int SaveChanges()
{
// create a cache for id values in case their are multiple added entries in the dbcontext for the same entitytype
Dictionary<string, UniqueID> idCache = new Dictionary<string, UniqueID>();
IEnumerable<DbEntityEntry> changes = this.ChangeTracker.Entries();
foreach (var entry in changes)
{
//check if this is a new row (do nothing if its only a row update because there is no id change)
if (entry.State == System.Data.EntityState.Added)
{
//determine the table name and ID field (by convention)
string tableName = entry.Entity.GetType().Name;
string idField = entry.Entity.GetType().Name + "ID";
UniqueID id = null;
//if we've already looked this up, then use the cache
if (idCache.ContainsKey(tableName))
{
id = idCache[tableName];
}
//if we havn't looked this up before get it and add it to the cache
else
{
id = this.UniqueIDs.Find(tableName, idField);
//if it doesn't already exist in the lookup table create a new row
if (id == null)
{
id = new UniqueID(tableName, idField, 1);
// since this is a new entry add it
this.UniqueIDs.Add(id);
}
else
{
// set the state to modified
this.Entry(id).State = System.Data.EntityState.Modified;
}
}
entry.CurrentValues[tableName + "ID"] = id.NextID;
id.NextID = id.NextID + 1;
}
}
return base.SaveChanges();
}
I am having trouble saving my entities after updating them. I can add new entities like this: add(student); but if I tried this:
if (ModelState.IsValid)
{
db.Entry(student).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("someView");
}
I get this error message:
System.Data.Entity.Infrastructure.DbUpdateConcurrencyException was unhandled by user code
Message=Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or
deleted since entities were loaded. Refresh ObjectStateManager
entries.
Here’s my controller method:
[HttpPost]
public ActionResult ClassAttendance(InstructorIndexData viewModel, FormCollection frmcol)
{
var instructorData = new InstructorIndexData();
string[] AllFstMNames = frmcol["item.Student.FirstMidName"].Split(',');
string[] AllLstNames = frmcol["item.Student.LastName"].Split(',');
string[] AllAddresses = frmcol["item.Student.Address"].Split(',');
string[] AllEnrollmentDates = frmcol["item.Student.EnrollmentDate"].Split(',');
//more of the same code…
var student = new Student();
var enrollment = new Enrollment();
for ( int i = 0; i < AllFstMNames.Count(); i++)
{
student.FirstMidName = AllFstMNames[i];
student.LastName = AllLstNames[i];
student.Address = AllAddresses[i];
student.EnrollmentDate = Convert.ToDateTime(AllEnrollmentDates[i]);
if (!string.IsNullOrEmpty(frmcol["item.Grade"]))
{
enrollment.Grade = Convert.ToInt32(AllGrades[i]);
}
enrollment.StudentID = Convert.ToInt32(AllStudentIds[i]);
enrollment.attendanceCode = Convert.ToInt32(AllAttendanceCodes[i]);
enrollment.classDays = AllclassDays[i];
enrollment.CourseID = Convert.ToInt32 (AllCourseIds[i]);
//update rows
}
if (ModelState.IsValid)
{
db.Entry(student).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("someView");
}
Can you help me with just being able to update values in the database?
While I was looking at the code here, my initial thought is that it doesn't seem quite right to have a for loop that updates the student and enrollment objects multiple times and then to have only one call to db.SaveChanges outside the loop. This is concerning because only the last iteration of the for loop will be applied when the data is saved to the database. (You have a comment to "update rows" at the end of the for loop - perhaps some code is missing or misplaced?)
Then, I started thinking about why it would be necessary to manually set the Entry(...).State property. Wouldn't the db automatically know that an object is modified and needs to be saved? That lead me to this question: Where is db defined? What technology stack is being used there?
Finally, after making an assumption that the db object might work something like the MS LINQ-to-SQL feature, I noticed that the the student object is newly instantiated before the for loop. This is fine for inserting new data, but if you are wanting to update existing data, I believe you need to first get a copy of the object from the database and then update the properties. This allows the db object to monitor the changes (again, assuming that it has this capability). (If this is not the case, then it leads me to wonder how the db will know which record in the database to update since you are not setting anything that appears to be a primary key, such as StudentId, on the student object in the loop.)
I am using asp.net mvc for an application. I've taken some guidance from Rob Conery's series on the MVC storefront. I am using a very similar data access pattern to the one that he used in the storefront.
However, I have added a small difference to the pattern. Each class I have created in my model has a property called IsNew. The intention on this is to allow me to specify whether I should be inserting or updating in the database.
Here's some code:
In my controller:
OrderService orderService = new OrderService();
Order dbOrder = orderService.GetOrder(ID);
if (ModelState.IsValid)
{
dbOrder.SomeField1 = "Whatever1";
dbOrder.SomeField2 = "Whatever2";
dbOrder.DateModified = DateTime.Now;
dbOrder.IsNew = false;
orderService.SaveOrder(dbOrder);
}
And then in the SQLOrderRepository:
public void SaveOrder(Order order)
{
ORDER dbOrder = new ORDER();
dbOrder.O_ID = order.ID;
dbOrder.O_SomeField1 = order.SomeField1;
dbOrder.O_SomeField2 = order.SomeField2;
dbOrder.O_DateCreated = order.DateCreated;
dbOrder.O_DateModified = order.DateModified;
if (order.IsNew)
db.ORDERs.InsertOnSubmit(dbOrder);
db.SubmitChanges();
}
If I change the controller code so that the dbOrder.IsNew = true; then the code works, and the values are inserted correctly.
However, if I set the dbOrder.IsNew = false; then nothing happens...there are no errors - it just doesn't update the order.
I am using DebuggerWriter here: http://www.u2u.info/Blogs/Kris/Lists/Posts/Post.aspx?ID=11 to trace the SQL that is being generated, and as expected, when the IsNew value is true, the Insert SQL is generated and executed properly. However, when IsNew is set to false, there appears to be no SQL generated, so nothing is executed.
I've verified that the issue here (LINQ not updating on .SubmitChanges()) is not the problem.
Any help is appreciated.
In your SaveOrder method you are always creating a new ORDER object. You need to change this so that if order.IsNew is false, it retrieves the existing one from the DB and updates it instead.
public void SaveOrder(Order order)
{
ORDER dbOrder;
if (order.IsNew)
{
dbOrder = new ORDER();
dbOrder.O_ID = order.ID;
}
else
{
dbOrder = (from o in db.ORDERS where o.O_ID == order.ID select o).Single();
}
dbOrder.O_SomeField1 = order.SomeField1;
dbOrder.O_SomeField2 = order.SomeField2;
dbOrder.O_DateCreated = order.DateCreated;
dbOrder.O_DateModified = order.DateModified;
if (order.IsNew)
db.ORDERs.InsertOnSubmit(dbOrder);
db.SubmitChanges();
}
I think you have the problem that your entity is detached from your context.
You should try to attach your entity back to your context if you want to update. The downside of LINQtoSQL is that for the re-attachment you'll need the original state of the object when it was detached...
Another solution is to re-get your entity from the context and copy all the data from your entity in the parameter. This will do until you'll have more complex entities.
What tvanfosson said.
I would just like to add that I use logic where if Id equals default(0 or Empty if using guids), then I assume it is new. Otherwise if I have the id passed in, then I go get the existing object and update it.