I am trying to form a relationship of 2 tables to a 3rd, on a 1 to many basis. I have the following code:
public class CompanyInvolvement
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public virtual Person Person { get; set; }
public virtual Company Company { get; set; }
}
public class Person
{
public Person()
{
CompanyInvolvements = new Collection<CompanyInvolvement>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Required]
public string ClientIdReference { get; set; }
public virtual ICollection<CompanyInvolvement> CompanyInvolvements { get; set; }
}
public class Company
{
public Company()
{
Involvements = new Collection<CompanyInvolvement>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Required]
public string ClientIdReference { get; set; }
[Required]
public string CompanyName { get; set; }
public virtual ICollection<CompanyInvolvement> Involvements { get; set; }
}
So effectively a Person can have many involvements in companies and a Company can have many people involved with it. The model builder is used like so:
modelBuilder.Entity<CompanyInvolvement>().HasRequired(x => x.Person).WithMany(x => x.CompanyInvolvements);
modelBuilder.Entity<CompanyInvolvement>().HasRequired(x => x.Company).WithMany(x => x.Involvements);
I originally created the relationship using the modelbuilder, specifying left and right keys (CompanyId and PersonId) and this worked great. But now I need the Start and End dates for an Involvement, I guess I needed to create a dedicated entity.
The question: When I use the above structure, I can create and read out involvements for a company and also see involvements for a Person. However, when I try to do the following:
var person = _context.People.FirstOrDefault(x => x.Id == personId);
var involvement = company.Involvements.FirstOrDefault(x => x.Person == person );
company.Involvements.Remove(involvement);
_context.SaveChanges();
I get the following error:
A relationship from the 'CompanyInvolvement_Company' AssociationSet is
in the 'Deleted' state. Given multiplicity constraints, a
corresponding 'CompanyInvolvement_Company_Source' must also in the
'Deleted' state.
I think my virtual properties in the 3 entities are correct, but I have the feeling the modelbuilder logic I have may be slightly misconfigured?
I finally figured out what I was doing wrong. I needed to remove the Id property from the CompanyInvolvement entity and add the following composite key:
[Key, Column(Order = 0)]
public Guid PersonId { get; set; }
[Key, Column(Order = 1)]
public Guid CompanyId { get; set; }
I'm guessing by convention, these two properties were then linked as foreign keys to the Person and Company entities respectively. I also removed the modelbuilder mapping as stated in my original question. Once these were done, deleting CompanyInvolvements worked as expected.
Related
I'm working on a project involving a legacy database schema and I'm running into problems when trying to load a related entity with Entity Framework (v6). Here is a simplified example of the data model that is being used:
public partial class Product {
public virtual Delivery Delivery { get; set; }
public virtual string OrderNumber { get; set; }
public virtual int? VersionNumber { get; set; }
public virtual string SocialNumber { get; set; }
}
public partial class Delivery {
public virtual string OrderNumber { get; set; }
public virtual int? VersionNumber { get; set; }
public virtual string SocialNumber { get; set; }
}
What I'm trying to achieve is to include the related Delivery when retrieving a list of Products with a given SocialNumber. A Delivery is related to a Product when the following condition is satisfied: product.OrderNumber == delivery.OrderNumber && product.VersionNumber == delivery.OrderNumber && product.SocialNumber == delivery.SocialNumber.
The easiest way to do this would be calling Include. However, that seems impossible since EF states that product.Delivery is not a valid navigation property. I've tried to get the wanted result by using a projection, but failed. I'd be thankful if anyone could explain the best EF/LINQ approach to get a list of Products (+ their related Delivery) with a given SocialNumber.
Update: I think I should elaborate some more.
The main issue is that I'm dealing with a legacy database that does not have explicit (composite) keys on every table. For example, the Delivery and Product classes are mapped to tables that don't have a primary key. Since EF needs a primary key, dummy keys are defined as follows:
dbModelBuilder.Entity<Product>().hasKey(pk => new { pk.OrderNumber });
The entity classes are generated by a code generator, so adding annotations there isn't the solution I'm looking for. I guess it should be possible to define these things via the DbModelBuilder, but I'm not sure how to go about it.
Update: Figured out a solution.
The following query functions as expected (only showing the LINQ-query that should provide pairs of Product and related Delivery):
from data in _dartz3Context.V_VOV_GBA_DATAs
join delivery in _dartz3Context.VOV_GBA_AANLEVERINGs
on new { Bsn = data.MunicipalBasicAdministrationSocialSecurityNumber, Version = data.VersionNumber, PolicyNumber = data.InsurancePolicyNumber }
equals new { Bsn = delivery.MunicipalBasicAdministrationSocialSecurityNumber, Version = delivery.VersionNumber, PolicyNumber = delivery.InsurancePolicyNumber }
where data.MunicipalBasicAdministrationSocialSecurityNumber == socialSecurityNumber
select new { Data = data, Delivery = delivery }
You need to define a composite key on your entities using annotations or the fluent api:
public partial class Product {
[Key, Column(Order = 0)]
public virtual string OrderNumber { get; set; }
[Key, Column(Order = 1)]
public virtual int? VersionNumber { get; set; }
[Key, Column(Order = 2)]
public virtual string SocialNumber { get; set; }
}
public partial class Delivery {
[Key, Column(Order = 0), ForeignKey("Product")]
public virtual string OrderNumber { get; set; }
[Key, Column(Order = 1), ForeignKey("Product")]
public virtual int? VersionNumber { get; set; }
[Key, Column(Order = 2), ForeignKey("Product")]
public virtual string SocialNumber { get; set; }
}
Example http://www.codeproject.com/Articles/813912/Create-Primary-Key-using-Entity-Framework-Code-Fir
Fluent API Version:
// Composite primary key
modelBuilder.Entity<Product>().HasKey(d => new { d.OrderNumber, d.VersionNumber, d.SocialNumber });
// Composite foreign key
modelBuilder.Entity<Delivery>()
.HasRequired(c => c.Product)
.WithMany(d => d.Delivery)
.HasForeignKey(d => new { d.OrderNumber, d.VersionNumber, d.SocialNumber });
I have a Project model which has a ProjectLead (one instance of the Person Foreign Key), this works fine. But now I also need to add a collection of People (Project members) referencing the same Person table and I can't get the Entity Framework to generate my database. As soon as I try to add the Fluent API code to create the link table ProjectPerson I get an error - "Schema specified is not valid. Errors: The relationship 'MyApp.WebApi.Models.Person_Projects' was not loaded because the type 'MyApp.WebApi.Models.Person' is not available." I assume this is because of the existing FK relationship already in place with ProjectLead.
Project Model:
public class Project
{
[Key]
public int ProjectId { get; set; }
public string Name { get; set; }
// Foreign Key - Project lead (Person)
public int ProjectLeadId { get; set; }
public virtual Person ProjectLead { get; set; }
// Create many to many relationship with People - Team members on this project
public ICollection<Person> People { get; set; }
public Project()
{
People = new HashSet<Person>();
}
}
Person Model:
public class Person
{
[Key]
public int PersonId { get; set; }
public String Firstname { get; set; }
public String Surname { get; set; }
// Create many to many relationship
public ICollection<Project> Projects { get; set; }
public Person()
{
Projects = new HashSet<Project>();
}
}
DB Context:
public class HerculesWebApiContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Project> Projects { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// This works fine
modelBuilder.Entity<Project>()
.HasRequired(c => c.ProjectLead)
.WithMany(d => d.Projects)
.HasForeignKey(c => c.ProjectLeadId)
.WillCascadeOnDelete(false);
// Adding these lines to create the link table `PersonProjects` causes an error
//modelBuilder.Entity<Person>().HasMany(t => t.Projects).WithMany(t => t.People);
//modelBuilder.Entity<Project>().HasMany(t => t.People).WithMany(t => t.Projects);
}
}
I gather that perhaps I need to use the InverseProperty attribute, but I am not sure where this should go in this case?
Can you explicitly define your join table? So, define a ProjectPeople relationship and make the code something like this...
public class ProjectPerson{
[Key]
public int ProjectPersonId { get; set; }
[ForeignKey("Project")]
public int? ProjectId {get;set;}
public virtual Project {get;set;}
[ForeignKey("Person")]
public int? PersonId {get;set;}
public virtual Person {get;set;}
public string RelationshipType {get;set;}
}
Then your other 2 classes will look like this...
public class Project
{
[Key]
public int ProjectId { get; set; }
public string Name { get; set; }
// Foreign Key - Project lead (Person)
public int ProjectLeadId { get; set; }
public virtual Person ProjectLead { get; set; }
// Create many to many relationship with People - Team members on this project
public virtual ICollection<ProjectPerson> ProjectPeople { get; set; }
public Project()
{
ProjectPerson = new HashSet<ProjectPerson>();
}
}
And this..
Public class Person
{
[Key]
public int PersonId { get; set; }
public String Firstname { get; set; }
public String Surname { get; set; }
// Create many to many relationship
public virtual ICollection<ProjectPerson> ProjectPeople { get; set; }
public Person()
{
ProjectPerson = new HashSet<ProjectPerson>();
}
}
I have a code-first MVC4 C# application with these two models
public class ClassRoom {
public int ID { get; set; }
public string ClassName { get; set; }
public virtual ICollection<Pupil> Pupils { get; set; }
}
public class Pupil {
public int ID { get; set; }
public string PupilName { get; set; }
public virtual ICollection<ClassRoom> ClassRooms { get; set; }
}
This is a part of the context
modelBuilder.Entity<Location>()
.HasMany(l => l.ClassRooms)
.WithMany(o => o.Pupils)
.Map(m => m.MapLeftKey("PupilID")
.MapRightKey("ClassRoomID")
.ToTable("PupilClassRoomMM"));
Therefore there is a many-to-many table called "PupilClassRoomMM".
This all worked fine, but now I want to add a column to the PupilClassRoomMM-table.
I tried to make this Model, but the Add-Migration doesn't get it.
public class PupilClassRoomMM
{
[Key, Column(Order = 0)]
public int ClassRoomID { get; set; }
[Key, Column(Order = 1)]
public int PupilID { get; set; }
public bool IsPrimary { get; set; }
}
The error is:
"PupilClassRoomMM: Name: The EntitySet 'PupilClassRoomMM' with schema 'dbo' and
table 'PupilClassRoomMM' was already defined.
Each EntitySet must refer to a unique schema and table."
How can I add a field to an already existing many-to-many table (I don't want to lose its data).
PLEASE CHECK POSSIBLE SOLUTION SECTION BELOW
I have an issue with a foreign key relationship - here are my tables:
public class Lead
{
[Key]
public int LeadId { get; set; }
public int? CustomerId { get; set; }
[ForeignKey("CustomerId")]
public virtual Customer Customer { get; set; }
}
public class Customer
{
[Key]
[Column("customer_id")]
public int CustomerId { get; set; }
[ForeignKey("CustomerId")]
public virtual Lead Lead { get; set; }
}
I'm having an issue where I receive this error:
Unable to determine the principal end of an association between the types 'Sales.Customers.Customer' and 'Sales.Leads.Lead'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
I've tried adding the relationship to the modelbuilder but it appears that its not working properly. When I do get the error message to go away its actually using the Lead.LeadId -> Customer.CustomerId as the relationship instead of Lead.CustomerId -> Customer.CustomerId relationship.
I've checked similar questions on Stackoverflow but they don't seem to match my DB structure and when I try to implement their suggestions the relationship still doesn't work properly.
Its really weird - would greatly appreciate help on this!
UPDATE
So in my attempt to get this relationship to work I've switched the keys around in the following way:
public class Lead
{
[Key]
public int LeadId { get; set; }
[ForeignKey("LeadId")]
public virtual Customer Customer { get; set; }
}
public class Customer
{
[Key]
[Column("customer_id")]
public int CustomerId { get; set; }
public int? LeadId { get; set; }
[ForeignKey("LeadId")]
public virtual Lead Lead { get; set; }
}
However, same error, still no luck - I'm really at a loss why this relationship won't work. To me it seems pretty straight forward.
UPDATE 2
Ok - after a TON of wasted time messing with this I've tried a slightly different approach:
Here are my new classes....
public class Lead
{
[Key, ForeignKey("Customer")]
public int LeadId { get; set; }
public virtual Customer Customer { get; set; }
}
public class Customer
{
[Key]
[Column("customer_id")]
public int CustomerId { get; set; }
public int? LeadId { get; set; }
public virtual Lead Lead { get; set; }
}
No more error messages with the code above! The only problem is that the relationship entity framework is creating is between the Customer.CustomerId and Lead.LeadId instead of the Customer.LeadId and Lead.LeadId - I feel like i'm SO CLOSE!
POSSIBLE SOLUTION
Ok - so after some more research I came across this post here:
EF Code First - 1-to-1 Optional Relationship
I modified my classes to this:
public class Customer
{
[Key]
[Column("customer_id")]
public int CustomerId { get; set; }
public virtual Lead Lead { get; set; }
}
public class Lead
{
[Key]
public int LeadId { get; set; }
public virtual Customer Customer { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().HasOptional<Lead>(l => l.Lead).WithOptionalDependent(c => c.Customer).Map(p => p.MapKey("LeadId"));
base.OnModelCreating(modelBuilder);
}
Everything works GREAT! But one BIG problem...
I had to remove the LeadId property from the Customer Table.... so now I'm not sure how I can assign a LeadId when creating a new Customer (when appropriate) if there is no LeadId property to assign to?
Posting this in fluent API, it should work.
public class Lead
{
[Key]
public int LeadId { get; set; }
public virtual Customer Customer { get; set; }
}
public class Customer
{
[Key]
[Column("customer_id")]
public int CustomerId { get; set; }
public virtual Lead Lead { get; set; }
}
builder.Entity<Lead>()
.HasOptional(l => l.Customer)
.WithOptionalPrincipal()
.Map(k => k.MapKey("LeadId"));
builder.Entity<Customer>()
.HasOptional(c => c.Lead)
.WithOptionalPrincipal()
.Map(k => k.MapKey("CustomerId"));
EDIT
I am implementing feature that allows users to follow each other.
I have database tables:
User{UserId, FirstName, LastName etc.}
Followings{FollowerUserId, FollowingUserId, CreatedOnDate etc.}
So I added EF class:
public class Follow
{
[Key, Column(Order = 1)]
public Guid FollowerUserId { get; set; }
[Key, Column(Order = 2)]
public Guid FollowUserId { get; set; }
public DateTime CreatedOnDate { get; set; }
public virtual User Follower { get; set; }
public virtual User Following { get; set; }
}
The last two virtual properties couse issue.
When I call:
var model = con.Follows.Where(x => x.FollowerUserId == uid);
I get following exception:
Invalid column name 'Following_UserId'.
The issue is probably caused because of two User objects in one class. Any idea how to workaround this?
UPDATE
public class User
{
public Guid UserId { get; set; }
...
public virtual ICollection<Follow> Following { get; set; }
public virtual ICollection<Follow> Followers { get; set; }
}
I think the reason is that the foreign key properties (FollowerUserId and FollowUserId) and navigation properties (Follower and Following) do not respect the naming conventions so that EF is unable to recognize the first properties as foreign keys. You can fix the problem by specifying the FK properties explicitly using the [ForeignKey] attribute:
public class Follow
{
[Key, Column(Order = 1), ForeignKey("Follower")]
public Guid FollowerUserId { get; set; }
[Key, Column(Order = 2), ForeignKey("Following")]
public Guid FollowUserId { get; set; }
public DateTime CreatedOnDate { get; set; }
public virtual User Follower { get; set; }
public virtual User Following { get; set; }
}
Edit
A least the second property doesn't respect the naming convention, the first one looks OK. So, alternatively you can fix the problem by renaming the second FK property FollowUserId into:
public Guid FollowingUserId { get; set; }
...because the navigation property is called Following.
Edit 2
About your UPDATE: You need to add the [InverseProperty] attribute to tell EF which navigation properties belong together:
public class Follow
{
[Key, Column(Order = 1), ForeignKey("Follower")]
public Guid FollowerUserId { get; set; }
[Key, Column(Order = 2), ForeignKey("Following")]
public Guid FollowUserId { get; set; }
public DateTime CreatedOnDate { get; set; }
[InverseProperty("Followers")] // refers to Followers in class User
public virtual User Follower { get; set; }
[InverseProperty("Following")] // refers to Following in class User
public virtual User Following { get; set; }
}