I have a "Category" Entity as follow:
public class Category
{
//<Summary>
//Fields...
//</Summary>
public Guid CategoryId { get; set; }
public string CategoryName { get; set; }
public bool IsDelete { get; set; }
// Fields for relationships
public Guid MainCategoryId { get; set; }
public Category MainCategory { get; set; }
public virtual ICollection<Category> ChildCategories { get; set; }
}
As seen above I want to create 0-one-to-many relationship in same table. I used Fluent API for this as follows:
HasRequired(category => category.MainCategory)
.WithMany(category => category.ChildCategories)
.HasForeignKey(category => category.MainCategoryId);
But it is a one-to-many, isn't 0-1-to-many. I use HasOptional, but it give me an error.
How can I do this with Fluent API?
thanks for reply
Make the MainCategoryId property nullable:
public Guid? MainCategoryId { get; set; }
And then you can use HasOptional method:
HasOptional(category => category.MainCategory)
.WithMany(category => category.ChildCategories)
.HasForeignKey(category => category.MainCategoryId);
Related
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
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);
I have multiple entities that I would like to share a single "Images" table. For example, products can have a list of images and categories can have a list of images. I would like to use the enum "EntityType" to distinguish what type of entity it is. My solution below doesn't work because there is a foreign key error when I try to insert an image with a EntityId that might exist in Category but not in Product. This makes sense because the solution below isn't taking into account the "EntityType". Are there any recommendations for how I can accomplish this? I know I can use "ProductId", "CategoryId", etc instead of "EntityId" but I will have a lot of entities so I would prefer to not to do it that way.
public class Product
{
public int Id { get; set; }
public List<Image> ProductImages { get; set; }
}
public class Category
{
public int Id { get; set; }
public List<Image> CategoryImages { get; set; }
}
public class Image
{
public int EntityId { get; set; }
public EntityType EntityType { get; set; }
public string ImageUrl { get; set; }
public Product Product { get; set; }
public Category Category { get; set; }
}
modelBuilder.Entity<Product>().ToTable("Product");
modelBuilder.Entity<Category>().ToTable("Category");
modelBuilder.Entity<Image>().ToTable("Image");
modelBuilder.Entity<Image>().HasOne(p => p.Product).WithMany(p => p.ProductImages).HasForeignKey(p => p.EntityId);
modelBuilder.Entity<Image>().HasOne(p => p.Category).WithMany(p => p.CategoryImages).HasForeignKey(p => p.EntityId);
What you're describing is a many-to-many relationship. For that, you'll need an entity to track said relationship:
public class ProductImage
{
[ForeignKey(nameof(Product))]
public int ProductId { get; set; }
public Product Product { get; set; }
[ForeignKey(nameof(Image))]
public int ImageId { get; set; }
public Image Image { get; set; }
}
On your Product/Category classes:
public ICollection<ProductImage> ProductImages { get; set; }
Then, for your fluent config:
modelBuilder.Entity<ProductImage>().HasOne(p => p.Product).WithMany(p => p.ProductImages);
modelBuilder.Entity<ProductImage>().HasOne(p => p.Image).WithMany();
Do the same with your categories.
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.
I'm trying to map a fairly "standard" category model using EF Code First
public class Category
{
public int ID { get; set; }
public int ParentID { get; set; }
public string Name { get; set; }
public Category ParentCategory { get; set; }
public List<Category> ChildCategories { get; set; }
}
I've got something along the lines of:
modelBuilder.Entity<Category>()
.HasOptional(t => t.ParentCategory)
.WithMany()
.HasForeignKey(t => t.ParentCategoryID)
.WillCascadeOnDelete();
But this doesn't seem to take care of ChildCategories??
Am I missing something?
To avoid the duplicate question argument, I followed the following, however didn't quite answer my specific query:
Code First Mapping for Entity Framework Hierarchy
Entity Framework CTP5 Code-First Mapping - Foreign Key in same table
Change your Entity to
public class Category
{
public int ID { get; set; }
public int? ParentID { get; set; }
public string Name { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual IList<Category> ChildCategories { get; set; }
}
Make ParentID nullable and to allow ChildCategories to be lazy loaded, make it virtual.