Earlier I created an AddClient page that (when posted) passed a client object and I used db.AddToClient(obj) in my repository to persist it. Easy stuff.
Now, I have a details page whose save submits a post to an action "UpdateClient". Before that action is hit, my custom model binder creates my Client object and it's conveniently handed to the action. The thing is, this client object is not connected to a EF context yet. Where is the correct place to do that? In the modelbinder, or maybe when we get it from the controller, or perhaps we wait until we do a repository call and link it up there? What's the recommended process?
From what I remember, you will either need to attach the object again to the context and set it as modified
Or reload the object from the database and apply your changes.
This article explains it better:
http://msdn.microsoft.com/en-us/magazine/ee321569.aspx#id0090022
What version of EF are you using ?
If an EF object has been created outside a context, you need to Attach the object to the context.
See: http://msdn.microsoft.com/en-us/library/bb896271.aspx
I know this is an old thread but in the interest of new readers:
Based on my observations using VS 2012, MVC 4 and EF 4.0 with a view that has an EF object for a model that submits a form back to the controller.
On the controller:
public ActionResult SubmitEFObject(tblData data, FormCollection col)
"data" will only have the properties used in the view (#Html.xxxFor) filled.
It appears that when "data" is created, the posted FormCollection is used to set data's properties. If you had a property that wasn't used, DataID for example, then data.DataID will have a null/default value. Add a "#Html.Hidden(m => m.DataID)" to your view and THEN DataID will be filled.
As a 'quick n dirty' way to work with this, I created a method that would merge the incoming 'data' with the 'data' in the database and return the merged object:
// Note: error handling removed
public tblData MergeWithDB(DBContext db, tblData data, params string[] fields)
{
tblData d = db.tblData.Where(aa => aa.DataID == data.DataID).Single();
if (fields.Contains("Field1")) d.Field1 = data.Field1;
if (fields.Contains("Field2")) d.Field2 = data.Field2;
if (fields.Contains("Field3")) d.Field3 = data.Field3;
// etc...
return d;
}
On the controller:
public ActionResult SubmitEFObject(tblData data, FormCollection col)
{
DataEntities db = new DataEntities();
tblData d = MergeWithDB(db, data, col.AllKeys);
db.SaveChanges();
}
You could make this more generic using reflection or maybe more efficient by looping through the string[] fields instead of all the ifs but for my purposes this was 'good enough'.
Database work should be put in the repository call.
Are you directly binding to an entity framework object in your model binding? If not, you should consider do some mapping between your custom object and entity framework object.
Related
I'm using a customized method for tracking individual modified properties of an n-tier disconnected entity class. I extracted it from
Programming Entity Framework: DbContext by Julia Lerman and Rowan
Miller (O’Reilly). Copyright 2012 Julia Lerman and Rowan Miller,
978-1-449-31296-1.
The code is:
public void ApplyChanges<TEntity>(TEntity root) where TEntity : class, IObjectWithState {
// bind the entity back into the context
dbContext.Set<TEntity>().Add(root);
// throw exception if entity does not implement IObjectWithState
CheckForEntitiesWithoutStateInterface(dbContext);
foreach (var entry in dbContext.ChangeTracker.Entries<IObjectWithState>()) {
IObjectWithState stateInfo = entry.Entity;
if (stateInfo.State == RecordState.Modified) {
// revert the Modified state of the entity
entry.State = EntityState.Unchanged;
foreach (var property in stateInfo.ModifiedProperties) {
// mark only the desired fields as modified
entry.Property(property).IsModified = true;
}
} else {
entry.State = ConvertState(stateInfo.State);
}
}
dbContext.SaveChanges();
}
The purpose of this method is to let the EF know only a predefined set of entity fields are ready for update in the next call of SaveChanges(). This is needed in order to workaround the entity works in ASP.NET MVC 3 as follows:
on initial page load: the Get action of the controller is loading the
entity object and passing it as a parameter to the view.
The View generate controls for editing 2 of the fields of the entity,
and holds the ID of the record in a hidden field.
When hitting [save] and posting the entity back to the controller all
of the fields excepting the 3 preserved in the view comes with a null
value. This is the default behavior of the MVC binding manager.
If i save the changes back to the database the update query will of course overwrite the non mapped fields with a sentence as follows:
UPDATE non_mapped_field_1 = NULL, ..., mapped_field_1 = 'mapped_value_1', mapped_field_2 = 'mapped_value_2', ... non_mapped_field_n = NULL WHERE ID = mapped_field_3
This is the reason i'm trying to track the fields individually and update only those fields i'm interested in. before calling the custom method with ApplyChanges() i'm adding the list of fields i want to be included in the update to the IObjectWithState.ModifiedProperties list, in order to get a SQL statement as follows:
UPDATE mapped_field_1 = 'mapped_value_1', mapped_field_2 = 'mapped_value_2' WHERE id = mapped_value_3
The problem is, when marking one of the fields as modified in ApplyChanges, i.e.:
entry.Property(property).IsModified = true;
the system is throwing the following exception:
{System.InvalidOperationException: Member 'IsModified' cannot be called for property 'NotifyCEDeadline' on entity of type 'User' because the property is not part of the Entity Data Model.
at System.Data.Entity.Internal.InternalPropertyEntry.ValidateNotDetachedAndInModel(String method)
at System.Data.Entity.Internal.InternalPropertyEntry.set_IsModified(Boolean value)
at System.Data.Entity.Infrastructure.DbPropertyEntry.set_IsModified(Boolean value)
...
So the question is. There's a way to bypass this EF validation or let the context know of the existance of this system property (IsModified) that i'm trying to change?
Summary of the architeture:
EF Code first (annotation + Fluent API)
Oracle .NET EF Data provider (ODAC)
Context is injected to a cutom business context with nInject.MVC => this is the reason i customized the ApplyChanges() method from
using (var context = new BreakAwayContext()){
context.Set().Add(root);
to a simple call to the already initialized dbcontext
dbContext.Set().Add(root);
Oracle Database is created manually i.e. without the help of EF, so no EF metadata tables are used.
Thanks,
Ivan.
Very good description, however I can't find any information on why you need a transient property called "IsModified" in the object and/or why you need to tell EF about it being modified (EF won't be able to persist it anyway).
The value of the IsModified property should be set by the model binder if the property was incldued in the view anyway.
You could just add code in your ApplyChanges method to skip a property named "IsModified", or even better, filter only known properties using entry.CurrentValues.PropertyNames, e.g.:
foreach (var property in stateInfo.ModifiedProperties) {
// mark only the desired fields as modified
if (entry.CurrentValues.PropertyNames.Contains(property)) {
entry.Property(property).IsModified = true;
}
}
Update: Ivan, very sorry I did not understand the problem better when you posted it several months ago and that I did not follow up after your added these clarifying comments. I think I understand better now. That said, I think the code snippet that I offered can be part of the solution. From looking at the exception you are getting again, I understand now that the problem that EF is detecting is that NotifyCEDDealine is not a persistent property (i.e. it is not mapped in the Code First model to a column in the database). IsModified can only be used against mapped properties, therefore you have two options: you change the code of the implementation of IObjectWithState in your entities so that non-mapped properties are not recorded in ModifiedProperties, or you use my code snippet to prevent calling IsModified with those.
By the way, an alternative to doing all this is to use the Controller.TryUpdateModel API to set only the modified properties in your entities.
Hope this helps (although I understand it is very late).
I have An EntityModel that is named ECommerceEntities that contains several entities. If I want to use this model in a view in asp.net mvc, Can I pass ECommerceEntities instance to view or Sould I pass one entity in ECommerceEntities.
I mean :
//Can I use this?
public ActionResult Index()
{
ECommerceEntities entity = new ECommerceEntities();
return View(entity);
}
or
//Should I use this?
public ActionResult Index()
{
ECommerceEntities.OneEntity one_entity = new ECommerceEntities.OneEntity();
//filling one_entity here and then send to view
return View(one_entity );
}
Thanks.
If you are asking if it is possible, it is possible to do both. Yes, both options will work. However if you only need the sub entity in the view, I would just pass the sub entity into the view. No use in passing in more than needed right?
Do not forget that in MVC whatever object you pass in to your view,(EcommerceEntities for example) can have its properties set in the post by MVC's automatic model binding which maps data from the post into the object you pass into the view.
So, this means that someone can hijack the http post and can fill in EcommerceEntities and its sub entities with various bits of random data of their choosing if you are not careful and you may accidentally save this data to your db because you did not expect some of these properties to get set.
So, when working in MVC you have to protect properties that are not being used in your view but are passed into the view to ensure that nobody has injected them.
If you do decide to pass in EcommerceEntities, make sure that you use whitelisting or look at MVC's bind attribute to protect your data when your entity is posted back to your controller.
Because of the work involved in protecting that much extra data, I would say that the sub entity would be best if the screen will populate correctly just off of the sub entity object.
Hopefully this is helpful.
If you want to display a list of all entities (which the Index action is typically used for), you probably want to get all the entities from your database context:
public ActionResult Index()
{
// assumes dbContext is already initialized
ICollection<ECommerceEntities> entities = dbContext.ECommerceEntities
return View(entities);
}
I'm begginer in asp.net mvc and i have some doubts.
P.S: I'm using DDD to learn
I have an ACtion in a Controller and it'll save an entity (from my model) by a repository (for a database).
My doubts is, How can I get the informations from the View and save it by a repository in my Controller ?
Is it correct to get an entity of my Model in Save method of controller, like this:
public ActionResult Save(Product product)
{
// validate object
// save data in repository
return View("Success");
}
Or Need I get an DTO (with a structure similar to my entity) and create an object passing property by property to an entity ?
I didnt' like of FormCollection and I'd like to know, What is recommended architecturally ?
Thanks a lot guys
Cheers
I you want to follow DDD practices as described by the Blue Book you should bind your views to DTO's which can be forwarded to a thin 'Application' layer where Domain objects are created or retrieved from database. This application layer can be a simple facade with methods or can leverage command pattern.
For a live demo, you can see my project -- DDDSample.NET
This kind of problem can be fixed by adding so called view model.
Basically - a view model is DTO that provides data for particular view. In similar fashion - view models are used to get data back from view via model binding. Then - controller just forwards necessary data to domain model.
Typically, in ASP.NET MVC your controller actions will receive strongly typed objects returned by the DefaultModelBinder when editing entity types. Using this pattern you can pass a "Product" to your GET view either by itself or as part of a DTO and then your "Save" method would receive a "Product" object in its parameter list.
So long as you are using either editor templates or fields with matching names (i.e. Html.TextBox("Name") corresponds to Product.Name) then the DefaultModelBinder should be able to correctly fill the typed entity object passed to the action method. You shouldn't have to mess with the FormCollection except in certain edge cases.
[HttpGet]
public ActionResult Create() {
return View("Create", new Product());
}
[HttpPost]
public ActionResult Create(Product product) { //or Save(Product)
...
}
As long as your form has fields which match the fields in Product, they should automatically be populated for you based on the values. How you save the entity depends on the data model, whether you're creating a new record or editing an existing one, etc.
Let's say I have a DB table with columns A and B and I've used the Visual Studio designer to create a Linq objects for this table. All fields are marked NOT NULL.
Now I'm trying to edit this record using typical MVC form editing and model binding, but field B doesn't need to be editable, so I don't include it in the form.
When the post handler binds the object it doesn't populate field B, leaving it null. Now when I save the object I get a DB error saying field B can't be NULL.
The code to save looks something like:
m_DataContext.MyObjects.Attach(myObject);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues, myObject);
m_DataContext.SubmitChanges();
How do I get this to work? Do I need to include field B as a hidden field on the form - I don't really want to do this as it may be updated by other users at the same time and I don't want to stomp over it.
I've found the solution to this problem revolves around getting the entity object associated with the data context before applying the changes. There's a couple of ways of doing this which I've described in separate answers below.
Descend into SQL
This approach ditches LINQ in favour of straight SQL:
public override void SaveMyObject(MyObject o)
{
// Submit
m_DataContext.ExecuteCommand("UPDATE MyObjects SET A={0} WHERE ID={1}", o.ID, o.A);
}
I like this approach the best because of it's simplicity. As much as I like LINQ I just can't justify it's messiness with this problem.
Use a Custom Model Binder
This approach uses a custom model binder to create the entity object and associate with the data context, before the binding takes place.
public class MyObjectBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
MyObject a = ((MyObjectController)controllerContext.Controller).Repository.GetMyObjectForUpdate(bindingContext.ValueProvider["ID"].AttemptedValue.ToString());
return a;
}
}
The repository then creates the object and associates it with the data context:
public Object GetMyObjectForUpdate(string id)
{
MyObject o=new MyObject();
o.ID=id;
m_DataContext.Articles.Attach(o);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues);
return o;
}
The action handler needs to be attributed to use the model binder...
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditMyObject([ModelBinder(typeof(MyObjectBinder))] MyObject o)
{
if (!ModelState.IsValid)
return View("EditMyObject", a);
Repository.SaveMyObject(a);
return RedirectToAction("Index");
}
and finally SaveMyObject simply calls datacontext.SubmitChanges().
For this to work I also needed to set the update check attributes on all columns to Never (in the dbml file).
Overall, this approach works but is messy.
Use Two Entity Objects
This approach uses two entity objects, one for the model binding and one LINQ:
public override void SaveMyObject(MyObject o)
{
// Create a second object for use with linq and attach to the data context
MyObject o2 = new MyObject();
o2.ID = o.ID;
m_DataContext.Articles.Attach(o2);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues);
// Apply fields edited by the form
o2.A = o.A;
// Submit
m_DataContext.SubmitChanges();
}
This approeach doesn't require any special handling in the controller (ie: no custom model binding) but still requires
the Update Check property to be set to Never in the dbml file.
You could add a timestamp field and check one on the page with the one in the DB (hiding the timestamp field as well). If a user has updated the record, a concurrency error is returned and the page is refreshed, or left the same iwth the users changes.
I have a simple form which inserts a new Category with the given parentID (ServiceID).
and my parent child relationship is this;
Service > Category
my url for creating a Category based on the ServiceId is this
/Admin/Categories/Create/3 => "3 is the serviceID"
and my Action method is this
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(int? id, Category category)
{
if (ModelState.IsValid)
{
try
{
category.Service = dbService.GetAll().WithServiceId(id.Value).SingleOrDefault();
dbCategory.Add(category);
dbCategory.Save();
return RedirectToRoute("List_Categories", new { ServiceId = id.Value });
}
catch
{
ModelState.AddRuleViolation(category.GetRuleViolations());
}
}
return
View ....
I am using LinqToSql for db actions. Anyway the strange part begins here.
When i save the data it inserts a data to Service table instead of insterting data just to Category Table and the data which is inserted to Category Table has the new inserted ServiceID.
Is it a problem with the ASP.NET MVC ModelBinder or am i doing a mistake that i dont see ?
I have used LinqToSql in several projects and have never had an issue like this
It could be an error in your LINQ To SQL set up. Is the service id an autogenerated field in the DB -- is it marked as such in the LINQ to SQL classes? Does your service disconnect it from the data context, then not reattach it before submitting? If so, have you tried adding the category to the service and then saving the updated service?
var service = ...
service.Categories.Add(category);
service.Save();
Where does your Category object come from? As I see you are relying on ASP.NET MVC Model binding for providing it but for that you need a form somewhere to make a POST. It would help to see the form too.
Also personally I rather use FormCollection (rather than business model classes) in my action signatures and then pull the posted data out from the FormCollection by myself. Coz sooner or later you end up having some complex objects with various one-to-many, many-to-many relationships that Model Binding just won't pick up and you have to construct it by yourself.