ASP.NET MVC + Entity Framework with Concurrency check - asp.net-mvc

I am trying to implement a application following the sample in this page: http://www.asp.net/entity-framework/tutorials/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application
I have a domain class with Timestamp as the concurrency check field:
public class PurchaseOrder {
[Timestamp]
public byte[] Timestamp {get; set;}
}
In my Edit.aspx I have the Timestamp as hidden field (I am using a view model):
<%: Html.HiddenFor(model => model.PurchaseOrder.Timestamp) %>
This is my Edit() method:
public ActionResult Edit(int id, FormCollection collection) {
var purchaseOrder = db.PurchaseOrders.Find(id);
UpdateModel(purchaseOrder, "PurchaseOrder", collection);
db.Entry(purchaseOrder).State = EntityState.Modified;
db.SaveChanges();
}
I opened the same edit page in 2 separate browser at the same time (so that their timestamp is the same), and update them one after the other.
When I update the second page, I expected a DbUpdateConcurrencyException. But I am getting none.
What I think happened is that in the second page, I am getting the purchaseOrder object again from the DB in the Edit action:
var purchaseOrder = db.PurchaseOrders.Find(id);
So the timestamp is the new timestamp because of the previous edit.
But I expected the UpdateModel() to replace the Timestamp value from the formcollection.
Obviously, this is not the case.
How can I set the value of the Timestamp of the retrieved purchaseOrder to the in the hidden field, so that the concurrency will be detected?

It doesn't work this way. Once you load entity by Find you cannot change its timestamp directly. The reason is that timestamp is computed column. EF holds internally original and current values for each loaded entity. If you change the value in the loaded entity, only current value is changed and during update EF compares the original value with the current value to know which columns must be updated. But in case of computed columns EF don't do that and because of that your changed value will never be used.
There are two solutions. The first is not loading the entity from database:
public ActionResult Edit(int id, FormCollection collection)
{
// You must create purchase order without loading it, you can use model binder
var purchaseOrder = CreatePurchaseOrder(id, collection);
db.Entry(purchaseOrder).State = EntityState.Modified;
db.SaveChanges();
}
The second solution is small hack described in linked question for ObjectContext API. If you need this for DbContext API you can try something like:
public ActionResult Edit(int id, FormCollection collection)
{
var purchaseOrder = db.PurchaseOrders.Find(id);
purchaseOrder.Timestamp = GetTimestamp(collection);
// Overwrite original values with new timestamp
context.Entry(purchaseOrder).OriginalValues.SetValues(purchaseOrder);
UpdateModel(purchaseOrder, "PurchaseOrder", collection);
db.SaveChanges();
}

We have overriden the DbContext class, and the SaveChanges method. In it, we look for the TimeStamp values, and if it does not match the value in the OriginalValues collection, we throw an exception.
we have a BaseEntity type for each entity, and it has a SI_TimeStamp column which is of type TimeStamp.
public override int SaveChanges()
{
foreach (var item in base.ChangeTracker.Entries<BaseEntity>().Where(r => r.State != System.Data.EntityState.Deleted &&
r.State != System.Data.EntityState.Unchanged))
if (!item.Entity.SI_TimeStamp.ValueEquals(item.OriginalValues.GetValue<byte[]>("SI_TimeStamp")))
throw new Exception("The entity you are trying to update has ben changed since ....!");
}
you have to place the original value in your forms.
Html.HidderFor (r => r.SI_TimeStamp)
I would actually recommend you to check the timestamp against the original value either when loading or after loading the entity. The overriden DbContext class method is a general solution, and it actually makes sense to check against the timestamp value before trying to save changes back to database.

Try putting a [ConcurrencyCheck] attribute in your TimeStamp Property
public class PurchaseOrder {
[ConcurrencyCheck]
[Timestamp]
public byte[] Timestamp {get; set;}
}

Related

Proper way to re-set 'read only' data in the ViewModel?

I'm trying to figure out a 'best practice' here and hope I'm not too far off. Here's the scenario:
Let's say we have two Actions. One for sending out a Razor View that contains a form. On this form, there is a <select> that is loaded with values from our ViewModel (let's say it's an expiration date field or something). There is also fields for the user to fill in, such as Name.
The other Action takes in the POST of the view, with the model as the parameter:
[HttpPost]
[ActionName("MyForm")]
public virtual ActionResult MyForm(MyViewModel model) {
// logic here
}
Now, if there's some sort of issue with the model (let's say, we couldn't charge their credit card). We could do ModelState.AddModelError and then pass back the model:
return View(model);
However, is this a best practice? What about properties that are in our ViewModel, but we didn't "return back" from the page, such as our expiration date field values. These will now be NULL and we'll have an exception when loading the view.
Would best practice be to always "recreate" the view model before sending it back (somehow taking in the values they typed in, overlaying them onto the default viewmodel?) or should we have some method that always sets those "readonly" fields, and gets called in both Actions?
Hopefully this question makes sense. I don't know what to call the properties that are just "read only" on the view, and not passed back as part of the FORM POST.
Thanks.
You call read-only data, some call it system data, some buddy data (using enricher classes and Structuremap), often it is referred to as hydrating.
I usually approach it similar to this example ("a method which always sets those fields") or I'll create an action filter (OnActionExecuted) which injects buddy data depending on the type of the view model.
For example:
public class ContactFormData
{
// data which gets posted back
public string Name {get; set;}
public string CountryCode {get; set;}
// buddy data
public SelectList Countries {get; set;}
}
[HttpGet]
[ActionName("ContactForm")]
public virtual ActionResult ContactForm() {
var m = new ContactFormData();
return ShowContactForm(m);
}
[HttpPost]
[ActionName("ContactForm")]
public virtual ActionResult ContactForm(ContactFormData formdata) {
if (ModelState.IsValid)
{
// logic & redirect
return redirect;
}
return ShowContactForm(formdata);
}
private ActionResult ShowContactForm(ContactFormData formdata)
{
formData.Countries = GetCountriesSelectListSomewhere();
return View(m);
}

My unmapped properties in breeze does not seems to work whith a projection

I have the following Entity:
public class Invoice
{
[Key]
public int Id { get; set; }
public DateTime? ArchiveDate { get; set; }
public DateTime? ClotureDate { get; set; }
...
}
I would like to know whether my invoice is archived or closed by using a kind of flag (boolean). For that purpose I added 2 unmapped properties in my breeze entity like this:
public class Invoice
{
[Key]
public int Id { get; set; }
public DateTime? ArchiveDate { get; set; }
public DateTime? ClotureDate { get; set; }
[NotMapped]
public bool Archived { get { return ArchiveDate.HasValue; } }
[NotMapped]
public bool Clotured { get { return ClotureDate.HasValue; } }
...
}
Now I can query my breeze entity like this:
var query = entityQuery.from("Invoices")
.where('id', '==', id)
.toType('Invoice');
The call above will return all properties of my invoice entity (including archived & clotured). It works well.
But I need only a few specific properties (for performance). Then I try:
var query = entityQuery.from("Invoices")
.where('id', '==', id)
.select("id, archived, clotured")
.toType('Invoice');
I got the error: The specified type member 'Archived' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Very frustrating. Any idea why do I cannot perform such query?
Or maybe does someone have another solution?
Many thanks.
Short version
What you are seeing is perfectly expected. The ArchivedDate is both a persisted data property and a serialized property. The Archived property is not persisted but it is serialized. That's why you see data values for both ArchivedDate and Archived. However, your remote query ... the LINQ query executed on the server ... may only refer to the persisted properties such as ArchivedDate. EF knows nothing about calculated properties such as Archived; they cannot participate in a LINQ query ... not in a where, select, orderBy or any other query. You can't mention something in a query that EF doesn't know about ... and you told EF (properly) to ignore these Archived and Clotured calculated properties.
Long version
The [Unmapped] attribute hides the properties from EF ... as it must because Archived and Clotured are calculated properties, not persistable data.
The [Unmapped] attribute also hides these properties from the metadata generated from EF. That too is both expected and good.
But this also means that you cannot construct a LINQ query that references these properties. They aren't data properties. They can't be queried by EF. Only data properties and navigation properties can appear in a LINQ query. It is really that simple.
Perhaps you're wondering why the unmapped calculated property values are actually communicated to the JavaScript client, why those values appear in the JSON payload and would populate the like-named Breeze entity properties if you add such properties to the client metadata for Invoice as "unmapped properties".
To understand why, you must understand the difference between properties that you query with EF and the properties that you serialize with Json.NET. After the EF query completes, the materialized entities have both the data properties (e.g., ArchivedDate) and the calculated properties (Archived). The [NotMapped] attribute doesn't hide a property from Json.NET. Json.NET serializes ALL properties of the materialized object - both data and calculated properties - unless you tell it not to. For example you could hide the Archived property from Json.NET serialization with the [Ignore] attribute.
The toType is a red herring and has no bearing on the matter.
Remove the ".toType('Invoice')' line from your query. Just go with:
var query = entityQuery.from("Invoices")
.where('id', '==', id)
.select("id, archived, clotured");
This forces breeze to coerce your projection into an Invoice entity type. If you leave it off you will get a true projection, i.e. a plain javascript object with just the properties you have specified, i.e. not an entity.

Is there a simple way to avoid nulls being saved to the Database when using Model Binding

I am using MVC3, Razor, C#, .NET4, EF5.
I have a number of situations where I am editing the domain model directly, and yes I realise that I should be using View Models :) However for the short term I am hoping I can get a solution to my problem whereby I am getting nulls being saved to the DB, where those fields are not specified as Hidden Fields in the forms. I realise this is to do with the Model Binding behaviour.
My Edit Action:
public ActionResult Edit(int id)
{
StdOrg myOrg = db.StdOrg.Single(s => s.Id == id);
return View(myOrg);
}
Lets assume my View has 5 fields on it:
Name
Address1
Address2
Address3
Address4
Now for my Post Edit Action:
[HttpPost]
public ActionResult Edit(StdOrg myOrg)
{
if (ModelState.IsValid)
{
db.StdOrg.Attach(myOrg);
db.ObjectStateManager.ChangeObjectState(myOrg, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myOrg);
}
Now my Table record has 10 columns in it, and columns 6-10 has values.
Name
Address1
Address2
Address3
Address4
Terms
EmployeeNo
Code
Active
SetUpDate
When I save the form, columns 6-10 ie Terms etc. get set to null. Now I realise this is due to the fact that I am not specifying them as hidden fields in my View and therefore MVC Model Binding is assuming these are null.
Is there a practical way around this, rather than specifying all columns in the form which I would rather not do for security reasons.
Many thanks in advance.
EDIT
My attempt which does not seem to work:
[HttpPost]
public ActionResult Edit([Bind(Include = "Id,Name,Name,Address1,Address2,Address3,Address4")] StdOrg myOrg)
Thoughts.... ???
IN CONCLUSION
For those interested I ended up using the "Value Injector" which is the other "Automapper" for which I wrote a simple matching routine to prevent nulls being set, and also to exclude navigation properties as well. Very useful Mapper.
One still really needs to implement ViewModels since these seem the only way to identify which of the classes properties are in the View, unless one can get Bind attribute working which I could not.
Otherwise even with a mapper one cannot reset a View field to null, since the mapper will ensure that it is ignored !! It cannot tell between View Properties and others in the domain class, as far as I can see.
Try using the BindAttribute, you can simply exclude or include the fields for that particular action (which is always good practice, someone could manipulate the ID field on the POST and modify a different value).
See this question for an example
Despite putting Exclude stuff:
[Bind(Exclude = "PropertyName,PropertyType")]
public class Filter {
public string PropertyName { get; set; }
public Type PropertyType { get; set; }
public string From { get; set; }
public string To { get; set; }
public string Match { get; set; }
}
those properties seem to be ovewritten with NULL, when I call this method from model (note: PropertyName and PropertyType are not and must not be present in markup! They are filled once in the code, before making markup)
[HttpPost]
public virtual PartialViewResult LoadFiltered(string entityType, IDictionary<string, Filter> filters)
And when no elements selected (all filters are disabled in interface) - I get two elements, having "controller" and "action" as keys. What the... ??

SaveChanges() not saving data

I'm fairly new to MVC so please be patient. Here's my action code in my controller in an MVC project I'm working on:
[HttpPost]
public ActionResult Create(User usr , string submitBtn, FormCollection form, int id)
{
var db = new UsrSqlEntities();
foreach (string fm in form)
{
if (fm.Contains("PayMonthOne"))
usr.fName = Int32.Parse(form[fm]);
}
db.SaveChanges();
}
I've debugged this in VS2010 and each step passes through with no errors i.e. 'User' exists in my Entity Framework, my 'form' contains a value which passes to 'fName'. However, running SQlProfiler in SSMS 2008 doesn't show any activity (and obviously not record in my database). My entity framework is modeled on this db as, when I do an update to an entity, the changes in the db reflect in the EF.
I don't know why SaveChanges() isn't working. Can somebody help?
If you are updating the entity, you will need to connect the usr object to the db context and mark it as modified.
db.Attach(usr);
db.Context.Entry(usr).State = EntityState.Modified;
If it is new you will need to add it via:
db.Add(usr);
Then call your
db.SaveChanges()
I would recommend the following:
var db=new UsrSqlEntities(); /* Module Level declaration */
[HttpPost]
public ActionResult Create(User usr)
{
db.Users.Add(usr); /* Add usr object to Users DbSet */
db.SaveChanges(); /* Save all changes to the database */
}
This assumes that you are creating a new User, and Users is your DbSet for User objects, and your User object has a property "PayMonthOne" of type int or int?.

MVC 3 Model Binding without Key

I have a question about ASP.NET MVC3 model binding. If I have a class I'm trying to use as a model, but I don't want the key put on the page, the model doesn't bind on a POST. Here is an example:
//Data Model
public class MyModel
{
[Key]
public string MyKey {get;set;} //Perhaps this is an ssn that I don't want on the form.
public string MyValueToGet {get;set;} //This is the value I want the user to enter.
}
//Conroller code.
public ViewResult Index()
{
MyModel model = new MyModel{ MyKey = "SecretInfo", MyValueToGet = "" };
return View(new model);
}
public ActionResult Edit(MyModel model)
{
repository.SaveChanges(model)
}
//View code.
#using(Html.BeginForm("Edit", "Home", FormMethod.Post))
{
Enter a value: #Html.EditorFor(m => m.MyValueToGet)
<input type="submit" value="Salve" />
}
So my problem is that model is null when the Edit method is called upon form submission. I can fix this by putting MyKey somewhere on the page (perhaps as a hidden field), but that is unacceptable if it is some sort of sensitive data. Is there a way to solve this problem? I am new to MVC, so I appreciate any help.
Create another unique but otherwise meaningless identifier like an (auto increment int) and use that to bind.
in other words modify your model to something like:
public class MyModel
{
[Key]
public int ID {get; set;}
public string MyKey {get;set;} //Now this can be sensitive, it doesn't matter because you no longer rely on it.
public string MyValueToGet {get;set;} //This is the value I want the user to enter.
}
EDIT
I believe your best choice would be to change the MyModel object, as it's design is flawed. The primary key in the majority of cases (and I think this is one of them) should be a simple auto incrementing integer, meaningless apart from it's role as the table's key.
While Luke's suggestion to use Session is a viable option and a solution that would work, I would personally do something similar to what I'll explain here, as it would seem to me to be more of the 'mvc way' of doing things.
Data model:
Either change your current model to something like what I suggest above, or, if that is not feasible for whatever reason (breaking dependancies or FK relationships), create a new table that can be used as a join, or proxy, if you will:
public class Proxy
{
public int ProxyId {get;set;}
public MyModel MyModel {get; set;}
}
Obviously, you'd have to do some work to populate this table, but you would then be able to use it to fetch records from MyModel without accessing the MyKey property directly.
It's not considered good practice to use your data models directly in your views, so you want to create a view model as well
public class MyModelViewModel
{
public int ModelId {get; set;}
public string ModelValueToGet {get; set;}
}
Notice we don't even need the key containing sensitive data in the view model.
Then type your view to the viewModel, not the data model, and include a hidden field for the ModelId
#using(Html.BeginForm("Edit", "Home", FormMethod.Post))
{
Enter a value: #Html.EditorFor(m => m.ModelValueToGet)
#Html.HiddenFor(m => m.ModelId)
<input type="submit" value="Save" />
}
Now in your controller you have your get method
public ViewResult Index()
{
//fetch the users record from the database
//if you're using the Proxy table, you'll want to write a LINQ query here
//instantiate a viewModel and populate it's properties using the fetched record
//remember, the viewModel.ModelId should be set to MyModel.ID or Proxy.ProxyId
//render the view
}
And the post method
public ViewResult Edit (MyModelViewModel viewModel)
{
//fetch the users record from the database using viewModel.ModelId
//If you're using the proxy table, you'll need to use that LINQ query again here
//update the record you fetched with the new data the user just entered
//you have complete control here of what gets updated and what stays the same
//pass the updated record to the repository to save the changes.
//redirect the user to be on their merry way
}
I think that's about as well as I can lay it out. Hope it makes sense.
An alternative is to encrypt the id before sending it to the client. Check this post for more information on how to accomplish this.
Asp MVC 3: Modifiy Values Sent to View

Resources