EF 6.0 how to cancel delete and modify property? - entity-framework-6

I have a Table Table1 with column State which 1 means normal data and 10 means removed data,
and I want to cancel delete operation and update this column to 10 when I deleting it in EF Database First:
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.CurrentValues["State"] = 10;
break;
default: break;
}
}
return base.SaveChanges();
}
and delete like this:
var data = dbContext.Table1.Find(primaryKey);
dbContext.Table1.Remove(data);
dbContext.SaveChanges();
before base.SaveChanges() I checked State value in entry.Entity, it has been changed from 1 to 10, but after SaveChanges, State value in database didn't changed (still 1).
how can I make this work?

Related

Best Way to Update only modified fields with Entity Framework

Currently I am doing like this:
For Example:
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
db.Entry(oldobj).CurrentValues.SetValues(model);
}
It works, but for example,
I have 50 columns in my table but I displayed only 25 fields in my form (I need to partially update my table, with remaining 25 column retain same old value)
I know it can be achieve by "mapping columns one by one" or by creating "hidden fields for those remaining 25 columns".
Just wondering is there any elegant way to do this with less effort and optimal performance?
This is a very good question. By default I have found that as long as change tracking is enabled (it is by default unless you turn it off), Entity Framework will do a good job of applying to the database only what you ask it to change.
So if you only change 1 field against the object and then call SaveChanges(), EF will only update that 1 field when you call SaveChanges().
The problem here is that when you map a view model into an entity object, all of the values get overwritten. Here is my way of handling this:
In this example, you have a single entity called Person:
Person
======
Id - int
FirstName - varchar
Surname - varchar
Dob - smalldatetime
Now let's say we want to create a view model which will only update Dob, and leave all other fields exactly how they are, here is how I do that.
First, create a view model:
public class PersonDobVm
{
public int Id { get; set; }
public DateTime Dob { get; set; }
public void MapToModel(Person p)
{
p.Dob = Dob;
}
}
Now write the code roughly as follows (you'll have to alter it to match your context name etc):
DataContext db = new DataContext();
Person p = db.People.FirstOrDefault();
// you would have this posted in, but we are creating it here just for illustration
var vm = new PersonDobVm
{
Id = p.Id, // the Id you want to update
Dob = new DateTime(2015, 1, 1) // the new DOB for that row
};
vm.MapToModel(p);
db.SaveChanges();
The MapToModel method could be even more complicated and do all kinds of additional checks before assigning the view model fields to the entity object.
Anyway, the result when SaveChanges is called is the following SQL:
exec sp_executesql N'UPDATE [dbo].[Person]
SET [Dob] = #0
WHERE ([Id] = #1)
',N'#0 datetime2(7),#1 int',#0='2015-01-01 00:00:00',#1=1
So you can clearly see, Entity Framework has not attempted to update any other fields - just the Dob field.
I know in your example you want to avoid coding each assignment by hand, but I think this is the best way. You tuck it all away in your VM so it does not litter your main code, and this way you can cater for specific needs (i.e. composite types in there, data validation, etc). The other option is to use an AutoMapper, but I do not think they are safe. If you use an AutoMapper and spelt "Dob" as "Doob" in your VM, it would not map "Doob" to "Dob", nor would it tell you about it! It would fail silently, the user would think everything was ok, but the change would not be saved.
Whereas if you spelt "Dob" as "Doob" in your VM, the compiler will alert you that the MapToModel() is referencing "Dob" but you only have a property in your VM called "Doob".
I hope this helps you.
I swear by EntityFramework.Extended. Nuget Link
It lets you write:
db.Person
.Where(x => x.ID == model.ID)
.Update(p => new Person()
{
Name = newName,
EditCount = p.EditCount+1
});
Which is very clearly translated into SQL.
Please try this way
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
// Newly Inserted Code
var UpdatedObj = (Person) Entity.CheckUpdateObject(oldobj, model);
db.Entry(oldobj).CurrentValues.SetValues(UpdatedObj);
}
public static object CheckUpdateObject(object originalObj, object updateObj)
{
foreach (var property in updateObj.GetType().GetProperties())
{
if (property.GetValue(updateObj, null) == null)
{
property.SetValue(updateObj,originalObj.GetType().GetProperty(property.Name)
.GetValue(originalObj, null));
}
}
return updateObj;
}
I have solved my Issue by using FormCollection to list out used element in form, and only change those columns in database.
I have provided my code sample below; Great if it can help someone else
// Here
// collection = FormCollection from Post
// model = View Model for Person
var result = db.Person.Where(x => x.ID == model.ID).SingleOrDefault();
if (result != null)
{
List<string> formcollist = new List<string>();
foreach (var key in collection.ToArray<string>())
{
// Here apply your filter code to remove system properties if any
formcollist.Add(key);
}
foreach (var prop in result.GetType().GetProperties())
{
if( formcollist.Contains(prop.Name))
{
prop.SetValue(result, model.GetType().GetProperty(prop.Name).GetValue(model, null));
}
}
db.SaveChanges();
}
I still didn't find a nice solution for my problem, so I created a work around. When loading the Entity, I directly make a copy of it and name it entityInit. When saving the Entity, I compare the both to see, what really was changed. All the unchanged Properties, I set to unchanged and fill them with the Database-Values. This was necessary for my Entities without Tracking:
// load entity without tracking
var entityWithoutTracking = Context.Person.AsNoTracking().FirstOrDefault(x => x.ID == _entity.ID);
var entityInit = CopyEntity(entityWithoutTracking);
// do business logic and change entity
entityWithoutTracking.surname = newValue;
// for saving, find entity in context
var entity = Context.Person.FirstOrDefault(x => x.ID == _entity.ID);
var entry = Context.Entry(entity);
entry.CurrentValues.SetValues(entityWithoutTracking);
entry.State = EntityState.Modified;
// get List of all changed properties (in my case these are all existing properties, including those which shouldn't have changed)
var changedPropertiesList = entry.CurrentValues.PropertyNames.Where(x => entry.Property(x).IsModified).ToList();
foreach (var checkProperty in changedPropertiesList)
{
try
{
var p1 = entityWithoutTracking.GetType().GetProperty(checkProperty).GetValue(entityWithoutTracking);
var p2 = entityInit.GetType().GetProperty(checkProperty).GetValue(entityInit);
if ((p1 == null && p2 == null) || p1.Equals(p2))
{
entry.Property(checkProperty).CurrentValue = entry.Property(checkProperty).OriginalValue; // restore DB-Value
entry.Property(checkProperty).IsModified = false; // throws Exception for Primary Keys
}
} catch(Exception) { }
}
Context.SaveChanges(); // only surname will be updated
This is way I did it, assuming the new object has more columns to update that the one we want to keep.
if (theClass.ClassId == 0)
{
theClass.CreatedOn = DateTime.Now;
context.theClasses.Add(theClass);
}
else {
var currentClass = context.theClasses.Where(c => c.ClassId == theClass.ClassId)
.Select(c => new TheClasses {
CreatedOn = c.CreatedOn
// Add here others fields you want to keep as the original record
}).FirstOrDefault();
theClass.CreatedOn = currentClass.CreatedOn;
// The new class will replace the current, all fields
context.theClasses.Add(theClass);
context.Entry(theClass).State = EntityState.Modified;
}
context.SaveChanges();
In EF you can do like this
var result = db.Person.Where(x => x.ID == model.ID).FirstOrDefault();
if(result != null){
result.Name = newName;
result.DOB = newDOB;
db.Person.Update(result);
}
Or you can use
using (var db= new MyDbContext())
{
var result= db.Person.Where(x => x.ID == model.ID).FirstOrDefault();
result.Name= newName;
result.DOB = newDOB;
db.Update(result);
db.SaveChanges();
}
For more detail please EntityFramework Core - Update Only One Field
No Worry guys
Just write raw sql query
db.Database.ExecuteSqlCommand("Update Person set Name='"+_entity.Name+"' where Id = " + _entity.ID + "");

SaveChanges is not working on modify the data

when i try to save the the new Student object. it works fine but when i am trying to modify the data, it doesn't update the data. Instead none error is thrown on SaveChanges.
i am using code first approach (using mysql provider) and here it is the complete source.
https://www.dropbox.com/s/e34frntq8u5gsmh/SchoolManagementSystem.rar?dl=0
my tired code is this :
public ActionResult Create(Student student, HttpPostedFileBase Image)
{
try
{
if (ModelState.IsValid)
{
student = db.Students.Find(student.ID);
if (student.ID > 0)
{
db.Entry(student).State = EntityState.Modified;
db.SaveChanges();
}
else
{
if (student.Basic == null) student.Basic = new BasicInformation();
if (Image != null && Image.ContentLength > 0)
{
student.Basic.PictureUrl = Image.FileName;
string path = Server.MapPath(("~/Images/"));
Image.SaveAs(path + Image.FileName);
}
db.Students.Add(student);
db.SaveChanges();
}
return RedirectToAction("StudentList");
}
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View(student);
}
This seems to be the modify scenario :
if (student.ID > 0)
{
db.Entry(student).State = EntityState.Modified;
db.SaveChanges();
}
Eliminate this line of code :
db.Entry(student).State = EntityState.Modified;
and instead add code that updates the object with the new modified values.
Hope this helps.
By this line
student = db.Students.Find(student.ID);
you overwrite the student that enters the method and you lose all changes. You can do two things in stead:
Only remove the line. Your code will now attach the modified student as EntityState.Modified.
Fetch the original student from the database and copy the modified values to it:
var studentOrg = db.Students.Find(student.ID);
db.Entry(studentOrg).CurrentValues.SetValues(student);
(both SaveChanges calls can be moved to one just before return RedirectToAction...)
Option 1 will generate an update statement containing all Student's fields, but not have a roundtrip to get the original record.
Option 2 has this roundtrip, but only updates modified fields. This (option 2) can be beneficial when changes are audited or when concurrency should be minimized.

Lambda expression for getting a table row and update the row in database

I am trying to update my DB table and from my model I am getting the id
public void Update(Abc model)
{
//Database Table Instance
Abc a=new Abc();
//Trying to update the column where id =id
try
{
a.id = model.Asset.id;
a.column = "R";
db.Entry(a).State = EntityState.Modified;
db.SaveChanges();
}
catch (Exception e) { }
}
It set the column to R bt sets Nulls for all other columns because I am not getting values for other columns from model. One way is to set the hidden values in model and send the model but the table has about 30 columns. so I want to get the table row from database where id=id and the only updatte the column in that row in my function in repository .....
In order to achieve this first you need to get the actual object from your db context and then change the desired property and save it to in database like below:
try
{
Abc a = db.Abcs.SingleOrDefault(a => a.id == model.Asset.id);
a.column = "R";
db.SaveChanges();
}
catch (Exception e) { }
This will fix your concern.

Two checks IValidatableObject in one entity

Is the essence of Project, the creation of which is necessary to check whether there is already an entity with the same name. When editing needs such as checking, but keep in mind that the old and the new name of the entity can be matched.
You also need to display an error message. For this I use interface IValidatableObject, but do not know how to tell the Validate method the object is currently being edited or created
DbContext.ValidateEntity takes the IDictionary<Object, Object> items as the second parameter. You can pass any data there and the data you pass will be passed to IValidatableObject.Validate in the ValidationContext.Items
Assuming you refer to check EF cant do for you.
This is actually difficult to check. You are checking an entity after it has been added to the context. It should not check itself and needs to consider other items in context that are not yet saved. As well as the DB. There are several 3 combinations plus an self recognition. Record a an entity record in LOCAL when ID is blank/new ie multiple new inserts needs careful coding. (Consider using temp IDs)
the not yet saved entries should be in context
Context.Set<TPoco>().Local
and get data from DB and keep in a temp list. BUT dont put in context.
Or use a SECOND context.
var matchingSet = Context.Set<TPoco>().AsNoTracking() // not into context...
.Where(t=>t.field == somevalue).ToList();
So what about logical and actual duplicates on the DB. Logical duplicates are duplicates on a field with no unique index that from a business perspective should be unique.
If you want to check those...
You need to read the DB.... BUT if these records are currently being changed, you CAN NOT just put them into the Context. You would overwrite them.
But what if the values the logical key values have changed?
Something caused a logical dup on a record on the DB may no longer be a dup once saved or vice verse. Is that still a dup or not ?
So you need to decide how you match LOCAL versus loaded records.
Ie check LOCAL and matching DB records and decidr what to do if a record is in both, only local or only db.
LOCAL ONLY and DB Only is easy.
But in both... That is your business process decision.
Problem is solved using method ModelState.AddModelError (string, string) in actions Edit and Create.
[HttpPost]
[HandleError(View="AjaxError")]
public ActionResult Edit(ProjectsViewData data)
{
if (ModelState.IsValid)
{
if (!ContainsProject(data.CurrentObject.Name))
{
db.Projects.Attach(data.CurrentObject);
db.ObjectStateManager.ChangeObjectState(data.CurrentObject, EntityState.Modified);
db.SaveChanges();
return Projects(data);
}
else
{
int projectId = (from p in db.Projects
where p.Name == data.CurrentObject.Name
select p.ProjectID).FirstOrDefault();
if (projectId == data.CurrentObject.ProjectID)
{
db.Projects.Attach(data.CurrentObject);
db.ObjectStateManager.ChangeObjectState(data.CurrentObject, EntityState.Modified);
db.SaveChanges();
return Projects(data);
}
else
{
ModelState.AddModelError("Name", Localizer.ProjectAlreadyExists);
}
}
}
data.ObjectToEdit = data.CurrentObject;
return Projects(data);
}
[HttpPost]
[HandleError(View = "AjaxError")]
public ActionResult Create(ProjectsViewData data)
{
if (ModelState.IsValid)
{
if (!ContainsProject(data.CurrentObject.Name))
{
db.Projects.AddObject(data.CurrentObject);
db.SaveChanges();
return Projects(data);
}
else
{
ModelState.AddModelError("Name", Localizer.ProjectAlreadyExists);
}
}
data.ObjectToAdd = data.CurrentObject;
return Projects(data);
}
Helper method:
private bool ContainsProject(string projectName)
{
if (projectName != null)
{
projectName = Regex.Replace(projectName.Trim(), "\\s+", " ");
List<string> projects = new List<string>();
var projectNames = (from p in db.Projects
select p.Name.Trim()).ToList();
foreach (string p in projectNames)
{
projects.Add(Regex.Replace(p, "\\s+", " "));
}
if (projects.Contains(projectName))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}

Adding New Child Object Whilst Modifying Existing Children Entity Framework

I've look at some of the answers to similar questions and they don't really seem to fit mine.
I'm trying to incorporate a pattern from Entity Framework: DbContext(page 90) and it doesn't seem to work. The code that I'm using is given below:
[HttpPost]
public ActionResult Edit(Order order)
{
if (ModelState.IsValid)
{
db.Orders.Add(order);
db.Entry(order).State = EntityState.Modified;
foreach (var orderDetail in order.OrderDetails)
{
if (orderDetail.OrderId == 0)
{
db.Entry(orderDetail).State = EntityState.Added;
}
else
{
db.Entry(orderDetail).State = EntityState.Modified;
}
// The example order that I'm updating has two child entities
// so this orderId will be for the third, added one.
int addedOrderDetailId = order.OrderDetails[2].OrderId;
}
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CustomerId = new SelectList(db.Customers, "CustomerId", "CompanyName", order.CustomerId);
return View(order);
}
I've been running an example where the Order object has two existing OrderDetail objects and I'm attempting to add a third. I included the addedOrderDetailId variable, so that I could add it to the 'Watch' and see when it changed
What I've found is happening is that the OrderId of the added OrderDetail object (which is 0 when the foreach loop is entered) is being updated by entity framework to the OrderId of the Order object. This is happening at after the first iteration through the foreach loop (when the first child entity is having its state changed to modified. This means that all three children are being marked as modified. This is causing SaveChanges() to try to update an entry into the database that doesn't exist.
If anyone else has had this problem, then I would be greatful for any advice as to get around this. I will also have to deal with existing child objects being deleted, but I haven't got around to this yet, so if anyone knows of a pattern for this, that would also be appreciated.
Edit:
After taking Slauma's advice and removing db.Orders.Add(order). I was able to move the call to db.Entry(order).State underneath the foreach loop. This allowed me to loop through the loop and set the state of each OrderDetail object to modified for the existing ones and added for the added one. I then simply had to assign the OrderId of the parent to the OrderId of the child and the update was successful. I've also included the code that I've used to delete child objects during the edit. I'm not sure how efficient this is, but it works. Here is the revised code:
[HttpPost]
public ActionResult Edit(Order order)
{
if (ModelState.IsValid)
{
List<int> previousProductIds = db.OrderDetails
.Where(ep => ep.OrderId == order.OrderId)
.Select(ep => ep.ProductId)
.ToList();
List<int> currentProductIds = order.OrderDetails
.Select(o => o.ProductId)
.ToList();
List<int> deletedProductIds = previousProductIds
.Except(currentProductIds).ToList();
foreach (var deletedProductId in deletedProductIds)
{
OrderDetail deletedOrderDetail = db.OrderDetails
.Where(od => od.OrderId == order.OrderId && od.ProductId == deletedProductId)
.Single();
db.Entry(deletedOrderDetail).State = EntityState.Deleted;
}
foreach (var orderDetail in order.OrderDetails)
{
if (orderDetail.OrderId == 0)
{
db.Entry(orderDetail).State = EntityState.Added;
orderDetail.OrderId = order.OrderId;
}
else
{
db.Entry(orderDetail).State = EntityState.Modified;
}
}
db.Entry(order).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CustomerId = new SelectList(db.Customers, "CustomerId", "CompanyName", order.CustomerId);
return View(order);
}
Remove this line from your code:
db.Orders.Add(order);
This will actually put the order including all orderDetails into Added state. Relationship fixup (which happens automatically in Add) will set the OrderId of all OrderDetails to the key of the order. When you enter the loop orderDetail.OrderId is != 0 for all detail items and you always enter the branch which sets the state to Modified. No orderDetail item is in Added state anymore when the loop is finished.

Resources