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
Related
Can someone guide me on how to construct the proper property navigation on the below tables?
I have these tables in my database:
I then need to relate the Status table to get the status Name on every table
These are my model classes:
[Table("Companies")]
public class CompanyEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int Status_Id { get; set; }
public DateTime Created_Date { get; set; }
public DateTime Updated_Date { get; set; }
[ForeignKey("Status_Id")]
public Status Status { get; set; }
}
[Table("Customers")]
public class CustomerEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int Status_Id { get; set; }
}
[Table("Status")]
public class Status
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public Company Company { get; set; }
}
My goal is to when I get all the Companies or Customers using DBContext e.g. var companies = _dbContext.Companies.ToList(). I want to get the status of every record for Companies and Customers. I'm not so sure how to construct the navigation property properly for the said models.
TIA!
Update #1
After following the below suggestion, yes the Status is not NULL anymore. But, it gets the wrong status id. Instead of using the Status_Id, it uses the Id of the Company. Please see below snippets. The status of the said company is 6.
But if you notice on the 2nd snip, the status is 3 which is the Id of the Company.
I also have this code in OnModelCreating.
modelBuilder.Entity<Company>()
.HasOne<Status>()
.WithMany()
.HasForeignKey(p => p.Status_Id);
This is the reason why I get that behavior. But if I removed this, the Status property is gets NULL.
Update #2
Just fixed my issue. I need to change the Company property from public Company Company { get;set; } to public List Companies { get;set; }. Then add the suggested answer Include`.
[Table("Status")]
public class Status
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<Company> Companies { get; set; }
}
You have to add the .Include() method to join your status table in your result like so:
_dbContext.Companies.Include(c => c.Status).ToList()
I'm trying to create a customer entity that has multiple contact persons, as well as one primary contact person, but I can't seem to add the migration, as I'm getting the following error:
Unable to determine the relationship represented by navigation property 'ContactPerson.Customer' of type 'Customer'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Customer
public class Customer
{
public Guid CustomerId { get; set; }
public string Name { get; set; }
public DateTime CreateDate { get; set; }
public string City { get; set; }
public string Address { get; set; }
// Contact person data
public virtual ContactPerson PrimaryContactPerson { get; set; }
public virtual ICollection<ContactPerson> ContactPersons { get; set; }
}
ContactPerson
public class ContactPerson
{
public Guid ContactPersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual Customer Customer { get; set; }
}
I tried adding the foreign keys myself, and annotating the foreign key property with my entity, on both the Customer entity as well as the ContactPerson entity, like this:
public class ContactPerson
{
public Guid ContactPersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[ForeignKey("Customer")]
public Guid CustomerId { get; set; }
public virtual Customer Customer { get; set; }
}
But it doesn't seem to make any difference, I'm still getting the same error. How can it be that EF can't determine the relationship?
I suppose it has something to do with the one-to-one relation simultaneously existing with the one-to-many relation, but I can't seem to wrap my head around this issue. Advice and suggestion are highly appreciated!
If I comment out the PrimaryContactPerson property, EF adds the migration just fine, so I'm positive that this has something to do with the two different relations.
I managed to solve my issue using the Entity Framework Fluent API, like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ContactPerson>(e =>
e.HasOne(r => r.Customer).WithMany(c => c.ContactPersons)
);
}
According to the documentation PreserveReferences should be set automatically whenever possible for AutoMapper.
Starting from 6.1.0 PreserveReferences is set automatically at config
time whenever possible.
https://github.com/AutoMapper/AutoMapper/wiki/5.0-Upgrade-Guide
I have also tried setting MaxDepth to 1 but I still get a stack overflow exception with the following mapping. Can I get around this somehow or do I need to modify the view models?
cfg.CreateMap<ArticleViewModel, Article>(MemberList.Source)
.MaxDepth(1)
.EqualityComparison((src, dst) => src.Id == dst.Id);
Code that causes the stack overflow exception:
var article = await iArticleRepository.GetAsync(id);
//The mapping below causes the exception
var mappedArticle = Mapper.Map<ArticleViewModel>(article);
Entities:
public class Article: IEntity<int>
{
[Key]
public int Id { get; set; }
...
public int SupplierId { get; set; }
public virtual Supplier Supplier { get; set; }
}
public class Supplier: IEntity<int>
{
[Key]
public int Id { get; set; }
...
public virtual ICollection<Contact> Contacts { get; set; }
}
public class Contact: IEntity<int>
{
[Key]
public int Id { get; set; }
...
public virtual ICollection<Supplier> Suppliers { get; set; }
}
View models:
public class ArticleViewModel
{
public int Id { get; set; }
...
public SupplierViewModel Supplier { get; set; }
}
public class SupplierViewModel
{
public int Id { get; set; }
...
public List<ContactViewModel> Contacts { get; set; }
}
public class ContactViewModel
{
public int Id { get; set; }
...
public List<SupplierViewModel> Suppliers { get; set; }
}
Well, it's unclear what does whenever possible mean. Since the documentation before that states
It turns out this tracking is very expensive, and you need to opt-in using PreserveReferences for circular maps to work
looks like your scenario fails into not possible category :)
Let not rely on that and use the explicit opt-in. The circular reference in this sample model is between Supplier and Contact, so you have to specify in one of the involved class mappings, for instance:
cfg.CreateMap<ArticleViewModel, Article>(MemberList.Source)
.MaxDepth(1)
.EqualityComparison((src, dst) => src.Id == dst.Id);
cfg.CreateMap<SupplierViewModel, Supplier>(MemberList.Source)
.PreserveReferences()
.EqualityComparison((src, dst) => src.Id == dst.Id);
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.
I am a bit confused as to why I am getting this error:
Introducing FOREIGN KEY constraint 'FK_QuestionTerms_Terms_TermId'
on table 'QuestionTerms' 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.
I have a class Question and a class Term, Questions may have any number of Terms associated with them, and Terms may have any number of Questions associated with them. So I am attempting to create a many to many relationship between the two. First I attempted to use convention, and I am allowing EntityFramework to create the database. This is the Question class
public class Question
{
public Guid Id { get; set; }
public int QuestionNumber { get; set; }
public string StatementHtml { get; set; }
public string AnswerHeaderHtml { get; set; }
public string NotesHtml { get; set; }
public Guid CategoryId { get; set; }
public Guid CourseId { get; set; }
public Guid QuestionTypeId { get; set; }
public Guid? SimulationId { get; set; }
public Guid? SimulationTabId { get; set; }
public ICollection<Term> Terms { get; set; }
public ICollection<ReferenceItem> ReferenceItems { get; set; }
}
And here is the Term Class
public class Term
{
public Guid Id { get; set; }
public string Name { get; set; }
public string StatementHtml { get; set; }
public string Authority { get; set; }
public Guid ProductId { get; set; }
public Product Product { get; set; }
public ICollection<Question> Questions { get; set; }
}
I have also attempted to override OnModelCreating as follows, both process result is the exact same error code.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Question>()
.HasMany(q => q.Terms)
.WithMany(t => t.Questions)
.Map(x =>
{
x.MapLeftKey("QuestionId");
x.MapRightKey("TermId");
x.ToTable("QuestionTerms");
});
}
The problem is that a cacade delete would go back and forth between the tables.
For example first deleting term A which would delete question 1,2 and 3. Question 1 was also used in term B so term B must be deleted .....
It therefore stops you creating such constraints.
There is a good coverage of how to fix it here: Entity Framework 4.1 InverseProperty Attribute and ForeignKey
Edit
This could be a side effect of other problems. You should start with a much simpler model and then gradually build it up.
For example:
Why do you have both ProductId and product
Why CategoryId and not Category
...
Try adding it in your OnModelCreating() method
modelBuilder.Entity<Question>().HasRequired(oo => oo.Term).WithMany(oo => oo.Questions).WillCascadeOnDelete(false);