Unable to determine the relationship represented by navigation - c#

I have two tables - Products and ProductRelations. They are like this:
public class Product
{
public int Id { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public IList<ProductRelation> ProductRelations { get; set; }
}
public class ProductRelation
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int ProductId { get; set; }
public int RelatedProductId { get; set; }
//[ForeignKey("ProductId")]
public Product Product { get; set; }
//[ForeignKey("RelatedProductId")]
public Product RelatedProduct { get; set; }
}
I get the error InvalidOperationException: Unable to determine the relationship represented by navigation 'Product.ProductRelations' of type 'IList<ProductRelation>'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I've never used OnModelCreating. Following the naming conventions are usually enough.
If I remove the public Product RelatedProduct { get; set; } from the ProductRelation class, the error goes away.
What am I missing?

You need multiple collections in Product. Try something like this:
public class Product
{
public int Id { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
[InverseProperty(nameof(ProductRelation.Product)]
public IList<ProductRelation> ProductRelationsLeft { get; set; }
[InverseProperty(nameof(ProductRelation.RelatedProduct)]
public IList<ProductRelation> ProductRelationsRight { get; set; }
}
Also you can try next setup via fluent api, providing default relation collection parameter (collection):
collection - The name of the collection navigation property on the other end of this relationship. If null or not specified, there is no navigation property on the other end of the relationship.
modelBuilder.Entity<ProductRelation>()
.HasOne(pt => pt.Product)
.WithMany() // leave empty
.HasForeignKey(pt => pt.ProductId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<ProductRelation>()
.HasOne(pt => pt.RelatedProduct)
.WithMany(t => t.ProductRelations)
.HasForeignKey(pt => pt.RelatedProductId);

Related

EF Include Child Collection returning Nulls in list

I am having an issue with EF returning NULL values within a child list. Here is my model that I am trying to get:
public class CompoundIngredient : Ingredient
{
public List<MeasuredIngredient> MeasuredIngredients { get; set; }
public string UserId { get; set; }
public CompoundIngredient()
{
MeasuredIngredients = new List<MeasuredIngredient>();
IsPublic = true;
}
}
However, when I do this:
return await _dataContext.CompoundIngredients
.Include(a => a.MeasuredIngredients)
.ThenInclude(a => a.MeasurementType)
.Include(a => a.MeasuredIngredients)
.ThenInclude(a => a.Ingredient)
.ThenInclude(a => a.IngredientType)
.FirstOrDefaultAsync(c => c.DisplayValue == name);
I get back a collection of 4 items. 2 items are populated and 2 are NULL.
Here is the data in the DB
As you can see from the picture there are 4 entries in the table, 2 of which belong to CompoundIngredientId 6 which is the ID of the ingredient who matches the Name value.
Why am I getting 4 results back, 2 of which are null?
EDIT:
So here are the models
public class CompoundIngredient : Ingredient
{
public List<MeasuredIngredient> MeasuredIngredients { get; set; }
public string UserId { get; set; }
public CompoundIngredient()
{
MeasuredIngredients = new List<MeasuredIngredient>();
IsPublic = true;
}
}
public class Ingredient
{
public int Id { get; set; }
public string DisplayValue { get; set; }
public string Description { get; set; }
public bool IsPublic { get; set; }
public IngredientType IngredientType { get; set; }
public int IngredientTypeId { get; set; }
public int CompanyId { get; set; }
public string UserName { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public string LastModifiedBy { get; set; }
public DateTime LastModifiedDate { get; set; }
}
public class MeasuredIngredient
{
public int Id { get; set; }
public decimal Amount { get; set; }
public int MeasurementTypeId { get; set; }
public MeasurementType MeasurementType { get; set; }
public int IngredientId { get; set; }
public Ingredient Ingredient { get; set; }
public int? UseId { get; set; }
public Use Use { get; set; }
}
public class Recipe
{
public int Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsPublic { get; set; }
public int RecipeCategoryId { get; set; }
public RecipeCategory RecipeCategory { get; set; }
public int SocialMediaId { get; set; }
public SocialMedia SocialMedia { get; set; }
public virtual List<TimeTemp> TimeTemps { get; set; }
public virtual List<RecipeFuel> RecipeFuels{ get;set; }
public List<MeasuredIngredient> MeasuredIngredients { get; set; }
public List<RecipeStep> RecipeSteps { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public string LastModifiedBy { get; set; }
public DateTime LastModifiedDate { get; set; }
public Recipe()
{
MeasuredIngredients = new List<MeasuredIngredient>();
RecipeSteps = new List<RecipeStep>();
SocialMedia = new SocialMedia();
RecipeFuels = new List<RecipeFuel>();
TimeTemps = new List<TimeTemp>();
IsPublic = true;
}
}
As you can see Measured Ingredient isnt exclusive to CompoundIngredient. Recipe also has a List on it as well.
as far as configurations i dont have much
public class MeasuredIngredientConfiguration : IEntityTypeConfiguration<MeasuredIngredient>
{
public void Configure(EntityTypeBuilder<MeasuredIngredient> builder)
{
builder.Property(p => p.UseId).IsRequired(false);
}
}
public class IngredientConfiguration : IEntityTypeConfiguration<Ingredient>
{
public void Configure(EntityTypeBuilder<Ingredient> builder)
{
builder.Property(p => p.IsPublic).HasDefaultValue(true);
}
}
public class RecipeConfiguration : IEntityTypeConfiguration<Recipe>
{
public void Configure(EntityTypeBuilder<Recipe> builder)
{
builder.Property(p => p.IsPublic).HasDefaultValue(true);
}
}
here are the tables in the DB with FKs
I highly suspect the issue will stem from CompoundIngredient inheriting from Ingredient using TPH inheritance (One table with a Discriminator) then being referenced by MeasuredIngredient, and this relationship not being set up quite right. Which version of EF Core is this?
Overall the relationship between these entities/tables feels "odd". You have an ingredient, then a "compound" ingredient that is made up of one or more MeasuredIngredient. (which does not extend ingredient) A Measured ingredient contains one Ingredient, and optionally one CompoundIngredient.
Given a compound ingredient represents just a collection of measured ingredients, this just feels a bit off. It sounds like you want a recipe to contain a list of ingredients where each is associated with a measurement (The MeasuredIngredient) but that "ingredient" may be a combination of other ingredients (with associated measurements) where you may want to possibly avoid duplicating data.
I built a simple test with the core relationships in EF Core 5 and I was able to get the expected results. The important detail here was ensuring the relationship between the 3 classes (and possibly other related classes) is configured correctly. For example, cutting down the object model down to the core I came up with:
public class Ingredient
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CompoundIngredient : Ingredient
{
public virtual ICollection<MeasuredIngredient> MeasuredIngredients { get; set; } = new List<MeasuredIngredient>();
}
public class MeasuredIngredient
{
public int Id { get; set; }
publi int IngredientId { get; set; }
public virtual Ingredient Ingredient { get; set; }
}
Creating test records with the relationships you showed and running EF Core 5 I wasn't able to reproduce the issue, but I honestly did not feel comfortable with leaving EF to sort out the discriminator and relationships.
The bits I didn't like were:
CompoundIngredient extends Ingredient while containing a Many relationship to MeasuredIngredient where there is no corresponding "One" relationship on MeasuredIngredient, but it does have a "One" relationship with Ingredient.
The discriminator here is implied, not configured.
What I am more comfortable with was:
public class Ingredient
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CompoundIngredient : Ingredient
{
public virtual ICollection<MeasuredIngredient> MeasuredIngredients { get; set; } = new List<MeasuredIngredient>();
}
public class MeasuredIngredient
{
public int Id { get; set; }
public virtual Ingredient Ingredient { get; set; }
public virtual CompoundIngredient CompoundIngredient { get; set; }
}
Then explicitly mapping the relationships to ensure there is no confusion on FKs:
public class MeasuredIngredientConfiguration : IEntityTypeConfiguration<MeasuredIngredient>
{
public void Configure(EntityTypeBuilder<MeasuredIngredient> builder)
{
builder.Property(p => p.UseId).IsRequired(false);
builder.HasOne(p => p.Ingredient)
.WithMany()
.IsRequired()
.HasForeignKey("IngredientId");
builder.HasOne(p => p.CompoundIngredient)
.WithMany(p => p.MeasuredIngredients)
.IsRequired(false)
.HasForeignKey("CompoundIngredientId");
}
}
public class IngredientConfiguration : IEntityTypeConfiguration<Ingredient>
{
public void Configure(EntityTypeBuilder<Ingredient> builder)
{
builder.Property(p => p.IsPublic).HasDefaultValue(true);
builder.HasDiscriminator<string>("Discriminator")
.HasValue<Ingredient>("I")
.HasValue<CompoundIngredient>("C"); // Whichever discriminator values you want to use.
}
}
I generally do not have FKs exposed in entities for navigation properties, opting for shadow properties. This should work just as well with the FK fields mapped.
Now I had excluded this configuration and this example did work with EF Core 5. I was also trying to force a misconfiguration around possibly the CompoundIngredientId and IngredientId in the measured ingredient, but outside of generating specific configuration errors around missing assumed FKs I wasn't able to reproduce your issue. It could also be behaviour specific to the version of EF Core you are using.
You could try adding the explicit mapping to see if that solves or otherwise changes your results. Getting null entries in your collection smells like EF is trying to parse the CompoundIngredient -> MeasuredIngredient, but it is getting other measured Ingredients with the same Ingredient reference (1-2) but not the matching compound ingredient ID. It's definitely a weird one.
Otherwise I would look to temporarily eliminate all other references such as Recipe, measurement type, etc. down to the simplest possible example and data set that reproduces the problem. This becomes easier to investigate options to identify where/what is getting mixed up.
Hopefully this gives you some ideas on how to get to the bottom of the issue.
Turns out the issue is not with EF… after looking further into it EF is returning the proper counts and relations. The issue is higher up during the deserialization of the json being returned from the API. I created a new question for this
JsonSerializer.DeserializeAsync<> Creating Null Items in Collections

C# EF Core: How to map One-to-Zero with multiple properties (with Attributes only) [With Workaround]?

Community,
I tried my first Entity Framework Core project. Now I am unable to map the properties in my models, these are NOT One-to-One relations, I would call it "One-to-Zero":
public class ContactDetails
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[ForeignKey("CreatedById")]
public User CreatedBy { get; set; }
[ForeignKey("UpdatedById")]
public User UpdatedBy { get; set; }
[ForeignKey("DeletedById")]
public User DeletedBy { get; set; }
// foreign keys
public int? CreatedById { get; set; }
public int? UpdatedById { get; set; }
public int? DeletedById { get; set; }
}
public class User
{
public int Id { get; set; }
public string MailAdress { get; set; }
public string Password { get; set; }
[ForeignKey("ContactDetailsByUserId")]
public ContactDetails ContactDetailsByUser { get; set; }
[ForeignKey("ContactDetailsByAdminId")]
public ContactDetails ContactDetailsByAdmin { get; set; }
// foreign keys
public int? ContactDetailsByUserId { get; set; }
public int? ContactDetailsByAdminId { get; set; }
}
So ContactDetails can have three different Users.
Usercan have two different ContactDetails.
If I try to create a migration for this, I get this error:
Unable to determine the relationship represented by navigation property 'ContactDetails.CreatedBy' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I really want to do this with Attributes only, if this is possible, anyway with the Fluent-API this works:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<ContactDetails>().HasOne(c => c.CreatedBy).WithOne().HasForeignKey<ContactDetails>(c => c.CreatedById).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<ContactDetails>().HasIndex(c => c.CreatedById).IsUnique(false);
builder.Entity<ContactDetails>().HasOne(c => c.UpdatedBy).WithOne().HasForeignKey<ContactDetails>(c => c.UpdatedById).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<ContactDetails>().HasIndex(c => c.UpdatedById).IsUnique(false);
builder.Entity<ContactDetails>().HasOne(c => c.DeletedBy).WithOne().HasForeignKey<ContactDetails>(c => c.DeletedById).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<ContactDetails>().HasIndex(c => c.DeletedById).IsUnique(false);
builder.Entity<User>().HasOne(u => u.ContactDetailsByUser).WithOne().HasForeignKey<User>(u => u.ContactDetailsByUserId).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<User>().HasIndex(u => u.ContactDetailsByUserId).IsUnique(false);
builder.Entity<User>().HasOne(u => u.ContactDetailsByAdmin).WithOne().HasForeignKey<User>(u => u.ContactDetailsByAdminId).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<User>().HasIndex(u => u.ContactDetailsByAdminId).IsUnique(false);
}
Maybe the Fluent thing will help someone, but is it possible to do this with Attributes only?
Thanks for your time.
Best Regards
Stewie

Load related data without foreign key constraint in ef-core-2.1

I want to load related entities data Parent by using Eager Loading O/RM pattern. But I can't specify a foregin key constraint on ParentId because it creates a cycle which is not allowed. Currently, I'm using an inner join to load Parent data explicitly.
Here is my Domain Model that I'm using.
[Table("Category")]
public class CategoryDM
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
[Display(Name="Parent")]
public int ParentId { get; set; }
[NotMapped]
public CategoryDM Parent { get; set; }
}
Is there any way to load related entities like this? or any other recommended way to achieve this.
var result = _context.Category.Include(e => e.Parent);
This should work fine, here is an exemplary working model.
Model
public class Category : ISelfRelated<Category>
{
public int Id { get; set; }
public string Name { get; set; }
public string ThumbnailUrl { get; set; }
public int? ParentId { get; set; }
public Category Parent { get; set; }
public IEnumerable<Category> Children { get; set; }
}
Model configuration
category.HasOne(c => c.Parent)
.WithMany(c => c.Children)
.HasForeignKey(c => c.ParentId)
.HasPrincipalKey(c => c.Id)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);

Stop AutoMapper initializing data model properties during mapping

I have a ClientDocument data model and mapping as follows:
public class ClientDocument : BaseEntity
{
public int DocumentOwnerId { get; set; }
public int ClientProfileId { get; set; }
public virtual ClientProfile ClientProfile { get; set; }
public int DocumentId { get; set; }
public virtual Document Document { get; set; }
}
public ClientDocumentMap(EntityTypeBuilder<ClientDocument> entityBuilder)
{
entityBuilder.HasKey(t => t.Id);
// One to Many with client profile
entityBuilder.HasOne(c => c.ClientProfile).WithMany(p => p.ClientDocuments).HasForeignKey(x => x.ClientProfileId).IsRequired();
// One to Many with document
entityBuilder.HasOne(c => c.Document).WithMany(p => p.ClientDocuments).HasForeignKey(x => x.DocumentId).IsRequired();
}
and a ClientDocumentViewModel as follows:
public class ClientDocumentViewModel
{
public int Id { get; set; }
public int CreatedBy { get; set; }
public DateTime AddedDate { get; set; }
[HiddenInput]
public int ClientProfileId { get; set; }
public string ClientProfileName { get; set; }
public int SecondaryClientProfileId { get; set; }
[HiddenInput]
public string SecondaryClientProfileName { get; set; }
public int DocumentOwnerId { get; set; }
public int DocumentId { get; set; }
public DocumentViewModel Document { get; set; }
}
When i pass the ClientDocumentViewModel into the mapper and map the view model to the data model using:
var entity = _mapper.Map<ClientDocumentViewModel, ClientDocument>(model);
The properties from the ViewModel are all correctly getting mapped to the data model, however the mapper is also initializing an instance of ClientProfile which is stopping the insert using entity framework.
I have other data models and view models which use the same mapping pattern. When I debug them, the ClientProfile property isn't being initialized and the data entity is inserted successfully. I've gone through and compared the data models, entity framework maps, the foreign keys on the db, and the mapping profiles, and they all seem the same.
Does anyone have any ideas why this is occurring?
you can specify ClientProfile to ignore property in your mapping profile like this :
CreateMap<ClientDocumentViewModel, ClientDocument>()
.ForMember(x => x.ClientProfile, src => src.Ignore());

The navigation on entity type has not been added to the model, or ignored, or entityType ignored

The navigation 'Tags' on entity type 'Notepad.Models.Note' has not been added to the model, or ignored, or entityType ignored.
public class Note
{
public Note()
{
CreationDate = DateTime.Now;
Tags = new HashSet<Tag>();
Parts = new HashSet<Part>();
}
public int ID { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Part> Parts { get; set; }
public DateTime? CreationDate { get; set; }
}
public class Tag
{
public Tag()
{
Notes = new HashSet<Note>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Note> Notes { get; set; }
}
It happens while adding a migration:
dnx ef migrations add DbData -c DataDbContext
Why do you think it happens?
EDIT:
DataDbContext:
public class DataDbContext : DbContext
{
public DbSet<Note> Notes { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Part> Parts { get; set; }
}
You have Many-to-many relationship there. As the documentation says: http://docs.efproject.net/en/latest/modeling/relationships.html#id21
Many-to-many relationships without an entity class to represent the join table are not yet supported. However, you can represent a many-to-many relationship by including an entity class for the join table and mapping two separate one-to-many relationships.
So you must create additional "join" class like this:
public class NoteTag
{
public int NoteId { get; set; }
public Note Note { get; set; }
public int TagId { get; set; }
public Tag Tag { get; set; }
}
then, replace
ICollection<Tag> Tags {set;get}
in your Note class to
ICollection<NoteTag> NoteTags {set;get}
and also in Tag class:
ICollection<Note> Notes {set;get;}
to
ICollection<NoteTags> NoteTags {set;get}
and then override OnModelCreating method in DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<NoteTag>()
.HasKey(t => new { t.NoteId, t.TagId });
modelBuilder.Entity<NoteTag>()
.HasOne(pt => pt.Note)
.WithMany(p => p.NoteTags)
.HasForeignKey(pt => pt.NoteId);
modelBuilder.Entity<NoteTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.NoteTags)
.HasForeignKey(pt => pt.TagId);
}
I am using EF 7, this problem took around 2 hours of my week end. :)
So, here is the simple solution -
I am having a profile class like this -
[Table("Profile")]
public class Profile
{
public Profile()
{
}
[Column(Order = 1)]
[Key]
public Guid ProfileID { get; set; }
[JsonIgnore]
public virtual ICollection<StudentLivingWith> StudentProfileMap { get; set; }
[JsonIgnore]
public virtual ICollection<StudentLivingWith> ParentProfileMap { get; set; }
}
I am using the ProfileID as a F-Key reference in my another table named "StudentLivingWith". (ya, I know the name is bit strange. :)) As you can see in below class, both the columns "StudentProfileID" and "ParentProfileID" refering to the same column "profileID" of my "Profile" table.
[Table("StudentLivingWith")]
public class StudentLivingWith
{
public StudentLivingWith()
{
}
[Column(Order = 1)]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int StudentLivingWithID { get; set; }
[Column(Order = 2)]
[ForeignKey("StudentProfileID")]
public Guid StudentProfileID { get; set; }
[Column(Order = 3)]
[ForeignKey("ParentProfileID")]
public Guid ParentProfileID { get; set; }
[JsonIgnore]
[InverseProperty("StudentProfileMap")]
public virtual ICollection<Profile> StudentProfile { get; set; }
[JsonIgnore]
[InverseProperty("ParentProfileMap")]
public virtual ICollection<Profile> ParentProfile { get; set; }
}
So the conclusion is - you just need to add [InverseProperty] tag on the reference, and this simple solution did the trick for me.
I hope this will help. Thanks.

Categories

Resources