Query DB loads one of my object's properties and it shouldn't - asp.net-mvc-3

In my Edit Controller Action, I post the object to update.
[HttpPost]
public virtual ActionResult Edit(Case myCase){
var currentDocuments = db.CaseDocuments.Where(p => p.idCase == myCase.idCase);
foreach (CaseDocument docInDB in currentDocuments )
{
var deleteDoc = true;
foreach (CaseDocument docNew in myCase.CaseDocuments )
{
if (docNew.idDocument == docInDB.idDocument)
deleteDoc = false;
}
if (deleteDoc )
db.CaseDocuments.Remove(docInDB);
}
foreach (CaseDocument pc in myCase.CaseDocuments)
{
if (pc.idDocument == 0)
db.CaseDocuments.Add(pc);
else
db.Entry(pc).State = EntityState.Modified;
}
*** **db.Entry(myCase).State = EntityState.Modified;** //THIS LINE
db.SaveChanges();
}
The Case model has a collection of Documents, and they are posted along with the Case Model.
As soon I enter the action, I can count the number of documents in the collection, and lets say there are 3.
Then, in order to see if I need to delete documents from database (as the user deleted one from UI), I need to get the Documents for that case from database in this way:
var currentDocuments = db.CaseDocuments.Where(p => p.idCase == myCase.idCase);
And here starts the weird thing: as soon I executa that statement, the myCase.Documents is loaded with what it is in database (lets say there are 4)!! So, I'm not able to compare the 2 collections (to detect if a document was deleted and remove it from db).
What I need is during the Edit Action of my Case model, I need to create/update/modify its documents. Do I need to see this from other angle? What I'm doing is wrong?
EDIT:
After the comments, I realized that the line where I marked myCase as Modified, was at the begining, and I suppose that this was the reason for that behaviour.
Now, moving that line to just before the db.SaveChanges(), fixed that problem, but at the db.Entry(myCase).State = EntityState.Modified; says "There is already an object with the same key in ObjectStateManager. "
What am I doing wrong here? This code looks bad!

Try it this way:
[HttpPost]
public virtual ActionResult Edit(Case myCase){
var currentDocumentIds = db.CaseDocuments
.Where(p => p.idCase == myCase.idCase)
.Select(p => p.idDocument);
foreach (int idInDb in currentDocumentsIds.Where(i => !myCase.CaseDocuments
.Any(ci => ci.idDocumnet == i))
{
var docToDelete = new CaseDocument { idDocument = idInDb };
db.CaseDocuments.Remove(docToDelete);
}
foreach (CaseDocument pc in myCase.CaseDocuments)
{
if (pc.idDocument == 0)
db.CaseDocuments.Add(pc);
else
db.Entry(pc).State = EntityState.Modified;
}
db.Entry(myCase).State = EntityState.Modified;
db.SaveChanges();
}
Edit: The difference between this code and your code is the way how it works with existing documents. It doesn't load them - it loads just their ids. This way you will save some data transfer from database but it should also help you avoiding that exception. When you load the document from the database you have it already attached in the context but if you try to call this:
db.Entry(pc).State = EntityState.Modified;
you will try to attach another instance of the document with the same key to the context. That is not allowed - context can have attached only single instance with unique key.

Related

Many-To-Many Entity Framework Update

I have an object that has a many-to-many relationship with another object. I am trying to write an update statement that doesn't result in having to delete all records from the many-to-many table first.
My data is:
StoredProcedure - StoredProcedureId, Name
Parameter - ParameterId, Name
StoredProcedure_Parameter - StoredProcedureId, ParameterId, Order
I have a UI for updating a stored procedured object (adding/removing parameters or changing the order of the parameters).
When I save, I end up at:
var storedProcedure = context.Sprocs.FirstOrDefault(s => s.SprocID == sproc.StoredProcedureId);
if (storedProcedure == null)
{
//do something like throw an exception
} else
{
storedProcedure.Name = sproc.Name;
//resolve Parameters many to many here
//remove all Params that are not in sproc.Params
//Add any params that are in sproc.Params but not in storedProcedure.Params
//Update the Order number for any that are in both
}
I know I could simply call .Clear() on the table and then reinsert all of the values with their current state (ensuring that all parameters that were removed by the UI are gone, new ones are added, and updated Orders are changed). However, I feel like there must be a better way to do this. Do many-to-many updates with EF usually get resolved by deleting all of the elements and reinserting them?
Here there is my code that I use and it works. The difference is that instead o having your 3 tables( StoredProcedure, StoredProcedure_Parameter and Parameter ) I have the following 3 tables: Order, OrdersItem(this ensure the many-to-many relation) and Item. This is the procedure that I used for updating or add an order, or after I change an existing OrderItem or add a new one to the Order.
public void AddUpdateOrder(Order order)
{
using (var db = new vitalEntities())
{
if (order.OrderId == 0)
{
db.Entry(order).State = EntityState.Added;
}
else
{
foreach (var orderItem in order.OrdersItems)
{
if (orderItem.OrderItemsId == 0)
{
orderItem.Item = null;
if (order.OrderId != 0)
orderItem.OrderId = order.OrderId;
db.Entry(orderItem).State = EntityState.Added;
}
else
{
orderItem.Order = null;
orderItem.Item = null;
db.OrdersItems.Attach(orderItem);
db.Entry(orderItem).State = EntityState.Modified;
}
}
db.Orders.Attach(order);
db.Entry(order).State = EntityState.Modified;
}
SaveChanges(db);
}
}

MVC3 Entity Framework Code First Updating Subset Related List of Items

I have a table of data with a list of key value pairs in it.
Key Value
--------------------
ElementName PrimaryEmail
Email someemail#gmail.ca
Value Content/Images/logo-here.jpg
I am able to generate new items on my client webpage. When, I create a new row on the client and save it to the server by executing the following code the item saves to the database as expected.
public ViewResult Add(CardElement cardElement)
{
db.Entry(obj).State = EntityState.Added;
db.SaveChange();
return Json(obj);
}
Now, when I want to delete my objects by sending another ajax request I get a failure.
public void Delete(CardElement[] cardElements)
{
foreach (var cardElement in cardElements)
{
db.Entry(cardElement).State = EntityState.Deleted;
}
db.SaveChanges();
}
This results in the following error.
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.
I have tried other ways of deleting including find by id remove and attach and delete but obviously I am approaching in the right fashion.
I am not sure what is causing your issue, but I tend to structure my deletes as follows:
public void Delete(CardElement[] cardElements)
{
foreach (var cardElement in cardElements)
{
var element = db.Table.Where(x => x.ID == cardElement.ID).FirstOrDefault();
if(element != null)
db.DeleteObject(element);
}
db.SaveChanges();
}
although I tend to do database first development, which may change things slightly.
EDIT: the error you are receiving states that no rows were updated. When you pass an object to a view, then pass it back to the controller, this tends to break the link between the object and the data store. That is why I prefer to look up the object first based on its ID, so that I have an object that is still linked to the data store.

Could this be a bug?

I have the following test case
[TestMethod()]
[DeploymentItem("Courses.sdf")]
public void RemoveCourseConfirmedTest()
{
CoursesController_Accessor target = new CoursesController_Accessor();
int id = 50;
ActionResult actual;
CoursesDBContext db = target.db;
Course courseToDelete = db.Courses.Find(id);
List<CourseMeet> meets = courseToDelete.meets.ToList<CourseMeet>();
actual = target.RemoveCourseConfirmed(courseToDelete);
foreach (var meet in meets)
{
Assert.IsNull(db.Meets.find(meet));
}
Assert.IsNull(db.Courses.Find(courseToDelete.courseID));
}
Which tests the following method from my controller.
[HttpPost, ActionName("RemoveCourse")]
public ActionResult RemoveCourseConfirmed(Course course)
{
try
{
db.Entry(course).State = EntityState.Deleted;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DbUpdateConcurrencyException)
{
return RedirectToAction("RemoveMeet", new System.Web.Routing.RouteValueDictionary { { "concurrencyError", true } });
}
catch (DataException)
{
ModelState.AddModelError(string.Empty, "Unable to save changes. Try again.");
return View(course);
}
}
I know i should be using a Mock db .... but for this project I have decided to go with this approach.
So this what happens. When I run the actual web site this function works perfectly fine and removes the course and all the meets that belong to it.
But when I run the test i get the following exception
System.InvalidOperationException: The operation failed: The relationship could not be
changed because one or more of the foreign-key properties is non-nullable. When a change is
made to a relationship, the related foreign-key property is set to a null value. If the
foreign-key does not support null values, a new relationship must be defined, the foreign-
key property must be assigned another non-null value, or the unrelated object must be
deleted.
Here is the even more interesting part if I comment out the following line from the test
List<CourseMeet> meets = courseToDelete.meets.ToList<CourseMeet>();
and replace the loop with the following:
foreach (var meet in db.Meets.ToList())
{
Assert.IsFalse(meet.courseID == courseToDelete.courseID);
}
I dont get any exceptions and the test case passess.
Am I missing something about Entity Framework or is this a bug?
Well this has been open for a while now. I still haven't been able to find a definite answer but working more with MVC and EF i think what is happening is that once i execute the line
List<CourseMeet> meets = courseToDelete.meets.ToList<CourseMeet>();
the meets get loaded into the object manager and hence when the parent object is deleted the no longer have a reference to the parent course.

Add an entity from another DataContex in linq

I am trying to merge data between two identical schema databases using Linq-to-sql:
List<Contact> contacts = (from c in oldDb.Contact
select c).ToList();
contacts.ForEach(c => c.CreatedByID = 0);
newDb.Contact.InsertAllOnSubmit(contacts);
newDb.SubmitChanges();
Merely throws an "An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported." exception.
Other than doing the following, how else can this be done generically (in reasonable execution time):
List<Contact> contacts = (from c in oldDb.Contact
select c).ToList();
contacts.ForEach(c => { c.CreatedByID = 0; newDb.Contact.InsertAllOnSubmit(contacts); });
newDb.SubmitChanges();
along with:
private t GetNewObject<t>(t oldObj)
{
t newObj = (t)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(typeof(t).Name);
PropertyInfo[] props = typeof(t).GetProperties();
foreach (PropertyInfo _prop in props)
{
_prop.SetValue(newObj, _prop.GetValue(oldObj, null), null);
}
return newObj;
}
The problem is this method is rather slow when there's only 11 objects and 75 properties, I need to do this for a couple hundred thousand objects so any performance gains I can get at this end would greatly reduce overall run time.
Basically, is there any Detach or similar call I could do that will disconnect the existing objects from the old DataContext and connect them to the new DataContext. Without having to create all new objects for each and every one of the returned rows.
I didnt got the
contacts.ForEach(c => { c.CreatedByID = 0; newDb.Contact.InsertAllOnSubmit(contacts); });
shouldnt be something like
contacts.ForEach(c => {
Contact c2 = GetNewObject<Contact>(c);
c2.CreatedByID = 0;
newDb.Contact.InsertOnSubmit(c2);
});
also, here's a way of detaching the object from the old database: http://omaralzabir.com/linq_to_sql__how_to_attach_object_to_a_different_data_context/

linqToSql related table not delay loading properly. Not populating at all

I have a couple of tables with similar relationship structure to the standard Order, OrderLine tables.
When creating a data context, it gives the Order class an OrderLines property that should be populated with OrderLine objects for that particular Order object.
Sure, by default it will delay load the stuff in the OrderLine property but that should be fairly transparent right?
Ok, here is the problem I have: I'm getting an empty list when I go MyOrder.OrderLines but when I go myDataContext.OrderLines.Where(line => line.OrderId == 1) I get the right list.
public void B()
{
var dbContext = new Adis.CA.Repository.Database.CaDataContext(
"<connectionString>");
dbContext.Connection.Open();
dbContext.Transaction = dbContext.Connection.BeginTransaction();
try
{
//!!!Edit: Imortant to note that the order with orderID=1 already exists
//!!!in the database
//just add some new order lines to make sure there are some
var NewOrderLines = new List<OrderLines>()
{
new OrderLine() { OrderID=1, LineID=300 },
new OrderLine() { OrderID=1, LineID=301 },
new OrderLine() { OrderID=1, LineID=302 },
new OrderLine() { OrderID=1, LineID=303 }
};
dbContext.OrderLines.InsertAllOnSubmit(NewOrderLines);
dbContext.SubmitChanges();
//this will give me the 4 rows I just inserted
var orderLinesDirect = dbContext.OrderLines
.Where(orderLine => orderLine.OrderID == 1);
var order = dbContext.Orders.Where(order => order.OrderID == 1);
//this will be an empty list
var orderLinesThroughOrder = order.OrderLines;
}
catch (System.Data.SqlClient.SqlException e)
{
dbContext.Transaction.Rollback();
throw;
}
finally
{
dbContext.Transaction.Rollback();
dbContext.Dispose();
dbContext = null;
}
}
So as far as I can see, I'm not doing anything particularly strange but I would think that orderLinesDirect and orderLinesThroughOrder would give me the same result set.
Can anyone tell me why it doesn't?
You're just adding OrderLines; not any actual Orders. So the Where on dbContext.Orders returns an empty list.
How you can still find the property OrderLines on order I don't understand, so I may be goofing up here.
[Edit]
Could you update the example to show actual types, especially of the order variable? Imo, it shoud be an IQueryable<Order>, but it's strange that you can .OrderLines into that. Try adding a First() or FirstOrDefault() after the Where.

Resources