14

As you know, When we want to Modify a data, We will go to Edit page:

public ActionResult EditAdmin(int UserId)
{ 
        User user = persons.Users.Find(id);
        return View(user);
}

Then We submit it on the Edit Page, it will Modify:

public ActionResult EditAdmin(User user)
{ 
        persons.Entry(user).State = EntityState.Modified;
        persons.SaveChanges();
}

But the problem is , I have alot of field don't need be modify:

public class User{
    public int UserId {get; set;} // do not need modify
    public int Password {get; set;} // do not need modify
    public string Name {get; set;}
    public bool Sex {get; set;}
    public DateTime AddTime {get; set;} // do not need modify
}

Obviously, I can't display some field on my edit page use Hidden, because I don't want it display on UI. but when submit, I still need it still keep the original value. So Is there any good idea for it? Thanks

Update1:

Why I can't use like

entry.Property(e => e.Password).IsModified = false;

link: https://stackoverflow.com/a/18004476/1900498

But It will display :

Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.

Community
  • 1
  • 1
qakmak
  • 1,287
  • 9
  • 31
  • 62
  • What, you don't even want to have these values in the message! Use a view model or DTO. – Gert Arnold Oct 24 '14 at 11:29
  • 1
    You need to have a new value for all modified fields. You can also accept the non-modified value for the other modifiable fields. But why do you think you need the old value for the non-modifiable fields? You don't (with the exception of the primary key and optimistic concurrency version or timestamp, of course). – Kris Vandermotten Oct 24 '14 at 12:21

10 Answers10

22

Fetch the existing version from the database, and then change just the 'modifiable' fields:

public ActionResult EditAdmin(User user)
{ 
    var currentPerson = db.Persons.FirstOrDefault(p => p.id = user.id);
    if (currentPerson == null)
        return HttpNotFound();

    currentPerson.Name = user.Name;
    currentPerson.Sex = user.Sex;
    // Id and Password are not updated.

    db.SaveChanges();
}
  • You might also want to do some optimistic concurrency checking as well, to ensure that the version being updated, is in fact current. Ideally, if you have a timestamp, use this, otherwise, you are faced with comparing all fields.

Edit
See also @Kris' comment and Ric's point about creating tailored view models and hence NOT polluting your views with ORM / data tier entities. I also still contend you need to carry a timestamp or hash through the ViewModel to prevent the last one wins overwriting problem.

StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • 2
    do you even need to set `EntityState.Modified`? The EF proxy should handle this – Rhumborl Oct 24 '14 at 11:26
  • This requires the action method to read and then write the row, which is two roundtrips to the database, where technically only one roundtrip is required. The select will read all "modifiable" fields to ensure the update sends only the ones actually modified. In fact, if your entity contains all columns in the table, you read them all, not just the "modifiable" ones. On the other hand, with one roundtrip you indeed don't know which fields were modified, so the update statement will send all "modifiable" columns to the database. Question is: which is worse? – Kris Vandermotten Oct 24 '14 at 12:16
  • 1
    Why use ``x.FirstOrDefault()`` not ``x.Find()``? – qakmak Oct 24 '14 at 13:25
  • @qakmak Yes, you can use `Find`, if you are confident that there is no chance you have a [stale context](http://stackoverflow.com/a/16966356/314291). Kris - yes, I was assuming a need for an optimistic concurrency check, and also to ensure that a browser user didn't hack the JSON / POST and supply updated values for Id, Password etc. I guess an optimal solution would be `UPDATE Person SET Name = {NewName}, Sex = {NewSec} WHERE ID = {OldId} AND LastUpdateTimeStamp={LastUpdateTimeStamp}` and then throw an `OptimisticConcurrencyException` if the rows updated was zero, but thisn't very ORMish. – StuartLC Oct 24 '14 at 14:05
10

Option 1:

You could use a readonly attribute:

Something like:

@Html.EditorFor(model => model.DriverID, new { htmlAttributes = new { 
        @Value = @Html.Action("getNextDriverID"), @readonly = "readonly"} })

Don't worry about the @Value part as this allows me to call an action method to auto generate a value.

In context, yours would look like:

@Html.EditorFor(model => model.UserId, new { htmlAttributes = new {@readonly = "readonly"} })

Please Note

This answer refers to using razor view engine.


Option 2:

Another option would be to use a different viewModel altogether:

public class edit User{

    public int userId {get; set;}
    public string Name {get; set;}
    public bool Sex {get; set;}
}

And then 'fill' your data using this in your `Edit ActionResult.

from there, you could then set the values in your [HttpPost] Action method using (linq or otherwise), before then saving to your database.


Option 3: Using ViewBags

since you are only looking to edit 2 parts of your model, you might just want to use the ViewBag:

Controller:

ViewBag.Item1 = xyz;
ViewBag.Item2 = xyz;

View:

@Html.TextBox("Item1")
@Html.TextBox("Item2")

Then in your post method, you could add these as string parameters:

public ActionResult Edit(string Item1, string Item2)
{
 ...
Community
  • 1
  • 1
  • 1
    Option 3 is a security risk since they will be editable by the user. – Worthy7 Jun 15 '16 at 04:32
  • option 1 won't work for calculated fields like if we have a field who is generated by some function (like by a datetime ticks or a path) and converted to string, on edit the field will be reset to null even if you make it read only – M.M.Rame Apr 30 '20 at 10:22
7

You could and actually should make a specific viewmodel for your edit page. Like:

public class UserViewModel
{
    public string Name {get; set;}
    public bool Sex {get; set;}
}

Then instead of returning the complete user to and from the view, use the UserViewModel.

public ActionResult EditAdmin(int userId)
{ 
        User user = persons.Users.Find(userId);
        return View(new UserViewModel 
            { 
                Id = user.Id,
                Name = user.Name, 
                Sex = user.Sex 
            });
}

[HttpPost]
public ActionResult EditAdmin(UserViewModel user)
{ 
        var dbUser = persons.Users.Find(user.Id);
        dbUser.Name = user.Name;
        dbUser.Sex = user.Sex;

        persons.Entry(dbUser).State = EntityState.Modified;
        persons.SaveChanges();
}
Ric .Net
  • 5,540
  • 1
  • 20
  • 39
  • Well you overlooked the crucial part. How do you get `id` in the post method? – yazanpro Feb 26 '18 at 09:08
  • 1
    @yazanpro Yes, that was a bug. Edited the answer. Funny nobody else saw this in 3 years. You probably want to put the Id field in the UserViewModel in a hidden input in your razor view – Ric .Net Feb 27 '18 at 09:56
  • Thanks for addressing this. However, this way we're back to the original issue which is the need to "hide" certain properties from getting tampered with by the client. If you rely on the model like you did here, the user can modify the hidden field (the user id) in JavaScript and can cause an update to a different user. The way I worked around this is by using [TempData](http://www.c-sharpcorner.com/blogs/viewdata-vs-viewbag-vs-tempdata-in-mvc1) to store the ID. The good thing about `TempData` is that is lasts between requests. I'm not sure if that's the best way though. – yazanpro Feb 27 '18 at 23:38
  • 1
    @yazanpro The OP says nothing about tampering. The question is about not modifying data that should not be modified. This answer solves this, because only the properties that can be modified are modified. Changing the question to a security issue requires a total different answer. I have an answer for that also, not involving TempData. If you need info about this, create a new question and tag me – Ric .Net Feb 28 '18 at 13:14
  • You're right. The OP doesn't mention security. I apparently missed the point behind the question. – yazanpro Feb 28 '18 at 18:18
4

Here is something I just learned how to do for Posting the data to a database and excluding other fields. I only wanted to post 1 change to my database on PIVPrinted checkbox.

[HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult PrintDetails([Bind(Include = "PatientID,LastName,FirstName,PatientDOB,PIVCompleted,PIVPrinted")] PIV pIV,
        string command)
    {

        if (command.Equals("Print Completed"))

        {
            pIV.PIVPrinted = false;
            db.Entry(pIV).State = EntityState.Unchanged;
            db.Entry(pIV).Property("PIVPrinted").IsModified = true;
            db.SaveChanges();

            return RedirectToAction("PrintDetails");
nolanfilm
  • 41
  • 1
2

Using the Razor view engine you can mark the entry as hidden:-

    <div class="form-group" style="visibility:hidden;height:0px;">
            @Html.EditorFor(model => model.CreationDate)
            @Html.ValidationMessageFor(model => model.CreationDate, "", new { @class = "text-danger" })
    </div>

Or just this which is simpler:-

    @Html.HiddenFor(model => model.CreationDate)
user2284063
  • 321
  • 2
  • 5
1

If you dont want use hidden in view you must load yor entity from db and add yor changes like

var olduser= db.Persons.FirstOrDefault(p => p.id = user.id);
olduser.Name=user.Name; 
olduser.Sex=user.Sex;
persons.SaveChanges();
M.Azad
  • 3,673
  • 8
  • 47
  • 77
1
 @Html.HiddenFor(model => model.UserId)
 @Html.HiddenFor(model => model.Password)
 @Html.HiddenFor(model => model.AddTime)

No need to do anything else, just list the constant values as Razor syntaxes.

Emre
  • 144
  • 2
  • 17
  • This solution worked for me and seemed like the simplest approach. – Don Gossett Jun 27 '18 at 13:30
  • This worked for me too, but how do I show the date on my page? I don't want it hidden because the user will want to know when the request was originally submitted - in my scenario. Thank you – Jamie Nov 02 '18 at 17:55
0

For modifying just a few fields I use the following code and I think You too can make use of it.

if (ModelState.IsValid)
{
    var action = this.db.DbcontextName.Find(int.Parse(id));   
    db.Entry(action).Property("Status").CurrentValue = "YourString/Data";

    db.SaveChanges()          
 }
Rom
  • 3
  • 1
0

simple and easy solution is to use sessions. In Edit get method just create session and assign value of that particular object for example.

Session["ProfilePic"] = personnel.ProfilePic;

now in Edit Post method set the value

personnel.ProfilePic = Session["ProfilePic"].ToString();

offcourse in Edit post method you will check the condition when your particular object value is null.

-1

You can use TempData like this:

public ActionResult EditAdmin(int UserId) { User user = persons.Users.Find(id); TempData["oldUser"] = user; return View(user); }

Then you can recover it after post:

public ActionResult EditAdmin(User user) { User oldUser = TempData["oldUser"] == null ? persons.Users.Find(user.UserId) : (User) TempData["oldUser"]; if (oldUser.Sex != user.Sex) doSomething(); persons.Entry(user).State = EntityState.Modified; persons.SaveChanges(); }

It's necessary to check TempData["oldUser"] == null because in case of errors or user backing to page sometimes the TempData is lost.

Guilherme Muniz
  • 1,265
  • 11
  • 12