I am unable to update the entity. Below is the code I used to update the entity. It works the first time its updated. But it fails the next time.
Generic method
public TEntity Update(TEntity entity)
{
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
return entity;
}
Controller
public IHttpActionResult Put(Invoice invoice)
{
return Ok(invoiceService.UpdateInvoice(invoice));
}
Service
public Invoice UpdateInvoice(Invoice invoice)
{
return _repo.Update(invoice);
}
The error that is coming.
System.InvalidOperationException: 'Attaching an entity of type
'Models.Invoice' failed because another entity of the same type
already has the same primary key value. This can happen when using the
'Attach' method or setting the state of an entity to 'Unchanged' or
'Modified' if any entities in the graph have conflicting key values.
This may be because some entities are new and have not yet received
database-generated key values. In this case use the 'Add' method or
the 'Added' entity state to track the graph and then set the state of
non-new entities to 'Unchanged' or 'Modified' as appropriate.'
Update:
See below for the answer.
It looks you haven shared all the necessary code. But the solution is as as below. Your method is taking some action and it is expecting to return some value which is causing the issue.
Here to solve this issue you need to include the code inside Try catch block and handle this exception.
try{
//your code
}
catch(InvalidOperationException){
//you may keep this section blank.
}
It may be not the best solution available. But for anyone who comes across this issue, I resolved it by re-initializing the context as it was not recognizing the the entity as already available. (By the id)
public TEntity Update(TEntity entity)
{
_context = new DbContex();
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
return entity;
}
Related
Situation
We have a controller where users can submit any number of E-Mail addresses to invite other (potential) members as friends. If an address is not found in the database, we send an E-Mail message to that user. Since the user does not has to wait for this process to complete in order to continue working this is done asynchronously.
Sending E-Mails can take a long time if servers respond slowly, are down or overloaded. The E-Mail sender should update the database according to the status received from the E-Mail server, so for example setting the friend request into "Error" state, when a permanent failure occurs, for example if the address does not exists. For this purpose, the E-Mail component implements the function SendImmediateAsync(From,To,Subject,Content,Callback,UserArg). After the message has been delivered (or it failed), the Callback is called with certain arguments about the Delivery state.
When it eventually calls the delegate, the DbContext object has already been disposed (since the controller has been too) and I cannot manually create a new one using new ApplicationDbContext() because there is no constructor that accepts a connection string.
Question
How can I write to the database long after the controller has been disposed? I have not yet figured out how to manually create a DbContext object for myself. An object of type ApplicationDbContext is passed to the constructor of the Controller and I hoped I could instantiate one for myself, but the Constructor has no arguments I can supply (for example connection string). I want to avoid to manually create an SQL Connection and assemble INSERT statements manually and would prefer to work with the entity model we have already set up.
Code
The code shows the affected segment only without any error checking for readability.
[Authorize]
public class MembersController : Controller
{
private ApplicationDbContext _context;
public MembersController(ApplicationDbContext context)
{
_context = context;
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Friends()
{
MailHandler.SendImmediateAsync(FROM,TO,SUBJECT,CONTENT,
delegate (Guid G, object any)
{
//THIS IS NOT WORKING BECAUSE _context IS DISPOSED
var ctx = _context;
Guid Result = (Guid)any; //user supplied argument
if (G != Guid.Empty)
{
ctx.MailConfirmation.Add(new MailConfirmation()
{
EntryId = Result,
For = EntryFor.FriendRequest,
Id = G
});
if (G == MailHandler.ErrorGuid)
{
var frq = _context.FriendRequest.SingleOrDefault(m => m.Id == Result);
frq.Status = FriendStatus.Error;
ctx.Update(frq);
}
ctx.SaveChanges();
}
}, req.Id);
//rendering view
}
}
First of all, when you are using EF Core with ASP.NET Core's dependency injection, each DbContext instance is scoped per-request, unless you have specified otherwise in ".AddDbContext". This means you should not attempt to re-use an instance of DbContext after that HTTP request has completed. See https://docs.asp.net/en/latest/fundamentals/dependency-injection.html#service-lifetimes-and-registration-options
DbContextOptions, on the other hand, are singletons and can be re-used across requests.
If you need to close the HTTP request and perform an action afterwards, you'll need to create a new DbContext scope an manage it's lifetime.
Second of all, you can overload DbContext's base constructor and pass in DbContextOptions directly. See https://docs.efproject.net/en/latest/miscellaneous/configuring-dbcontext.html
Together, this is what a solution might look like.
public class MembersController : Controller
{
private DbContextOptions<ApplicationDbContext> _options;
public MembersController(DbContextOptions<ApplicationDbContext> options)
{
_options = options;
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Friends()
{
MailHandler.SendImmediateAsync(FROM,TO,SUBJECT,CONTENT, CreateDelegate(_options) req.Id);
}
private static Action<Guid, object> CreateDelegate(DbContextOptions<ApplicationDbContext> options)
{
return (G, any) =>
{
using (var context = new ApplicationDbContext(options))
{
//do work
context.SaveChanges();
}
};
}
}
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base (options) { }
// the rest of your stuff
}
This assumes, of course, that your "MailHandler" class is properly using concurrency to run the delegate so it doesn't block the thread processing the HTTP request.
Why not just pass the dbContext as the userArgs to your SendImmediateAsync? Then the dbContext will not get disposed and can be passed back when you do the callback. I'm pretty sure that should work.
I have 3 action name delete on 3 different controllers which are on "registration,profile and questions" they all have action delete methods. How can I from my registration-delete method call out profile-delete and questions-delete . That way when a user wants to delete their account all they have to do is go on registration-delete instead of going on registration,profile and questions delete methods. I want in 1 [HttpPost, ActionName("Delete")](registration) to call out the other 2 ActionName("Delete") methods as i prefer people to delete everything in one place is this possible? assuming that each user shares a unique ID that is the same all across. The comment in the code im just using to illustrate any help would be great
registration-delete below
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed()
{
var ss = User.Identity.Name;
var getid = (from s in db.registration where ss == s.email select s.RegistrationID).FirstOrDefault();
registration registration = db.registration.Find(getid);
//This delete's the registration
db.buyers.Remove(registration);
// How can i call-out profile-delete actionname here and questions-delete like
//if (question-delete != null){
// include ActionResult deleteconfirmed("question-delete" }
db.SaveChanges();
return RedirectToAction("logout");
}
If I understand your question, you are asking to call other controller's action method from the currently executing action method? You typically would not do that. The first rule of Controller's action methods is: no action method should be more than 10 lines of code. Basically, an action method is really supposed to be a simple method to collect a view, or call an action in your domain, and return.
In other words, the SRP pattern:
http://codebetter.com/karlseguin/2008/12/05/get-solid-single-responsibility-principle/
Instead, you would organize your Domain-logic (what you describe is considered domain model logic, not controller logic) for this repetitive code such as deleting questions here, but when user is deleted also delete questions, etc.
// an example of IOC injection of your IUserService
private IUserService
public RegistrationController(IUserService userService)
{
_userService = userService;
}
[HttpPost]
public ActionResult Delete()
{
// insert modelstate and/or validation logic
//
if (User.Identity.IsAuthenticated == false)
{
return RedirectToAction("index", "Home");
}
// good practice to never bubble up exceptions to users
try
{
if (_userService.DeleteByEmail(User.Identity.Name) == false)
{
ModalState.AddModelError(String.Empty, "Not allowed?");
return View();
}
// save it all in a single atomic operation here to keep it all in
// a single Transaction Scope that will rollback properly if there is
// an error.
//
db.SaveChanges();
}
catch (Exception)
{
ModalState.AddModelError(String.Empty, "An error occurred...");
return View();
}
// all ok!
return RedirectToAction("logout");
}
Notice how clean this action method is. It just gets down to business with a single line of code or two, and a whole bunch of exit paths to handle the user's experience properly in many different situations.
Now, your domain logic can be encapsulated into a service (or provider, or alike):
namespace MyWebsite.Models
{
public class UserService : IUserService
{
// Todo: convert to some IoC lovin'
//
public Boolean DeleteByEmail(String email)
{
var user =
(from user in db.Registrations
where
user.Email == email
select s).FirstOrDefault();
if (user == null)
return false;
// call into other services to delete them
ProfileDataService.DeleteByUserId(user.UserId);
QuestionsService.DeleteByUserId(user.UserId);
// finally, delete the user
db.Registrations.Delete(user);
// note, I am NOT calling db.SaveChanges() here. Look
// back at the controller method for the atomic operation.
//
return true;
}
}
}
This can be implemented 100s of different ways. The point being is to abstract that logic out to a common code base, or "Domain". I chose to put that logic in your current Website namespace under Models as a shortcut in this example.
As for your other Delete() methods on your other controllers, you would just call into a QuestionsService.DeleteByUserId() and ProfileDataService.DeleteByUserId() to handle those. You can even share those services across the domain, as I showed above.
I have the following method in a generic base class:
public virtual void Insert(TEntity entity) {
dbSet.Add(entity);
}
My service layer uses the Insert method to add new records. Now I would like to be able to return a bool, to make sure that it inserted properly. Something like the following:
public virtual int Count {
get {
return dbSet.Count();
}
}
public virtual bool Insert(TEntity entity) {
int count = this.Count;
dbSet.Add(entity);
return this.Count == count + 1;
}
Is there a more elegant way to this? Am I approaching it completely incorrectly? I could do something similar for the Delete method, but how would I check if an Update has been performed successfully?
You don't need to do that. Add will either succeed or throw an exception. It cannot fail to add and return without throwing.
What's more, the query is not executed immediately. It's just queued until Commit (context.SaveChanges()) is called. So you don't know whether the actual SQL fails or not until later.
I'm working on a simple application and am looking to get my head around the CodeFirst EF approach. So far so good.
I have managed to get Create and delete sorted and have got edit working. The thing is I think the edit code could be improved; I'm just not sure how. So here it is:
public ActionResult Edit(int id, CreateResourceViewModel model)
{
if (ModelState.IsValid)
{
// save the changes
//UpdateModel(model.Resource);
//resourceAdminManager.SaveChanges();
Resource current = resourceAdminManager.Resources.Find(id);
current.ResourceTypeID = model.Resource.ResourceTypeID;
current.Name = model.Resource.Name;
current.Description = model.Resource.Description;
current.Email = model.Resource.Email;
current.TurnAroundTime = model.Resource.TurnAroundTime;
resourceAdminManager.SaveChanges();
return RedirectToAction("Index");
}
else
{
return View(model);
}
}
I know there is no exception handling around this, which I need to address but my main concern is the fact that I have manually updated the model. My concerns are:
1. This is in the controller
2. This is hard coded and so any changes to the model will require a re-work of code
Can someone suggest a better way of doing this please.
Many thanks
Nathan
So following on from the AutoMapper suggestions:
This is very helpful and I've started to play with this. I'm running into a little trouble with it.
The contoller now looks like:
if (ModelState.IsValid)
{
try
{
var current = resourceAdminManager.Resources.Find(id);
current = Mapper.Map<CreateResourceViewModel, Resource>(model);
resourceAdminManager.SaveChanges();
return RedirectToAction("Index");
}
catch (Exception exc)
{
ModelState.AddModelError("Error", exc); // or, use a generic error.
}
}
return View(model);
The error occurs in the view when I click save. I get null exception on the following:
<%: Html.DropDownListFor(model => model.Resource.ResourceTypeID, new SelectList(Model.ResourceTypes, "ResourceTypeId", "Title"), "-- Select Resource Type --")%>
Any ideas on what I may be missing here?
One word: AutoMapper.
Will turn those 5 left-to-right boilerplate lines into one.
One of the best MVC (well, C#) add-on's i've ever found.
If implemented correctly (i think your ViewModel may need to change a tad), your action will look like this:
public ActionResult Edit(int id, CreateResourceViewModel model)
{
if (ModelState.IsValid)
{
try
{
var current = resourceAdminManager.Resources.Find(id);
current = Mapper.Map<CreateResourceViewModel,Resource>(model);
resourceAdminManager.SaveChanges();
return RedirectToAction("Index");
}
catch (Exception exc)
{
ModelState.AddModelError("Error", exc); // or, use a generic error.
}
}
return View(model);
}
A warning: this will basically replace the entire entity. So if you only want certain fields changed, only include those in the ViewModel, so the other's will be unchanged. You can also specify "Ignore" in AutoMapper so those fields won't get touched.
And to address your concerns:
This is in the controller
Well actually, it should be. In fact, that's the controllers job - updating the model.
The fact you have boilerplate L-R code is bad, which is why AutoMapper helps.
This is hard coded and so any changes to the model will require a re-work of code
Yup, again - AutoMapper solves this. It works on convention (like MVC), so if you have a proeprty in the ViewModel with the same name as the target model (domain object), you won't have to explicitly map -> it will just work. Furthermore, the mapping is a single static class, so it can be easily maintained.
You are using ViewModel which is very good practise.
1] For Exception handling will suggest to inherit your Controller
from your custom "BaseControllor" instead of System.Web.Mvc.Controller
public class YourController : BaseController
In "BaseControllor" override OnException so that all excptions across your Controllor Actions will be catch in it.
public class BaseController: Controller
{
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
}
}
2] You need to refactor your "Save" code into differnt class in same project or differnt project and differnt Class
3] Yes for any Changes in model you would need to make changes in viewmodel, and in your save logic
I have on post action in controller. Code is as given below
[HttpPost]
public ActionResult Create(Int64 id, FormCollection collection)
{
var data = Helper.CreateEmptyApplicationsModel();
if (TryUpdateModel(data))
{
// TODO: Save data
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
else
{
// TODO: update of the model has failed, look at the error and pass back to the view
if (!ModelState.IsValid)
{
if (id != 0) Helper.ShowLeftColumn(data, id);
return View("Create", data);
}
}
return RedirectToAction("Details", "Info", new { area = "Deals", InfoId= id });
}
I wrote test case for this as below
[TestMethod]
public void CreateTest_for_post_data()
{
var collection = GetApplicantDataOnly();
_controller.ValueProvider = collection.ToValueProvider();
var actual = _controller.Create(0, collection);
Assert.IsInstanceOfType(actual, typeof(RedirectToRouteResult));
}
When I debug this single test case, test case passed because condition
if (TryUpdateModel(data)) return true and its goes to if condition.
But when I debug test cases from entire solution this test case failed because it goes to else condition of " if (TryUpdateModel(data))".
I dont know why..
Please help...
Thanks
I've experienced a similar problem which will solve your issue provided you don't need to use a FormCollection.
I haven't used TryUpdateModel ever since the day I learned of the Auto-Binding feature. Auto-binding, in a nutshell pretty much does the work TryUpdateModel would do for you, that is, it'll set a model object according to the values found in the FormCollection as well as attempting to validate the model. And it does this automatically. All you'll have to do is place a parameter in the ActionMethod and it will automatically have it's properties filled with the values found in the FormCollection. Your Action signature will then turn into this:
public ActionResult Create(Int64 id, SomeModel data)
Now you don't need to call TryUpdateModel at all. You still need to check if the ModelState is valid to decide whether or not to redirect or return a view.
[HttpPost]
public ActionResult Create(Int64 id, SomeModel data)
{
if (ModelState.IsValid)
{
// TODO: Save data
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
else
{
if (id != 0) Helper.ShowLeftColumn(data, id);
return View("Create", data);
}
}
This won't throw an exception in your unit tests so that's one problem solved. However, there's another problem now. If you run your application using the above code, it'll work just fine. You model will be validated upon entering the Action and the correct ActionResult, either a redirect or a view, will be returned. However, when you try to unit test both paths, you'll find the model will always return the redirect, even when the model is invalid.
The problem is that when unit testing, the model isn't being validated at all. And since the ModelState is by default valid, ModelState.IsValid will always return true in your unit tests and thus will always return a redirect even when the model is invalid.
The solution: call TryValidateModel instead of ModelState.IsValid. This will force your unit test to validate the model. One problem with this approach is that means the model will be validated once in your unit tests and twice in your application. Which means any errors discovered will be recorded twice in your application. Which means if you use the ValidationSummary helper method in your view, your gonna see some duplicated messages listed.
If this is too much to bear, you can clear the ModelState before calling TryValidateModel. There are some problems with doing so because your gonna lose some useful data, such as the attempted value if you clear the ModelState so you could instead just clear the errors recorded in the ModelState. You can do so by digging deep into the ModelState and clearing every error stored in every item like so:
protected void ClearModelStateErrors()
{
foreach (var modelState in ModelState.Values)
modelState.Errors.Clear();
}
I've placed the code in a method so it can be reused by all Actions. I also added the protected keyword to hint that this might be a useful method to place in a BaseController that all your controllers derive from so that they all have access to this method.
Final Solution:
[HttpPost]
public ActionResult Create(Int64 id, SomeModel data)
{
ClearModelStateErrors();
if (ModelState.IsValid)
{
// TODO: Save data
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
else
{
if (id != 0) Helper.ShowLeftColumn(data, id);
return View("Create", data);
}
}
NOTE: I realize I didn't shed any light on the root issue. That's because I don't completely understand the root issue. If you notice the failing unit test, it fails because a ArgumentNullException was thrown because the ControllerContext is null and it is passed to a method which throws an exception if the ControllerContext is null. (Curse the MVC team with their damned defensive programming).
If you attempt to mock the ControllerContext, you'll still get an exception, this time a NullReferenceException. Funnily enough, the stack trace for the exception shows that both exceptions occur at the same method, or should I say constructor, located at System.Web.Mvc.ChildActionValueProvider. I don't have a copy of the source code handy so I have no idea what is causing the exception and I've yet to find a better solution than the one I offered above. I personally don't like my solution because I'm changing the way I code my application for the benefit of my unit tests but there doesn't seem to be a better alternative. I bet the real solution will involve mocking some other object but I just don't know what.
Also, before anyone gets any smart ideas, mocking the ValueProvider is not a solution. It'll stop exceptions but your unit tests won't be validating your model and your ModelState will always report that the model is valid even when it isn't.
You might want to clean up your code a bit:
[HttpPost]
public ActionResult Create(int id, FormCollection collection)
{
var data = Helper.CreateEmptyApplicationsModel();
if (!ModelState.IsValid)
{
if (id != 0)
{
Helper.ShowLeftColumn(data, id);
}
return View("Create", data);
}
if (TryUpdateModel(data))
{
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
return RedirectToAction("Details", "Info", new { area = "Deals", InfoId= id });
}
Don't use Int64, just use int.
As for your failing test, I would expect your test to fail all the time since TryUpdateModel will return false. As your running the code from a unit test, the controller context for a controller is not available, thus TryUpdateModel will fail.
You need to somehow fake/mock TryUpdateModel so that it does not actually run properly. Instead you "fake" it to return true. Here's some links to help:
How do I Unit Test Actions without Mocking that use UpdateModel?
The above SO answer shows an example using RhinoMocks which is a free mocking framework.
Or this:
http://www.codecapers.com/post/ASPNET-MVC-Unit-Testing-UpdateModel-and-TryUpdateModel.aspx
Debug your tests and check the modelstate errors collection, all the errors that the tryupdatemodel encountered should be there.