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.
Related
I'm trying to create a mostly simple web app with Asp.Net MVC and Entity Framework. I actually finished few projects with it but wasn't satisfied about the code so I'm following some more popular ways to do it. Here is my structure:
Controller gets the request,
I have services which contain business logic. They also use my Database Context to change the data on the database. I didn't want to create another database layer so I'm using them,
I inject my services to controller (with Unity) and call stuff like CustomerService.Delete(Id)
My service deletes the data based on Id.
So my controller does not include any logic or database operations, my services does include both. I think it's a good way to do stuff but I have a problem.
Let's say I add a customer for the first time and there is another table keeping their balance and I want to include a bonus $10 for first registration, when I call my CustomerService.Add(Customer), that method also calls CustomerService.AddBalance(Customer, 10). In those methods I call DbContext.SaveChanges, here is the problem, I call SaveChanges 2 times, and if CustomerService.AddBalance(Customer, 10) fails for some reason I will still have the customer data but not the balance one. I know I can use a transactions but where do I put that code? If I knew there was a place which runs last before request is finished, I could call SaveChanges() there and it would work but I think that's not a great idea either.
Basically I want to call SaveChanges() once per request but I couldn't find a good place to do it.
Thanks
If you are doing any work with the database you want to be using transactions. If you don't you are going to leave your db in an inconsistent state if one of your calls fails. Using transactions, nothing will be committed in the database until you call Commit. You can call SaveChanges as much a you like and it won't make a difference.
You can create a TransactionScope in a attribute and then add it to any controller action where you are accessing the db. Here' one I use
[AttributeUsage(AttributeTargets.Method)]
public class TransactionAttribute : ActionFilterAttribute
{
private TransactionScope TransactionScope { get; set; }
public override void OnActionExecuting(HttpActionContext filterContext)
{
TransactionScope =
new TransactionScope(TransactionScopeOption.Required, new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted
});
}
public override void OnActionExecuted(HttpActionExecutedContext filterContext)
{
if (TransactionScope == null)
return;
if (filterContext.Exception == null)
{
TransactionScope.Complete();
return;
}
TransactionScope.Dispose();
}
}
You could put the transaction code in BeginRequest stash it in HttpContext and commit on EndRequest. This is going to create a TransactionScope for every call that asp.net handles.
According to documentation EF by default sets all the changes to database in transaction until SaveChanges() is called.
So you can extract the logic of adding to database to separate methods, create separate method which saves changes to DB and design your code as following:
AddCustomer to DB (do not call save changes)
AddBalance to DB(do not call save changes)
SaveChanges on the end of the in your service method
This will guarantee that all happens in transaction and in case of any error changes to database won't be applied.
I have found that creating an Extension to your Context (i am assuming your are using EF) is a good way to go:
public static class TIContextCompleteExtention
{
public static void Complete(this TIContext context)
{
try
{
context.SaveChanges();
}
catch (Exception ex)
{
throw ex;
}
}
public static async Task CompleteAsync(this TIContext context)
{
try
{
await context.SaveChangesAsync();
}
catch (Exception ex)
{
throw ex;
}
}
}
The Above TIConext is derived from EF's dbContext.
Wherever you need to COMPLETE your transaction use
_context.Complete(); or _context.CompleteAsync() instead of EF's SaveChanges() / SaveChangesAsync()
If you later swap out your ORM all you need to do is change the above extensions to fit your new Context.
Hope this helps.
I've been looking around and can't quite find the answer. I'm using a ViewModel in my Edit View so that I can have values for some dropdownlist. Now when I go to update my DB I'm not understanding how I can update my database record. I'm guessing I could create a new entity object, do a Find, and then update each property based on the ViewModel passed in from the Form but that sure seems like a lot of manual work.
Here I'm using the VeiwModel in the Edit View.
#model CPPCustomerCall.ViewModels.CustomerCallVM
Here is my controller's ActionResult. I changed the object type of the ActionResult to take in CustomerCallVM instead of the CustomerCall which was auto-generated. I assume since the Edit View's model is the ViewModel that's the type of object the ActionResult will receive. However, my ViewModel has more properties that aren't needed for the Entity Model to update the record. How do I go about updating my DB record in this ActionResult?
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,CustomerName,Subject,PhoneNumber,CallMessage,CallDate,Status,CallNotes")] CustomerCallVM customerCall)
{
if (ModelState.IsValid)
{
db.Entry(customerCall).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(customerCall);
}
First, Bind and view models are mutually exclusive. If you don't want something to be eligible to be modified, then it shouldn't be on your view model, in the first place. Barring that, view models diverge from entities in the sense that they can't be saved directly. As a result, there's always some intervention present on your part to map the posted values back onto the entity, which means you can then selectively not map over certain properties that shouldn't be, regardless of whether they were posted or not. Long and short, get rid of the Bind stuff. It's just something else to maintain and a huge source of potential bugs.
That said, the code you have is workable; you're just missing the crucial part where you map the data from your view model back onto your entity. First, you need to fetch the entity from the database so you have a base to work from:
var customerCall = db.CustomerCalls.Find(id);
if (customerCall == null)
{
return new HttpNotFoundResult();
}
FWIW, your edit route should include the id in the route, according to REST conventions. Following REST isn't strictly required, but it's certainly recommended. While a web application adhering to REST doesn't mean it's a good application, not adhering to rest is generally a sure sign of a badly designed and coded application.
Then, you map over your properties. You can either do this manually:
customerCall.CustomerName = model.CustomerName;
// etc.
Or you can use a library like AutoMapper.
mapper.Map(model, customerCall);
AutoMapper requires a bit of initial setup to make this magic work, of course, so review the docs, if you're going that route. Manual mapping is easier, but far more tedious and repetitive.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,CustomerName,Subject,PhoneNumber,CallMessage,CallDate,Status,CallNotes")] CustomerCallVM customerCall)
{
if (ModelState.IsValid)
{
// Find The record you need
var dbObj = CustomerCalls.FirstOrDefault(x=> x.id = customerCall.id);
//Check if null
if(dbObj == null) dbObj = new CustomerCall();
/// Map your properties
// Add object to the stack
if(dbObj.id == 0){
CustomerCalls.Add(dbObj);
}else{
CustomerCalls.Update(dbObj);
}
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(customerCall);
}
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'm using EF4 POCOs and UnitOfWork/repository patterns with MVC 3. I'm trying to understand how I would modify a new record that is to be inserted.
My service method to insert/update looks something like this (the repository is injected in the service constructor via IoC):
public void UpdateData(Guid id, int newValue)
{
MyPoco poco = _repository.FirstOrDefault(p => p.Id = id);
if (poco == null)
{
poco = new Poco
{
//set properties
};
_repository.Add(poco);
}
poco.SomeFieldToUpdate = newValue;
}
And my changes get persisted via my UnitOfWork on a UseUnitOfWorkAttribute action filter on my controller:
void IResultFilter.OnResultExecuted(ResultExecutedContext filterContext)
{
var unitOfWork = IoCFactory.Instance.CurrentContainer.Resolve<IUnitOfWork>();
unitOfWork.Commit();
}
Of course, this works fine if this is ever hit just once, for existing or new data. And it works fine on multiple passes if it already exists.
But if the Guid value doesn't exist in the table, then it tries to do multiple inserts if this is called multiple times.
So that's my dilemma. I understand why this doesn't work, I'm just not sure the proper way to fix it. Basically, I need to somehow get a reference to the existing POCO in the UnitOfWork, and somehow update it. But the UnitOfWork is not available in my service (by design) -- and I'm not even sure I know how to pull an entity out of the UoW and update it anyway.
Am I going about this wrong or am I overlooking something simple here? Or do I have a fundamental flaw in how I've designed this? I have a feeling I may be making this harder than it should be.
Thanks in advance.
The reason why this happens is because your entity is not saved yet and you execute query to get it. Query will not find it in database and correctly return null.
You should not need to use repository / unit of work / ObjectContex as internal storage of not saved entities among service calls. If you need it you should check your application design and refactor it because something is probably wrong.
Anyway you can get not saved entity from context but it is not very nice code. You will need special method on your repository to get entity by id. You will use it instead of calling FirstOrDefault. Something like:
public MyPoco GetById(Guid id)
{
MyPoco enity = context.ObjectStateManager.GetObjectStateEntries(EntityState.Added)
.Where(e => e.Entity != null && e.Entity.GetType() == typeof(MyPoco)))
.Select(e => (MyPoco)e.Entity)
.Where(p => p.Id == id)
.SingleOrDefault();
if (entity == null)
{
entity = context.MyPocos.FirstOrDefault(p => p.Id == id);
}
}
Do you set the id you pass into UpdateData as the key on the new Poco object, like so:
poco = new Poco
{
Id = id;
//set properties
};
If yes, you could query for the object not with FirstOrDefault but by using the TryGetObjectByKey in a repository method:
public Poco GetPocoByKey(Guid id)
{
EntityKey key = new EntityKey("MyEntities.Pocos", "Id", id);
object pocoObject;
if (context.TryGetObjectByKey(key, out pocoObject))
return (Poco)pocoObject;
return null;
}
The advantage is that TryGetObjectByKey looks at first into the ObjectContext if it can find an object with the specified key. Only if not, then the database will be queried. Since you add the new Poco to the context the first time it isn't found, TryGetObjectByKey should find it in the context when you search for the object with the same key a second time, even if it has not been saved to the database yet.
Edit: This solution doesn't work!
Because TryGetObjectByKey does not find the key for objects which are in added state in the ObjectContext, even not if the key is not a DB generated key and supplied by the application (see comments below).
I have a controller attribute called [RequiresCompletedProfile], that I can put on action methods to disallow a user from going there unless they have completed their profile.
This was working fine when I had one kind of user, but the app has since evolved into having 2 kinds of users: Vendors and Clients.
So, there is no longer a "User_Profile" table. There is now a "Client_Profile" and a "Vendor_Profile" and their schemas are different. I should note that I'm using LINQ but I'm mapping all LINQ objects to POCO before returning them out.
My solution was to make an interface called "User_Type", that had the following method:
bool IsProfileCompleted();
Now my Client objects and my Vendor objects can both implement the interface and be responsible for determining if their fields/members constitute their profile being completed.
However, now that I have multiple user types, I can't be sure which table to pull the profile from, so I have to do something like this:
public class RequiresCompleteProfileAttribute : ActionFilterAttribute
{
IUserRepository userRepo = new SqlUserRepository();
IClientProfileRepository clientProfileRepo = new SqlClientProfileRepo();
IVendorProfileRepository vendorProfileRepo = new SqlVendorProfileRepo();
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// Database call #1
var user = userRepo.GetUserByUsername(User.Identity.Name);
UserType profile;
if (user.UserTypeName == "Client")
{
// Database call #2
profile = clientProfileRepo.GetClientByUserName(filterContext.HttpContext.User.Identity.Name);
}
else
{
// Database call #2
profile = vendorProfileRepo.GetClientByUserName(filterContext.HttpContext.User.Identity.Name);
}
if (!profile.IsProfileCompleted())
filterContext.HttpContext.Response.Redirect("/admin/editprofile/");
}
base.OnActionExecuting(filterContext);
}
}
You can see here that I have to make 2 database calls, one to determine the type of the user, and the other to get the profile from the appropriate table.
Is this bad practice? If so, what should I do instead?
It's not exactly bad practice, but you'd be well served by having a middle layer business object that encapsulates the logic of querying the client by username based upon the usertypename.