I'm having trouble figuring out how to edit an existing entity when posting it to my controller. When I save a new Person, it works just fine, because Id isn't set, so NHibernate views it as a new entity and attaches it to the session. However, when I try to edit an existing entity, the MVC model binder can't set the Id, even though the JSON being posted has it properly set. So even though it's an existing entity, NHibernate again sees it as a new one, and then throws an exception because I'm calling .Update() on an entity that's not in the database or session.
Here's the code I'm using (obviously Person has a lot more properties, I just left them off to keep the code short)
Person class:
public class Person
{
public virtual int Id {get; private set;}
//... other properties
}
The JSON being posted to my edit action
{"Id": 10}
And in the controller
public JsonResult EditPerson(Person person)
{
Session.Update(person);
return Json(new { success = true});
}
I was always under the impression that you had to load the entity to get it into the session so that you could edit it.
so you would need code like
var entity = Session.Get<Entity>(entity.Id);
//make your changes
Session.Save(entity);
Try
public virtual int Id {get; protected set;}
NHibernate uses proxies to load and set the properties of your classes, if your setter is private (rather than public or protected) the proxy (which inherits from your mapped class) cannot access it and assign the value it loaded from the database.
Related
I am trying to update an entity using Entity Framework and save it to the database. When the update is called, my service method retrieves the DTO, assigns its the values of the entity object that the UI passed to it, and then saves it to the database. Instead of manually assigning those values, i'd like to use Automapper, however when I do this, the values that I am not mapping are updated to null. Is there a way in Entity Framework or Automapper to prevent this?
Service method finds the existing object in the database, assigns the new entity's properties to it, then saves:
public void Update(MyEntity updatedEntity, int id)
{
var existingObject = db.tblmyentity.Find(id);
existingObject.name = updatedEntity.name;
existingObject.address = updatedEntity.address;
existingObject.phone = updatedEntity.phone;
db.SaveChanges();
}
However, there are values stored in fields of this object not accessible by the UI, such as who modified the object and when. Using AutoMapper to simplify this code (shown below) causes these fields to update to null:
public void Update(MyEntity updatedEntity, int id)
{
var existingObject = db.tblmyentity.Find(id);
Mapper.Map(updatedEntity, existingObject);
db.SaveChanges();
}
A good practice is to create a (service, api) model that contains only the relevant properties that can be updated. E.g.:
public class MyEntityServiceModel
{
public string name { get; set; }
public string address { get; set; }
public string phone { get; set; }
}
// this looks differently in recent versions of AutoMapper, but you get the idea
Mapper.CreateMap<MyEntityServiceModel, MyEntity>();
// your update functions looks the same, except that it receives a service model, not a data model
Update(MyEntityServiceModel updatedEntity, int id)
{
// same code here
}
This approach has the following advantages:
you obtain what you are asking for
safety: you do not risk updating more properties than you should since the service model clearly specify the properties that should be updated
serialization: the service model is more appropriate if you need serialization (EF models may include unwanted navigation properties)
Update function consumer becomes unaware of the data persistence library you are using.
From the documentation:
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.
Entities setup:
public class Page{
public Page () {
Event = new HashSet<Event>();
}
[Key]
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; } // don't want to retrieve, too large
public ICollection<Event> Event { get; set; }
}
public class Event{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public Page Page { get; set; }
}
The context is set up with a One-To-Many relationship.
These are the queries I run, one after the other:
var pages = _dbContext.Page.Select(page => new Page
{
Id = page.Id,
Title = page.Title
}).ToList();
var events = _dbContent.Event.ToList();
I expect each Page to have the Events collection populated (and vice-versa for Event with the Page reference), but the fix-up doesn't happen (Page in Event is null, and Event in Page is null).
If I replace the first query by this, then the fix-up works:
var pages = _dbContext.Page.ToList();
So it seems that with projection the fix-up doesn't happen. The reason I split this in 2 queries was to avoid using something like Include which would make a huge join and duplicate plenty of data.
Is there any way around that? Do I need to do the fix-up manually myself?
When you project into a new type yourself in the query, EF Core does not track the object coming out of the query even if they are of type an entity which is part of Model. This is by design.
Since in your case Pages are not getting tracked, Events have nothing to do fixup with. Hence you are seeing null navigation properties.
This behavior was same in previous version (EF6). The main reason for not tracking is, as in your case, you are creating new Page without loading Content. If we track the new entity then it will have Content set to null (default(string)). If you mark this whole entity as modified then SaveChanges will end up saving null value in Content column in database. This would cause data loss. Due to minor error could cause major issue like data loss, EF Core does not track entities by default. Another reason is weak entity types (or complex types in EF6) which share CLR type with other entities but uniquely identified through Parent type, if you project out such entity then EF Core cannot figure out which entity type it is without parent information.
You could put those entities in changetracker by calling Attach method, which will cause fix up and you will get desired behavior. Be careful not to save them.
In general the scenario you want is useful. This issue is tracking support for that in EF Core.
I don't think that should work. Did you verify this behavior worked in previous versions of EntityFramework? Since, you aren't pulling out the full entity, and only properties of it, and then passing it into a new Entity, you are essentially just Selecting properties and creating a new Entity.
If you would like this to attach you can manually call the Attach Method after selecting your page
var pages = _dbContext.Page.Select(page => new Page
{
Id = page.Id,
Title = page.Title
}).ToList();
pages.ForEach(p => _dbContext.Page.Attach(p));
Keep in mind that if you call SaveChanges After this you will lose the unloaded properties, so only use this when calling Get Methods
So, I have a problem in save data which contains related entities, when I save it a new relation blank is created.
Exemple:
Entities:
public class Project
{
public int Id { get; set; }
public int Code{ get; set; }
public string Description{ get; set; }
public virtual Client Client { get; set; }
}
public class Client
{
public int Id { get; set; }
public int Code { get; set; }
public string Name { get; set; }
}
The Controller GET:
public ActionResult Create()
{
PopulateDropDownClienteList(String.Empty); //Returns to ViewBag to create a combobox .in view
return View();
}
The View:
#Html.DropDownListFor(m => m.Client.Id, new SelectList(ViewBag.Client_Id, "Id", "Name"), new { Name = "Client.Id" });
The Controller POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(string command, Project project)
{
try
{
if (ModelState.IsValid)
{
projectRepository = new ProjeRepository();
Project pro = projectRepository.ReturnByCode(project.Code);
if (pro == null)
projectRepository.Save(project);
else
projectRepository.Update(project);
PopulateDropDownClienteList(String.Empty);
Return View();
}
else
{
return View(project);
}
}
catch (Exception ex)
{
return View();
}
}
So when I save the data, the client is not associated with the project. just creating a new blank Client.
You Project Save code is not updating the entity, it is ADDING a new one all the time.
You should have update logic similar to following grounds -
To Add new FK Entry and associate it with parent record -
var entity = entities.Students.Where(p => p.Id == "2").First();
entity.StudentContact = new StudentContact() { Contact = "xyz", Id = "2" };
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
// other changed properties
entities.SaveChanges();
To update a FK record with new details -
var entity = entities.Students.FirstOrDefault();
entity.StudentContact.Contact = "ABC";
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
entry.Property(e => e.StudentContact.Contact).IsModified = true;
// other changed properties
entities.SaveChanges();
The above code, I have a Student records which has FK relationship with StudentContacts. I updated Contact information of a student and then updated it to database using ATTACH.
You've got a number of issues here, so let me break them down.
First and foremost, do not ever catch Exception (at least without throwing it again). There's two very important things about using try...catch blocks: you should only wrap the code where you're expecting an exception (not nearly your entire method as you've done here), and you should only catch the specific exception you're expecting (not the base type Exception). When you catch Exception, any and every exception that could possibly be generated from your code will be caught, and in this case, simply discarded, which means you really will never know if this code works at all.
Second, you have a fine method that generates a dropdown list of choices, but never store the user's selection anywhere meaningful. To understand why, you need to stop and think about what's happening here. An HTML select element has a string value and a string text or label component. It does not support passing full objects back and forth. I can't see what your PopulateDropDownClienteList method does, but what it should be doing is creating an IEnumerable<SelectListItem>, where each item gets its Text property set to whatever you want displayed and its Value property to the PK of the Client. However, once you have that, you need some property on Project to post back to. Your virtual Client won't work as that needs a full Client instance, which your form will never have. So, you have two choices:
Implement a view model to feed to the view (and accept in the post). In that view model, in addition to all other editable fields, you'll include something like ClientId which will be an int type, and you'll bind this to your drop down list. Once you're in your post method, you map all the posted values to your project instance, and then use the ClientId to look up a client from the database. You then set the resulting client as the value for your Client property and save as usual.
You alter your database a bit. When you just specify a virtual, Entity Framework smartly creates a foreign key and a column to hold that relationship for you behind the scenes. That's great, but in situations like this, where you actually need to access that foreign key column, you're screwed. That way around that is to explicitly define a property to hold that relationship on your model and tell Entity Framework to use that instead of creating its own.
[ForeignKey("Client")]
public int ClientId { get; set; }
public virtual Client Client { get; set; }
With that, you can now directly use ClientId without worrying about filling in Client. You again bind your drop down list to ClientId, but now, you do not need to look up the client explicitly from the database. Entity Framework will just save the ClientId as it should to the database, and then restore the Client based on that when you look up the project again in the future.
I'm sure I am misunderstanding something fundamental about how EF5 works.
In a [previous question] I asked about how to pass values between actions in an ASP.NET MVC application and it was suggested I could use TempData as a mechanism to pass around data (in my case I've gone for the POCOs that represent my data model in EF).
My controllers in MVC are not aware of any persistence mechanism within EF. They make use of a service layer which I've called "Managers" to perform common tasks on my POCOs and read/persist them to the underlying datastore.
I'm writing a workflow to allow an "employee" of my site to cancel a "LeaveRequest". In terms of controllers and actions, there's an HttpGet action "CancelLeaveRequest" which takes the ID of the LeaveRequest in question, retrieves the LeaveRequest through the service layer, and displays some details, a warning and a confirm button. Before the controller returns the relevant View, it commits the LeaveRequest entity into TempData ready to be picked up in the next step...
The confirm button causes an HttpPost to "LeaveRequest" which then uses the LeaveRequest from TempData and a call down to the service layer to make changes to the LeaveRequest and save them back to the database with EF.
Each instance of a manager class in my code has it's own EF DBContext. The controllers in MVC instantiate a manager and dispose of it within the page lifecycle. Thus, the LeaveRequest is retrieved using one instance of a DBContext, and changes are made and submitted via another instance.
My understanding is that the entity becomes "detached" when the first DBContext falls out of scope. So, when I try to commit changes against the second DBContext, I have to attach the entity to the context using DBContext.LeaveRequests.Attach()? There is an added complication that I need to use an "Employee" entity to note which employee cancelled the leave request.
My code in the service layer for cancelling the leave request reads as follows.
public void CancelLeaveRequest(int employeeId, LeaveRequest request)
{
_DBContext.LeaveRequests.Attach(request);
request.State = LeaveRequestApprovalState.Cancelled;
request.ResponseDate = DateTime.Now;
using (var em = new EmployeesManager())
{
var employee = em.GetEmployeeById(employeeId);
request.Responder = employee;
_DBContext.Entry(request.Responder).State = System.Data.EntityState.Unchanged;
}
_CommitDatabaseChanges();
}
You can see that I retrieve an Employee entity from the EmployeesManager and assign this employee as the responder to the leave request.
In my test case, the "responder" to the Leave Request is the same employee as the "requestor", another property on Leave Request. The relationships are many-to-one between leave requests and a requesting employee, and many-to-one between leave requests and a responding employee.
When my code runs in it's present state, I get the following error:
AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges.
I suspect this is because EF thinks it knows about the employee in question already. The line that fails is:
_DBContext.Entry(request.Responder).State = System.Data.EntityState.Unchanged;
However, if I remove this line and don't try to be clever by telling EF not to change my employee object, the leave request gets cancelled as expected but some very strange things happen to my Employees.
Firstly, the employee who made/responded to the request is duplicated. Then, any navigation properties (like "Manager", a many-to-one relationship between an Employee and other Employees) seem to get duplicated too. I can understand that the duplication of the Manager property on Employee is because I am loading the Manager object graph in as part of GetEmployeeById and I think I understand that the original Employee is being duplicated because, as far as the LeaveRequest DBContext is concerned, it has just appeared out of nowhere (I retrieved the Employee through a different DBContext). However, assuming those two points are correct, I'm at a loss as to how I can a) prevent the Employee and it's associated object graph being duplicate in the database and b) how I can ensure the modified LeaveRequest is persisted correctly (which it seems to stop doing with various combinations of attaching, changing state to modified etc... on the employee and leave request).
Please can someone highlight the error of my ways?
My LeaveRequest entity:
public class LeaveRequest
{
public LeaveRequest()
{
HalfDays = new List<LeaveRequestHalfDay>();
}
public int CalculatedHalfDaysConsumed { get; set; }
public Employee Employee { get; set; }
public virtual ICollection<LeaveRequestHalfDay> HalfDays { get; set; }
public int LeaveRequestId { get; set; }
public DateTime RequestDate { get; set; }
public int ResponderId { get; set; }
public virtual Employee Responder { get; set; }
public DateTime? ResponseDate { get; set; }
public LeaveRequestApprovalState State { get; set; }
public LeaveRequestType Type { get; set; }
public ICollection<LeaveRequest> ChildRequests { get; set; }
public LeaveRequest ParentRequest { get; set; }
}
The "Employee" field (of type Employee...) is the person who submitted the request. The "Responder" is potentially a different, but could be the same, employee.
You should change your navigation properties to this:
public int ResponderId {get;set;}
public virtual Employee Responder { get; set; }
This scalar property will be auto-mapped to the navigation property by EF. Next you can simply do the following (and you don't need the Unchanged state):
var employee = em.GetEmployeeById(employeeId);
request.ResponderId = employee.Id;
See also this article about relationships in EF.
I am using Asp.net MVC and Entity framework (Below code is just for demo)
I have a model like below
public class Person
{
[Key]
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
/*Time at which person was created*/
public DateTime CreatedOn { get; set; } /*should not change once created*/
}
At the time of creating + inserting new person, i manually set CreatedOn datetime property.
On Update
My View only has one textbox
#using (Html.BeginForm())
{
#Html.LabelFor(a => a.FirstName)
#Html.EditorFor(a => a.FirstName)
#Html.ValidationMessageFor(a => a.FirstName)
<br />
<input type="submit" name="name" value="submit" />
}
Controller
[HttpPost]
public ActionResult EditPerson(Person person)
{
if (ModelState.IsValid)
{
db.Entry(person).State = EntityState.Modified;
db.SaveChanges();
/* ----------------
Error here
-----------------
*/
return RedirectToAction("ListPerson");
}
return View(person);
}
I am getting error at mentioned point in my code , error: The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value. Error seems obvious as person object received inside controller has default time-value, and entity framework tries to update that field too.
My question is how can i instruct entity framework not to update a property. Suppose in above case if for CreatedOn property i say to entity framework do not update it, i wont get error.
I tried [Editable(false)] attribute on CreatedOn but it didn't work.
There are some options like , before updating i first load the existing entity from Database and copy createdOn property.....
I want you know that in my real model there many properties that i dont want to update and there are many such models, so I am expecting some realistic answers.
Edit 1
I am using code first.
The specific error you're getting is because the CreatedOn is being set to the default value. When you do your postback you'll have to load the object from the database, update it and then save it. By doing that you'll stop this particular error that you're experiencing.
It appears that you're using your entity model as the model in your controller. I wouldn't recommend this. By creating a model specifically for the post you can remove any non-editable fields from your model. Then you would copy the model values from the ViewModel to your Entity model. This prevents users from editing properties that you might not want them to edit.
public class PersonViewModel
{
[Key]
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
}
The main reason this is a good idea is that someone may try to guess what values might also work. For instance, if you had a property on your Person object called IsAdministrator and I were to intercept the post and add &IsAdministrator=on&Adminstrator=on&Admin=on (to cover a few bases) then the ModelBinder will take that value and apply it to the model. Now I've just become an administrator in your system. I realize this is a lot of work if you have a lot of models but it's never a good idea to use your entity models for posts.
You can also use the Bind attribute to limit the items bound by the mapper.
public ActionResult EditPerson([Bind(Exclude = "CreatedOn")] Person person)
This should prevent the binder from binding that particular property. However it won't fix your problem since when the person object is created the default value is always set. The only way to fix this is to load your object from the database and then update/save it.