3

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 :)

Admir Tuzović
  • 10,997
  • 7
  • 35
  • 71

1 Answers1

6

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.

Slauma
  • 175,098
  • 59
  • 401
  • 420
  • I'm sorry but your suggestion wasn't much helpful. What you suggest is usual by-the-book approach that loads unnecessary stuff. My solution works just fine, as I've said, it does what it is required and also queries run against SQL server are minimal (checked by Anjlab SqlProfiler). I'm not going to sacrifice performance for code reduction. Thanks anyway for the effort. – Admir Tuzović Apr 29 '12 at 17:08
  • @BarisaPuter: I've added an Edit2 to my answer, I forgot to mention it, but maybe you know that. BTW: In this answer (http://stackoverflow.com/a/10271910/270591) is an approach to set multiple properties to modified *if* you know the property names you want to update. But to implement this as a general solution you will likely need reflection to extract the property names from the ViewModel. – Slauma Apr 29 '12 at 18:11
  • I don't see how SQL update will be more expensive? It will contain only those 25 columns I need to update opposite to having all 50 columns being updated without actual need. – Admir Tuzović Apr 29 '12 at 19:14
  • This is how I fetch only a model, so I have minimal SELECT query as well: ActivateUserModel model = db.Users.Where(u => u.Id == id).Select(u => new ActivateUserModel { Id = u.Id, Active = u.Active}).SingleOrDefault(); – Admir Tuzović Apr 29 '12 at 19:15
  • @BarisaPuter: The code in your second comment is for the GET request, of course you don't need to load the full entity in that case, because you don't need change tracking. BTW: If you have such a high-traffic website or high performance requirement that you need to be worried about updating a single entity (probably only a minimal fraction of the total time until the user sees the next page) I would recommend to use something other than EF which is not designed for high performance. Stackoverflow abondoned to use LINQ to SQL for performance reasons and EF was even out of discussion. – Slauma Apr 29 '12 at 21:31
  • @BarisaPuter: I forgot your other comment: There are not 50 columns updated but only the (say) 5 you changed in the view instead of 25. You have to *load* (SELECT) all columns though, but that's a read operation and not a write. – Slauma Apr 29 '12 at 22:09
  • We expect quite a lot of client requests from web service for our phone app. I actually was thinking to go back to plain ADO.NET, however it is company policy to build on MVC3 + EF 4.3, so I need to make the best of what I'm given. I actually made a flaw, SingleOrDefault() results in double nested select in order to check if there's only 1 result. First() is lightweight fast query. As for the update, I'll experiment bit more by faking heavy load to see where is my bottleneck. Thank you again for assistance :) – Admir Tuzović Apr 30 '12 at 05:10