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?.
Related
My update method is not working in an ASP.NET MVC 3 application. I have used the following EF 4.1 code:
[HttpPost]
public ActionResult UpdateAccountDetails(Account account)
{
if (ModelState.IsValid)
{
service.SaveAccount(account);
}
}
and SaveAccount looks like this:
internal void SaveAccount(Account account) {
context.SaveChanges();
}
internal void SaveAccount(Account account) {
// Load current account from DB
var accountInDb = context.Accounts.Single(a => a.Id == account.Id);
// Update the properties
context.Entry(accountInDb).CurrentValues.SetValues(account);
// Save the changes
context.SaveChanges();
}
Alternative:
internal void SaveAccount(Account account) {
context.Entry(account).State = EntityState.Modified;
context.SaveChanges();
}
The problem here is that you're not accounting for the fact that Web pages are stateless. You probably pupulate your page with the account data returned from the database, but then the object is destroyed at the end of the request.
On postback, a new Acccount object is created by the model binder, but this one is not hooked up to the database, so your database context has no idea that it even exists. So when you call SaveChanges, nothing has changed as far as it is concerned.
You have to either get a new Account object from the database and update it's fields with the data from the model binder created Account, or attach the new account object to the database.
This article should help
http://msdn.microsoft.com/en-us/library/bb896271.aspx
You may need to add context.Accounts.Attach(account); to reattach your entity to the context
You aren't making any changes, so there is really nothing to be saved. The simplest way may be doing the following:
internal void SaveAccount(Account account)
{
context.Attach(account);
ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(account);
entry.SetModified();
context.SaveChanges();
}
If you're using an .edmx model in your application, make sure the property StoreGeneratedPattern is set to none for all the fields that have to be updated.
EF doesn't tell you, and acts like it is storing the changes to the database, when in fact it's not.
I'm trying to update a record in a database with the values in a ViewModel via Linq To SQL. I had it working but it has since stopped (more on this later).
I have a Customers domain object mapped to a table. I don't need all the fields so I use AutoMapper to map it to a ViewModel (CustomerEditVM) that has a subset of the Customer fields. I do this in my service layer:
public CustomerEditVM GetCustomerEditVMById(int custId)
{
var domainCustomer = _repository.GetCustomerById(custId);
Mapper.CreateMap<Customer, CustomerEditVM>();
CustomerEditVM customer = Mapper.Map<Customer, CustomerEditVM>(domainCustomer);
return customer;
}
I send the CustomerEditVM ViewModel to my view and the user edits the record. In my service layer I map it back to a Customer object and call the Update method in my repository:
public void SaveCustomer(CustomerEditVM customer)
{
Mapper.CreateMap<CustomerEditVM, Customer>();
Customer newCust = Mapper.Map<CustomerEditVM, Customer>(customer);
_repository.Update(newCust);
}
Here is my repository and Update method:
namespace AuctionAdmin.Models.Repositories
{
public interface ICustomerRepository
{
Customer GetCustomerById(int custId);
void Update(Customer customer);
}
public class CustomerRepository : ICustomerRepository
{
private AuctionAdminDataContext _dataContext;
public CustomerRepository()
{
_dataContext = new AuctionAdminDataContext();
}
public Customer GetCustomerById(int custId)
{
var customer = _dataContext.Customers.SingleOrDefault(c => c.CustomerID == custId);
return customer;
}
public void Update(Customer customer)
{
_dataContext.Customers.Attach(customer);
_dataContext.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, customer);
_dataContext.SubmitChanges();
}
}
}
The Update used to work fine but now fails with this error:
Unable to refresh the specified object. The object no longer exists
in the database.
I'm not sure why this worked so well before and now doesn't but clearly I'm not using Linq to update the database properly. How should I be doing this?
Thanks
So my understanding is that Automapper wasn't really designed to work this way. It flattens objects like you are doing to get your view model but it doesn't really do things the other way. I believe this is by design because Jimmy & Crew are using more of a command pattern with messaging to save things back into the database.
However, I know that doesn't solve your problem. So here are a few things.
With Linq2Sql You need to pull the object out, then update it, then save it. This is
because linq2sql is tracking the changes of the object. However, between requests you no longer have the linq2sql object.
public void SaveCustomer(CustomerEditVM customer)
{
//Get the customer from repo
var domainCustomer = _repository.GetCustomerById(customer.Id);
Mapper.CreateMap<CustomerEditVM, Customer>();
Customer newCust = Mapper.Map<CustomerEditVM, Customer>(domainCustomer);
_repository.Update(newCust);
}
However, that most likely won't work because of the way linq2sql and automapper work. Even if the mapping does work, linq2sql might not show that changes have been made to the object. You are going to be better off mapping this by hand.
Also, there is really no such thing as Update in Linq2Sql.
public void Update(Customer customer)
{
_dataContext.Customers.Attach(customer);
_dataContext.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, customer);
_dataContext.SubmitChanges();
}
All you need to do is get the object from linq2sql update it and call SubmitChanges(); on the _dataContext. It takes care of it for you. I have seen some repository interfaces include a Update method but it doesn't do anything in a Linq2Sql implementation. Also, its probably not the best idea to call SubmitChanges in the update method as you may want to update may items then submit all the changes at once, which is kind of the purpose of submitchanges (ie unit of work)
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;}
}
Given following ASP.NET MVC controller code:
[HttpPost]
public ActionResult Create(FormCollection collection)
{
string[] whitelist = new []{ "CompanyName", "Address1", "Address2", ... };
Partner newPartner = new Partner();
if (TryUpdateModel(newPartner, whitelist, collection))
{
var db = new mainEntities();
db.Partners.AddObject(newPartner);
db.SaveChanges();
return RedirectToAction("Details/" + newPartner.ID);
}
else
{
return View();
}
}
The problem is with the Entity Framework 4: the example Partner entity is mapped to a database table with it's fields NOT ALLOWED to be NULL (which is ok by design - they're required).
Unfortunately, invoking TryUpdateModel when some of the properties are nulls produces as many ConstraintExceptions what is not expected! I do expect that TryUpdateModel return false in this case.
It is ok that EF wouldn't allow setting a property's value to null if it should not be, but the TryUpdateMethod should handle that, and add the error to ModelState errors collection.
I am wrong, or somebody screwed up the implementation of TryUpdateModel method?
It's not "screwed up". It's by design. My preferred way of dealing with this is to bind to an edit model rather than directly to an entity. If that's not an option for you, then you can write an associated metadata provider or initialize the properties.
I am following a simple ADO.NET Entity/MVC 2 tutorial wherein my Views are created by right-clicking the action and selecting 'Add View'. The views get created based on my model and all is good. I can view the initial list of items from the DB but when I click Edit or Delete or Details I get 'Object reference not set to an instance of an object'. It acts like my data is not there at all so I'm thinking I may need to fill ViewData again?
Here is how I am getting the data:
CheckingEntities chk = new CheckingEntities();
//
// GET: /CheckingMVC/
[Authorize]
public ActionResult Index()
{
ViewData.Model = chk.tblCheckings.ToList();
return View();
}
And here is an example where I am getting the details:
// GET: /CheckingMVC/Details/5
[Authorize]
public ActionResult Details(int id)
{
return View();
}
I suspect I have filled the ViewData incorrectly or need to do it again but don't know where or how to do that. Still quite new to MVC.
The values passed to your Views must be populated on every request. Furthermore, values set inside your controller during a request cannot and will not be persisted between requests as every new requests creates a brand new set of controller instances from the controller factory.
In your Details() action you are accepting an id and then returning your View without any data being placed in the Model. Instead try something along these lines:
[Authorize]
public ActionResult Details(int id)
{
var item = Entities.SomeEntitySet.SingleOrDefault(e => e.Id == id);
return View("Details", item);
}