I have the following model:
public class Order
{
public int Id { get; set; }
public ItemType ItemType { get; set; }
public int ItemId { get; set; }
}
public enum ItemType
{
Customer, Organization, Department, //etc
}
The ItemId is a foreignkey to a separate model/table. The above model maps as-is with the underlying table.
Now I would like to automatically load the related entities when loading an Order.
Is this possible by using composite foreign keys when mapping, as in:
public class Order
{
public int Id { get; set; }
public ItemType ItemType { get; set; }
public int ItemId { get; set; }
public ItemTypeCustomer => ItemType.Customer;
public ItemTypeOrganization => ItemType.Organization;
public ItemTypeDeparment => ItemType.Department;
public Customer Customer { get; set; }
public Organization Organization { get; set; }
public Department Department { get; set; }
}
modelBuilder.Entity<Order>()
.HasOptional(p => p.Customer)
.WithMany()
.HasForeignKey(p => new { p.ItemTypeCustomer , p.ItemId });
Would the above work? Is it even possible to build something like this using code-first?
`using Microsoft.EntityFrameworkCore;
public IEnumerable GetCandidates() { return context.Orders.Include(C=> C.ItemType); }'
Related
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);
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.
I'm trying to add some entities using EntityFramework. I need the same model as in image
I created 3 classes:
public class UsersOrders : Entity
{
public int Order_ID { get; set; }
public int User_ID { get; set; }
public virtual User User { get; set; }
public virtual Order Order { get; set; }
}
public class User : Entity
{
public int User_ID { get; set; }
public string Surname { get; set; }
public string Name { get; set; }
public string Patronymic { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public virtual ICollection<UsersOrders> Orders { get; set; }
}
public class Order : Entity
{
public int Order_ID { get; set; }
public virtual Address Address { get; set; }
public virtual User User_ID_Courier { get; set; }
public virtual ICollection<UsersOrders> Users { get; set; }
}
Using FluentAPI trying to set primary keys in my DBContext:
builder.Entity<UsersOrders>()
.HasKey(od => new {od.Order_ID});
It works, but why in DB this field "Order_Order_ID" appears? I'm not sure but I really didn't set this field.
Thanks for any help!
UPD: OnModelCreating
protected override void OnModelCreating(DbModelBuilder builder)
{
//OrderedDishes
builder.Entity<OrderedDishes>().HasKey(od => new { od.Order_ID, od.Dish_ID, od.Number });
builder.Entity<OrderedDishes>().HasRequired(od => od.Order).WithMany(od => od.Dishes).HasForeignKey(od => od.Order_ID);
builder.Entity<OrderedDishes>().HasRequired(od => od.Dish).WithMany(od => od.Orders).HasForeignKey(od => od.Dish_ID);
//OrderStatus
builder.Entity<OrderStatus>().HasKey(os => new { os.Order_ID, os.StatusType_ID });
builder.Entity<OrderStatus>().HasRequired(os => os.Order);
builder.Entity<OrderStatus>().HasRequired(os => os.StatusType);
//DishStatus
builder.Entity<DishStatus>().HasKey(os => new { os.Order_ID, os.Dish_ID, os.Number, os.StatusType_ID });
builder.Entity<DishStatus>().HasRequired(os => os.OrderedDishes);
builder.Entity<DishStatus>().HasRequired(os => os.StatusType);
//user
builder.Entity<UsersOrders>().HasKey(od => new { od.Order_ID });
builder.Entity<UsersOrders>().HasRequired(os => os.User);
builder.Entity<UsersOrders>().HasRequired(os => os.Order);
//PriceOfDish
builder.Entity<PriceOfDish>().HasKey(t => new { t.Dish_ID, t.DateTime });
}
You have the wrong field because you rely on the Code First's convention but your are not following it correctly for the UsersOrders entity. Assuming that Id is the primary key of User class then it will look for UserId (not User_Id) which is not in your UsersOrders entity. The same goes for Order navigational property in UsersOrders.
To fix this you have to follow the convetion by refactoring your UsersOrders and use data annotations like this: (If you use this solution you must remoeve configuration for UsersOrders entity in your OnModelCreating implementation.
public class UsersOrders
{
[Key]
public int OrderID { get; set; }
[Key]
public int UserID { get; set; }
public virtual User User { get; set; }
public virtual Order Order { get; set; }
}
Or in your OnModelCreating implementation make the following changes for UsersOrders entity like this:
modelBuilder.Entity<UsersOrders>().HasKey(od => new { od.Order_ID, od.User_ID });
modelBuilder.Entity<UsersOrders>().HasRequired(os => os.User).WithMany(p => p.Orders).HasForeignKey(p => p.User_ID);
modelBuilder.Entity<UsersOrders>().HasRequired(os => os.Order).WithMany(p => p.Users).HasForeignKey(p => p.Order_ID);
In the two solutions, note that UsersOrders use composite keys using Order_Id and User_Id not only Order_ID.
I'm trying to use EF6 (via code first) against an existing db. The underlying DB has no FK's in it. The entities are defined with an identity pk, but can also be referenced by a surrogate value (RefId).
Assuming I have:
public class Cart
{
public int Id { get; set; } // PK
public string RefId { get; set; }
public virtual List<CartItem> CartItems { get; set; }
}
public class CartItem
{
public int Id { get; set; } // PK
public string RefId { get; set; }
public string CartRefId {get;set;}
public virtual Cart Cart { get; set; }
}
public class CartMap : EntityTypeConfiguration<Cart>
{
public CartMap()
{
HasKey(t => t.Id);
}
}
public class CartItemMap : EntityTypeConfiguration<CartItem>
{
public CartItemMap()
{
HasKey(t => t.Id);
HasRequired(t => t.Cart)
.WithMany(t => t.CartItems)
.HasForeignKey(t => t.CartRefId);
}
}
How can I tell EF that it should be joining the CartItem to Cart based on
Cart.RefId = CartItem.CartRefId
By default EF will attempt to join
Cart.Id == CartItem.CartRefId
because Cart.Id is the Key of the Cart entity.
I dont think the EF will try this: Cart.Id == CartItem.CartRefId because the types arent the same. So, is not possible to map to have relationships without PK, if you need the Id fields to be autoincrement you could do some like this:
public class Cart
{
public int Id { get; set; }
public string RefId { get; set; } // PK
public virtual List<CartItem> CartItems { get; set; }
}
public class CartItem
{
public int Id { get; set; }
public string RefId { get; set; } // PK
public string CartRefId { get; set; }
public virtual Cart Cart { get; set; }
}
public class CartMap : EntityTypeConfiguration<Cart>
{
public CartMap()
{
HasKey(t => t.RefId);
Property(i => i.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
public class CartItemMap : EntityTypeConfiguration<CartItem>
{
public CartItemMap()
{
HasKey(t => t.RefId);
Property(i => i.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(t => t.Cart)
.WithMany(t => t.CartItems)
.HasForeignKey(t => t.CartRefId);
}
}
If not, other way could be assumes the default mapping with Id PK, then you can use a join query:
var q = from c in db.Carts
join i in db.CartItens on c.RefId equals i.CartRefId
select i;