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.
Related
I have two classes like so.
public class Client { public Guid Id { get; set; } ... }
public class Meeting
{
public Guid Id { get; set; }
public Client[] Invitees { get; set; } = new Client[0];
public Client[] Attendees { get; set; } = new Client[0];
}
The config in the contex is as follows.
private static void OnModelCreating(EntityTypeBuilder<Client> entity) { }
private static void OnModelCreating(EntityTypeBuilder<Meeting> entity)
{
entity.HasMany(a => a.Invitees);
entity.HasMany(a => a.Attendees);
}
I only need the reference to the clients from my meetings. The clients need not to know anything. The meetings need to reference the clients twice or less (volountary presence, optional invitation).
The migration on the above creates two tables, which I'm perfectly fine with. But it creates two indices as well, like this.
migrationBuilder.CreateIndex(
name: "IX_Clients_MeetingId",
table: "Clients",
column: "MeetingId");
migrationBuilder.CreateIndex(
name: "IX_Clients_MeetingId1",
table: "Clients",
column: "MeetingId1");
I'm not fine with that. First of all, I expected only one index to be created, as we're indexing the sme table's primary keys. Secondly, if I can't dogde that, I dislike the digit in IX_Clients_MeetingId1.
What can I do (if anything) to only have a single index created?
How can I specify the name of the index if I'm not using WithMany()?
I'm not providing any links as a proof of effort. Checking MSDN, SO and blogs resulted in a lot of hits on the full M2M relation, i.e. .HasMany(...).WithMany(...) and that's not what I'm heading for. I saw a suggestion to manually make the change in the migration file but tempering with those is begging for issues later. I'm not sure how to google-off the irrelevant results and I'm starting to fear that the "half" M2M I'm attempting is a bad idea (there's no in-between table created, for instance).
Well, it seems that EF is assuming you have 2 one2many relations. So one Client could only be invited to at most one meeting.
As a quick resolution you can either
add 2 join entities explicitly and configure the appropriate
one2many relations. Then you have one table for Invitations and one
for Attendance.
add one many2many join entity that also tracks a
link type (Client, Meeting, LinkType) so that "invited" and
"attended" are link types
Add 2 properties to Client to show EF that
you mean this as a many2many relation:
Like so:
public class Client {
public Guid Id { get; set; }
public ICollection<Meeting> InvitedTo { get; set; }
public ICollection<Meeting> Attended { get; set; }
}
These should not show up in the clients table but as 2 separate tables. (Essentially solution 1 with implicit join entity)
Stepping back, I think you can simply improve the model by introducing an MeetingMember entity. In the current model there's no way a client can be invited to two meetings, nor are clients restricted to attending meetings to which they are invited. So you need a M2M relation, and you can get away with one if you use an explicit linking entity, like
MeetingMember(MeetingId, ClientId, InvitedAt, Attended)
In my project I have a User entity, and a FeeEarner entity which is essentially a collection of additional properties that some types of users have. All FeeEarner instances are linked to a User, but not all Users are linked to a FeeEarner.
I have a virtual property on FeeEarner to navigate to the User, which works. However I have a scenario where I really need to be able to navigate from the user to the FeeEarner (if it exists) from the user. Essentially I want to do something like this:
context.Users.Where(x => x.FeeEarner == null || x.FeeEarner.FirmId == somevalue);
... which I would expect to generate SQL something like this (which works):
select [User].* from [User]
left join FeeEarner on [User].id = FeeEarner.UserId
where FeeEarner.FirmId IS NULL OR FeeEarner.FirmId = 'somevalue'
These entities are very heavily used in an already released product so I cannot modify the database structure without a lot of pain. I know I could write the query the other way around (starting with the FeeEarner) but for various reasons that would not fit very well with the user case I am aiming for here.
When I add a navigation property to the User entity for FeeEarner I get:
Unable to determine the principal end of an association between the
types 'FeeEarner' and 'User'. The principal end of this association
must be explicitly configured using either the relationship fluent API
or data annotations.
I've tried various DataAnnotation and FluentAPI solutions to coerce EF to understand what I'm trying to do, such as:
modelBuilder.Entity<FeeEarner>().HasRequired(t => t.User);
modelBuilder.Entity<User>().HasOptional(t => t.FeeEarner).WithOptionalPrincipal(t => t.User);
But this results in:
FeeEarner_User_Source: : Multiplicity is not valid in Role 'FeeEarner_User_Source' in relationship 'FeeEarner_User'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be '*'.
Here are my entity classes (simplified for clarity):
public class User {
[Key]
public Guid Id { get; set; }
public virtual FeeEarner FeeEarner { get; set; }
}
public class FeeEarner {
[Key]
public Guid Id { get; set; }
public Guid FirmId { get; set; }
public Guid UserId { get; set; }
[ForeignKey(nameof(UserId))]
public virtual User User { get; private set; }
}
I've read as many posts as I can on this topic but none of the solutions I have tried seem to work. I have an existing database and created a new Code First From Existing Database project.
I have a base table called Thing. Every object has a record in this table using Id as the Unique Primary Key. Each other object inherits from this but they use the same Id in the child tables without using a new Identity column in the sub tables. Effectively giving each 'Thing' a unique Id:
public class Thing
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Car
{
public int Id { get; set; }
//other properties
}
public class Person
{
public int Id { get; set; }
//other properties
}
public class Color
{
public int Id { get; set; }
//other properties
}
Every new record first creates an item in 'Thing' and then using that Id value creates a new record in its respective table, creating multiple 1 to 0..1 relationships where the Id field on the derived tables is also the FK to Thing.
Thing 1 to 0..1 Car
Thing 1 to 0..1 Person
Thing 1 to 0..1 Color
and so on
I have tried many different Data Annotation and Fluent API combinations but it always comes back to the same error:
'Unable to retrieve metadata for Model.Car'. Unable to determine the principal end of association between the types 'Model.Thing' and 'Model.Car'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.'
I did manage to get past this error by using virtual with the inverse annotation and setting the Id field to be Key and ForeignKey, but then the message jumps to Person. If you then set it up the same as Car the message reverts back to Car.
It seems I could go back and create a normal Foreign Key to each child table, but that is a lot of work and I am sure it is possible to get this working somehow. Preferably using fluent API.
If you are going to use Data Annotations, you need to declare the PK of the dependent entity as FK too:
public class Thing
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Car Car{get;set;}
}
public class Car
{
[Key,ForeignKey("Thing")]
public int ThingId { get; set; }
//other properties
public virtual Thing Thing{get;set;}
}
And if you are going to use Fluent Api (remove the attributes from your model), the configuration would be like this:
modelBuilder.Entity<Car>().HasRequired(c=>c.Thing).WithOptional(t=>t.Thing);
Based on the multiplicity that is specified, it only makes sense for Thing to be the principal and Car to be the dependent, since a Thing can exist without a Car but a Car must have a Thing.
As you can see you don't need to specify that ThingId is the FK of this relationship.This is because of Entity Frameworkâs requirement that the primary key of the dependent be used as the foreign key. Since there is no choice, Code First will just infer this for you.
Update
Reading again your question I think you are trying to create a hierarchy. In that case you could use the Table per Type (TPT) approach.
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.
This goes for both Entity Framework 4 (4.3.1) and 5.
I have a User class (to go with my Entity Framework MembershipProvider). I've removed some of the properties to simplify. The actual User is from the MVCBootstrap project, so it's not part of the same assembly as the other classes.
public class User {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Required]
[StringLength(256)]
public String Username { get; set; }
}
And then I have this class:
public class NewsItem {
public Int32 Id { get; set; }
[Required]
[StringLength(100)]
public String Headline { get; set; }
[Required]
public virtual User Author { get; set; }
[Required]
public virtual User LastEditor { get; set; }
}
Then I create the database context (The DbSet for the user is in the MembershipDbContext):
public class MyContext : MVCBootstrap.EntityFramework.MembershipDbContext {
public MyContext(String connectString) : base(connectString) { }
public DbSet<NewsItem> NewsItems { get; set; }
}
Running this code will give me this exception when the database is being created:
Introducing FOREIGN KEY constraint 'FK_dbo.WebShop_dbo.User_LastEditor_Id' on table 'WebShop' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
So I change the database context:
public class MyContext : MVCBootstrap.EntityFramework.MembershipDbContext {
public MyContext(String connectString) : base(connectString) { }
public DbSet<NewsItem> NewsItems { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new NewsItemConfiguration());
}
}
And this configuration:
public class NewsItemConfiguration : EntityTypeConfiguration<NewsItem> {
public NewsItemConfiguration() {
HasRequired(n => n.Author).WithOptional();
HasRequired(n => n.LastEditor).WithOptional();
}
}
Or is this wrong?
Anyway, when I run the code, the database get's created, and the database seems okay (looking at foreign key constraints etc.).
But, then I get the 10 latest NewsItems from the context, and start loading them into view models, part of this is accessing the Author property on the NewsItem. The controller doing this takes forever to load, and fails after a long, long time. When running in debug mode, I get an exception in this piece of code: this.AuthorId = newsItem.Author.Id;, then exception I get is this:
A relationship multiplicity constraint violation occurred: An EntityReference can have no more than one related object, but the query returned more than one related object. This is a non-recoverable error.
It's probably something simple and stupid I'm doing wrong, I'm sure I've get similar code running on several sites, so .. what is causing this? Are my models wrong, is it the database context, or?
This part
Introducing FOREIGN KEY constraint 'FK_dbo.WebShop_dbo.User_LastEditor_Id' on table 'WebShop' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint. See previous errors.
is actually a SQL Server issue (and an issue of many other RDBMS's). It is a complex issue resolving multiple cascade paths, and SQL Server decides just to punt and not try. See
Foreign key constraint may cause cycles or multiple cascade paths?
You were trying to configure your model to delete the child Author and LastEditor objects when the NewsItem is deleted. SQL Server won't do that.
Come to think of it... is that what you want? It seems you would want to disassociate the Author and LastEditor from the NewsItem, not delete them from the database.
Your object model requires a 1:1 relationship between NewsItem and Author, and between NewsItem and LastEditor. I'm not sure what this refers to in the code
this.AuthorId = newsItem.Author.Id;
but it seems to me, you should be making the assignment the other way around, e.g.
newsItem.Author = myAuthorInstance;
or if you include foreign key properties in your model and if you have previously saved your author instance and have an Id:
newsItem.AuthorId = myAuthorInstance.Id;
If you share the generated DB schema (relevant parts) that would make it easier to diagnose the issue.
User can be an author of several news items. Also, user can be editor of several news items.
Hence, relationship have to be "one-to-many":
public class NewsItemConfiguration : EntityTypeConfiguration<NewsItem> {
public NewsItemConfiguration() {
HasRequired(n => n.Author).WithMany();
HasRequired(n => n.LastEditor).WithMany();
}
}