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.
Related
I am getting SqliteException: SQLite Error 1: 'foreign key mismatch - "Rental" referencing "Movie"'.
CREATE TABLE Movie (
title VARCHAR(30) NOT NULL,
description VARCHAR(300) NOT NULL);
CREATE TABLE Rental (
user_id TEXT ,
movie_id INT ,
FOREIGN KEY (user_id) REFERENCES AspNetUsers(Email),
FOREIGN KEY (movie_id) REFERENCES Movie(rowid));
with following code.
public async Task OnGetAsync(int movie_id, string email)
{
Rental newRental = new Rental();
newRental.movie_id=movie_id;
newRental.user_id=email;
_context.Rental.Add(newRental);
_context.SaveChanges();
}
Movie table has implicit rowid automatically added by SQLlite
What am I doing wrong?
A couple things: Movie appears to be missing a PK. When using identity (auto-generated) keys EF needs to be told about them as well. It can deduce some by naming convention, but I recommend being explicit to avoid surprises. Your entities will need to be set up for the appropriate relationships:
I.e.
public class Movie
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int movie_id { get; set; }
// ...
}
public class Rental
{
[Key, Column(Order=0)]
public int movie_id { get; set; }
[Key, Column(Order=1)]
public int user_id { get; set; }
// ...
}
Unfortunately this doesn't really tell EF that there is a relationship between Movie and Rental. (Or User and Rental) From an application point of view, without that relationship,
what guarantee is there that the Movie_id your call receives from a client exists? It's also a bit strange that this method is listed as an "OnGet" type method which implies a GET action rather than a POST or PUT action.
Typically with EF you will want to leverage navigation properties for you domain rather than just exposing FKs. Also, you are defining this as an async method without awaiting any async operations.
I would recommend avoiding composite keys unless truly necessary, so give Rental a Rental ID and just rely on many to one relationships for the Movie and User references:
public class Rental
{
[Key, DatabaseGenerate(DatabaseGeneratedOption.Identity)]
public int rental_id { get; set; }
// ....
public int movie_id { get; set; }
[ForeignKey("movie_id")]
public virtual Movie Movie { get; set; }
public int user_id { get; set; }
[ForeignKey("user_id")]
public virtual User User { get; set; }
}
public void RentMovie(int movie_id, string email)
{
var movie = _context.Movies.Single(x => x.Movie_Id == movie_id);
// Better would be to go to the Session for the current logged in user rather than trusting what is coming from the client...
var user = _context.Users.Single(x => x.Email = email);
try
{
Rental newRental = new Rental
{
Movie = movie;
User = user;
};
_context.Rental.Add(newRental);
_context.SaveChanges();
}
catch
{ // TODO: Handle what to do if a movie could not be rented.
}
}
In the above example we attempt to load the requested movie and user. If these do not exist, this would fall through to the global exception handler which should be set up to end the current login session and log the exception. (I.e. any tampering or invalid state should be captured and log the user out.) Where when attempting to record the rental, the exception handling can be set up to display a message to the user etc. rather than a hard failure.
You can go a step further and remove the FK properties from the entities and use Shadow Properties (EF Core) or .Map(x => x.MapKey()) (EF6) to set up the relationship. This avoids having two sources of truth for viewing/updating relationships between entities.
Optionally the Movie object could have an ICollection<Rental> where Rentals could have a RentedDate and ReturnedDate for instance so that movies could be inspected to see if a copy was available to rent. I.e. searching by name then determining if one or more copies is currently in-stock. A Rental record could be added to movie.Rentals rather than treating Rentals as a top-level entity.
Using navigation properties is a powerful feature of EF and can accommodate some impressive querying and data retrieval options via Linq without reading a lot of records and piecing things together client side.
I am not fantastic with EF so maybe it's an easy one.
I Have
public void DeleteLicense(int licenseId)
{
var entityToDelete = context.Licenses.Find(licenseId);
context.Licenses.Remove(entityToDelete);
}
I have checked that it finds correct license, and context is a ninject (one per request) DbContext,
But I get a weird error when I call SaveChanges() on the context after running the function above. I get: "The CustomerName field is required."
Now this is weird because CustomerName is in Account (not Licence) they are linked, but still. So here follows some more:
My Account entity
[Required]
public String CustomerName { get; set; }
public virtual ICollection<License> Licenses { get; set; }
...
My License entity
public virtual Account Account { get; set; }
...
My fluent setup
modelBuilder.Entity<Account>().HasMany(x => x.Licenses)
.WithRequired(x => x.Account).WillCascadeOnDelete(false);
I don't understand, because even if there is a failing restraint then why missing CustomerName. I don't touch CustomerName when I delete a license and the CustomerName is set since before.
Update
So here is some more details from the code. The full execution path as far as I can see is
DeleteLicenseAPI below takes the call, the ID is correct, it passes over to a private function.
The private function calls the DeleteLicense shown close to the top of the question.
The Commit() only calls context.SaveChanges();
public ActionResult DeleteLicenseAPI(int licenseId)
{
if (DeleteLicense(licenseId))
{
return Content("ok");
}
return Content("[[[Failed to delete license]]]");
}
private bool DeleteLicense(int licenseId)
{
//todo: sort out busniess rules for delete, is cascaded?
_accountRepository.DeleteLicense(licenseId);
_accountRepository.Commit();
return true;
}
The _accountRepository looks like this
public class EFAccountRepository : EntityFrameworkRepository<Account>
, IAccountRepository
public EFAccountRepository(EvercateContext context) : base(context)
{
}
And here is the code in Ninject that sets it all up
kernel.Bind<EvercateContext>()
.To<EvercateContext>()
.InRequestScope()
.WithConstructorArgument("connectionStringOrName", "EvercateConnection");
kernel.Bind<IAccountRepository>().To<EFAccountRepository>();
So even tho I use Unit of Work as far as I can see (and it shouldn't) nothing else is called in this request before running SaveChanges.
Is there any way to see what a DbContext will do on SaveChanges, without actually running the method (as it throws DbEntityValidationException)
I can imagine that this weird exception could occur if you are initializing the Account navigation property in the License constructor like so:
public License
{
Account = new Account();
}
The flow when you call...
var entityToDelete = context.Licenses.Find(licenseId);
context.Licenses.Remove(entityToDelete);
...is then probably:
License entity gets loaded (without navigation property Account) and attached to the context (state Unchanged)
The constructor sets the Account navigation property, but it doesn't get attached (state Detached)
When you call Remove for the License entity DetectChanges is called internally by EF. It detects that License.Account is refering to a detached entity and attaches it to the context (in state Added). The state of the License is changed to Deleted.
When you call SaveChanges the change tracker finds two entities: The License in state Deleted and the Account in state Added.
Validation runs and finds that the required property CustomerName for the entity Account that is supposed to be inserted into the database is null (because only the default constructor of Account is called).
The validation exception is thrown.
I'm not sure if the details are right but something like that is probably happening.
In any case you should delete the Account = new Account(); from the License constructor and also check if you initialize other reference navigation properties in entity constructors in your codebase as well. (Initializing empty navigation collections is OK.) This is a common source of notoriously strange problems that are difficult to find and understand.
I tried overriding SaveChanges as recommended.
When I did I found a License about to be deleted (as it should) but I also found an Account about to be created.
I changed the DeleteLicense as displayed below.
public void DeleteLicense(int licenseId)
{
var entityToDelete = context.Licenses.Find(licenseId);
entityToDelete.Account = null;
context.Licenses.Remove(entityToDelete);
}
And right away the code works. The License is removed and the account is still there, but no new account is created.
But why, I do not understand why at all.
Is it something in the relation i set with fluent api?
In my case this happened because my entity had a [Required] property that was of type int? which made it nullable. While inspecting the model that came back from the db I saw the property had a value but the entity that ended up being saved to the database had that value stripped during SaveChanges for some reason. When I switched the value to the expected int type all worked just fine. :shrug:
I had a similar issue and for me, it looked like I hadn't correctly established the relationship between Parent and Child in their respective classes.
My fix was to add the attributes specified below to the Child class, for the property that represented its Parent's Id
public class Child
{
[Key, Column(Order = 1)]
public string Id { get; set; }
[Key, ForeignKey("Parent"), Column(Order = 2)] // adding this line fixed things for me
public string ParentId {get; set;}
}
public class Parent
{
[Key, Column(Order = 1)]
public string Id { get; set; }
...
public virtual ICollection<Child> Children{ get; set; }
}
I have been searching for a way to join 2 distantly related domain models into one view model with no luck.
I am working on an existing application and have been asked to add a field to a result of fax log search.
My controller is returning a viewModel, and I am just wanting to add an additional field to it. Which sounds like it should be an easy task.
Background Info:
This is the original viewModel:
public class VOEIFaxLogSearchListViewModel
{
public DateTime DateTimeAdded { get; set; }
public string Processor { get; set; }
public string FaxStatusCode { get; set; }
public string VendorOrderID { get; set; }
public string FromFaxNumber { get; set; }
}
I want to add an additional field to this viewmodel:
public string CustomerName { get; set; }
The way that the application is designed, a stored procedure is called to return a dataset of the search results. (I won't the search method (GetFaxLogSearchResult) or its SQL call as it isn't necessary)
var resultFaxDS = vOEIDAO.GetFaxLogSearchResult(startDate,endDate,userName,faxType);
The resulting DataSet is then converted to a DataTable.
DataTable faxTable = resultFaxDS.Tables[0];
A For loop interates through each of the result records and puts them into a Domain Model named FaxModel. Which is mapped with Automapper to the viewModel named FaxLogSearchListViewModel.
for (int i=0; i<faxTable.Rows.Count;i++)
{
var row = faxTable.Rows[i];
var faxLogModel = vOEIDAO.DRToFaxModel(row);
faxViewModel.Add(Mapper.Map<FaxModel,FaxLogSearchListViewModel>(faxLogModel));
}
}
return faxViewModel;
}
Here is what I have done so far to add this result field:
1) added the new property to the view model.
2) modified stored procedure that pulls back the search results so it returns CustomerName in the dataset
The dilemna:
The method adding each row of the dataset into the Domain model (DRToFaxModel) is doing just that... it is populating a domain model(FaxModel). The field that I want to add isn't in the Domain model.
As a result, I don't want to add a field to the domain model if it doesn't belong to the concrete class.
Here is the domain model and the method used to populate it with each row from the search results:
public class FaxModel
{
public int FaxID { get; set; }
public int FaxStatusID { get; set; }
public string ToFaxNumber { get; set; }
public string FromFaxNumber { get; set; }
public DateTime DateTimeAdded { get; set; }
public string FaxStatusCode { get; set; }
public string Processor { get; set; }
public string VendorOrderID { get; set; }
}
public FaxModel DRToFaxModel(DataRow dr)
{
FaxModel voObj = new FaxModel();
voObj.FaxID = GetVOInt(dr["FaxID"]);
voObj.FaxStatusID = GetVOSmallInt(dr["FaxStatusID"]);
voObj.ToFaxNumber = GetVOStr(dr["ToFaxNumber"]);
voObj.FromFaxNumber = GetVOStr(dr["FromFaxNumber"]);
voObj.DateTimeAdded = GetVODateTime(dr["DateTimeAdded"]);
voObj.FaxStatusCode = GetVOStr(dr["FaxStatusCode"]);
voObj.Processor = GetVOStr(dr["Processor"]);
voObj.VendorOrderID = GetVOStr(dr["VendorOrderID"]);
//Cant add CustomerName to the model without modifying the FaxModel domain model.
//Shouldn't do that because it is a domain model.
//CustomerName is in the CustomerModel domain Model
// voObj.CustomerName = GetVOStr(dr["CustomerName"]);
return voObj;
}
So currently, my ViewModel with the added CustomerName property is returned with a null for CustomerName.
My domain models are distantly related.
In the database the FAX table can be joined joined to the CUSTOMER table but only by joining through an ORDER table. (the FAX table has an orderID field and the ORDER table has a CustomerID field)
So my resulting question is:
how do you use autoMapper to map a Fax domain model to a Customer domain model since the 2 domains don't have any common fields to build the relationship without joining through another table?
Or can you map more than 2 tables into 1 viewModel using automapper? how is this done?
What a great question. First of all, it's so refreshing to see someone asking about the proper way to do something, rather that just seeking a quick fix. Second, the amount of documentation provided is exactly the way SO questions should be written. I wish I could give this more than +1.
That said, since you're essentially asking an architecture question, there aren't any concrete answers, just opinions.
Here's my opinion:
You state the result of the sproc is mapped to a domain model:
var resultFaxDS = vOEIDAO.GetFaxLogSearchResult(startDate,endDate,userName,faxType);
However, you've added a return field, CustomerName to your sproc which is not part of the domain model. I think that's the heart of your issue.
There's a choice to be made here: does this sproc return a domain model or doesn't it?
Right now, my opinion is that it does not represent a domain model anymore, due to the new field, so you should not be trying to map it to a domain model before mapping it to your view model. You need to create a new data type to map this result to, which represents what you are actually getting from the sproc, and map that to your view model.
The alternate option is that this sproc does in fact represent a domain model. If that is the case, you should not be adding a new field to it that is not part of the model. Rather, you'll need to get the FaxModel domain objects and CustomerModel domain objects separately, and assemble your view models from both objects.
This is an example of the Single Responsibility Principle, meaning that an object, function, assembly, heck, even a program, should have one purpose. By giving your sproc a return value that both is and isn't a domain model, you're giving it more than one purpose. It would be best to either decide that it represents a FaxModel, and accept that the customer name needs to come from another source, or decide that it returns something else, say CustomerFaxModel which contains both customer and fax information, and use it as such.
To answer your technical question, AutoMapper does allow you to pass an existing target object to the map function in addition to a source object. You can map the target from object A to get some fields, and then pass the already mapped target to Map() a second time with a source of object B to map other fields.
Always, always, keep asking questions like this and you'll do well.
I have a client/server application where the server uses Entity Framework as the ORM.
Every entity that is to be sent to the client is represented by a DTO-class.
The mapping between the Entity Framework and DTO-classes is handled using AutoMapper.
Let's say we have the following Tables:
Person (string Name, int CountryID)
Country (int CountryID, int Population, string Name)
They are represented by the following EF classes:
class Person
{
public string Name { get; set; }
public int CountryID { get; set; }
public Country Country { get; set;}
}
class Country
{
public int CountryID { get; set; }
public int Population { get; set; }
public string Name { get; set;}
}
Which in turn are represented by the following DTOs:
class PersonDTO
{
public string Name { get; set; }
public CountryDTO Country { get; set;}
}
class CountryDTO
{
public int CountryID { get; set; }
public int Population { get; set; }
public string Name { get; set;}
}
The initial state of the database represents an empty Person-Table and a Country-table that has one entry: (1, 123, 'CountryXYZ')
The clients' app task is to create a new Person Entity and attach its Country-Reference to the available 'CountryXYZ' Country-Entity.
In order to do so, the client app first requests the available CountryDTOs.
It then creates a new PersonDTO instance and sets its Country Property to the only CountryDTO that it has received from the server.
This PersonDTO-instance is then being sent back to the server.
The server in turn maps the PersonDTO-instance back to a Person-instance.
The last servers step is now to store the Person-instance in the ObjectContext and call ObjectContext.SaveChanges().
The problem I have with this approach is that as soon as I call ObjectContext.SaveChanges(), a new Country-row is created in the database instead of just using the available Country-row.
What am I missing here?
I am new to EF and I think this use case is pretty common... so I hope there is an easy fix to this.
In case the problem description is not clear enough please let me know.
Thanks!
If you know that client will always use country it already received from your server (it is existing one) you can simply modify your saving logic to use:
objectContext.PersonSet.AddObject(personToSave);
objectContext.ObjectStateManager
.ChangeObjectState(personToSave.Country, EntityState.Unchanged);
objectContext.SaveChanges();
If you use AddObject method the entity and all its relations are marked as added and will be inserted to the database as new objects unless you reconfigure their state.
Your entity also exposes FK property so you can use FK property when you map your DTO back to entity instead of creating country instance. In such case you will not need to deal with changing the state of relation because that relation will be represented only through integer column.
If client can create both Person and Country in single call you will need some flag in your DTO to differ between existing or new entity or you will have to query database to verify if such Country already exists.
This is an instance where using the Self Tracking Entities would probably be useful to you.
It uses a T4 template to generate your entity classes, and they can be round-tripped across the wire using WCF. However, you need to share the assembly that contains the entities on the client and the server.
If you are in control of both, and you are using .Net for both, I would go that route.
I have two classes:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
public int Id { get; set; }
public string Email { get; set; }
public virtual ICollection<Company> Companies { get; set; }
}
In my MVC application controller get new Company from post. I want to add current user to created Company in something like this.
User user = GetCurrentLoggedUser();
//company.Users = new ICollection<User>(); // Users is null :/
company.Users.Add(user); // NullReferenceException
companyRepository.InsertOrUpdate(company);
companyRepository.Save();
How it should look like to work properly? I don't know it yet but after adding user to collection I expect problems with saving it to database. Any tips on how it should look like would be appreciated.
Use this approach:
public class Company
{
public int Id { get; set; }
public string Name { get; set;}
private ICollection<User> _users;
public ICollection<User> Users
{
get
{
return _users ?? (_users = new HashSet<User>());
}
set
{
_users = value;
}
}
}
HashSet is better then other collections if you also override Equals and GetHashCode in your entities. It will handle duplicities for you. Also lazy collection initialization is better. I don't remember it exactly, but I think I had some problems in one of my first EF test applications when I initialized the collection in the constructor and also used dynamic proxies for lazy loading and change tracking.
There are two types of entities: detached and attached. An attached entity is already tracked by the context. You usually get the attached entity from linq-to-entities query or by calling Create on DbSet. A detached entity is not tracked by context but once you call Attach or Add on the set to attach this entity all related entities will be attached / added as well. The only problem you have to deal with when working with detached entities is if related entity already exists in database and you only want to create new relation.
The main rule which you must understand is difference between Add and Attach method:
Add will attach all detached entities in graph as Added => all related entities will be inserted as new ones.
Attach will attach all detached entities in graph as Unchanged => you must manually say what has been modified.
You can manually set state of any attached entity by using:
context.Entry<TEntity>(entity).State = EntityState....;
When working with detached many-to-many you usually must use these techniques to build only relations instead of inserting duplicit entities to database.
By my own experience working with detached entity graphs is very hard especially after deleting relations and because of that I always load entity graphs from database and manually merge changes into attached graphs wich are able to fully track all changes for me.
Be aware that you can't mix entities from different contexts. If you want to attach entity from one context to another you must first explicitly detach entity from the first one. I hope you can do it by setting its state to Detached in the first context.
In your constructor for the Company entity you can create an empty collection on the Users property.
public class Company
{
public Company() {
Users = new Collection<User>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
As far as saving to the database is concerned, I asked a related question a few days ago and was assured that Entity Framework is able to track the changes made to related entities. Read up on that here:
Are child entities automatically tracked when added to a parent?