Say I have class called User, that is my basic entity (I use it with DbContext as DbSet Users), that I use as ground level for my Data-Access-Layers. Let's say class looks like this:
public class User
{
[Key]
public int Id { get; set; }
public bool Active { get; set; }
public string Description { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public byte[] Photo { get; set; }
public DateTime Created { get; set; }
}
Now I want to have pure view where I only show whether user is Active or not, and simple checkbox that allows me to change that value. I don't want to load any other entity properties, specially property Photo as it's just insane. I've created ActivateUserModel that goes like this:
public class ActivateUserModel
{
[Key]
public int Id { get; set; }
public bool Active { get; set; }
}
I have strongly-typed view called Activate, that takes ActivateUserModel and displays it (it's only a checkbox and hidden for Id) and then I have [HttpPost] Activate action that captures ActivateUserModel, converts it to User entity, and then saves changes to database. This is POST action:
[HttpPost]
public ActionResult Activate(ActivateUserModel model)
{
if (ModelState.IsValid)
{
User user = new User { Id = model.Id, Active = model.Active };
db.Users.Attach(user);
db.Entry(user).Property(u => u.Active).IsModified = true;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
This works like a charm. I've monitored my queries issued against SQL server, and all I load is pair Id/Active and all I update is also Active change based on id.
However I don't like the code how it looks. Say I have entity that has 50 properties, and view that has 25. I don't want to write 25 lines where I say IsModified=true.
So my question is: is there more efficient way to do the same, without digging into any reflection-based methods? I want to transfer data from any view model to entity and then save only those properties.
Thank you in advance for responses, I hope I made question clear enough :)
You can do it this way:
[HttpPost]
public ActionResult Activate(ActivateUserModel model)
{
if (ModelState.IsValid)
{
User user = db.Users.Single(u => u.Id == model.Id);
db.Entry(user).CurrentValues.SetValues(model);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
db.Entry(user).CurrentValues.SetValues(model) will check if a property in user also exists with the same name in model and if yes, copy the property value from model to user. If not, it leaves the property value in user unchanged.
I doubt though that this is not reflection based. But the code above is the straightforward way and is designed to support exactly your scenario.
Edit
The code above loads the full user entity including the Photo property. If you don't like to load the potentially large binary field I'd recommend to solve this problem with other strategies than the IsModified trick. Updating with Entity Framework strongly relies on change tracking which requires that you load a full entity. You are going to complicate your code when you try to avoid this and set the Modified flag for specific properties manually.
You probably know that you can't exclude single scalar properties from being loaded when you fetch an entity from the database. I would recommend to move the Photo property into a new entity UserPhoto which just has an Id and the Photo property and put a navigation property UserPhoto into the User class. Then you can decide via lazy, eager or explicit loading if you want to load the photo together with the User or not.
You can either create a one-to-one mapping between User and UserPhoto if you want to store the UserPhoto in a separate table. Or you can even leave the Photo column in the User table and map the two entities User and UserPhoto to this same table via Table Splitting.
Edit 2
Refering to your comment that the approach loads "unnecessary stuff". I forgot to mention the following:
Indeed in the code above you have the costs to load the entity from the database. But when you apply model to the loaded entity user using SetValues(model) EF will only mark those property as Modified which really changed compared to the original values in the database. The generated UPDATE statement will only contain those columns. So, the costs to write the UPDATE statement are minimized.
If you don't want to load the entity you don't know the current column values in the database and you don't know what did really change. Your only chance is to force an UPDATE for all properties to be sure that the row in the database gets correctly updated. In your examlpe you had to set IsModified for all 25 properties included in the ViewModel to true. The generated SLQ UPDATE statement will contain all 25 columns. So, the UPDATE statement is potentially much more expensive and does - borrowing your words - unneccessary stuff.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With