I am starting out with using the EF Code First with MVC and am a bit stumped with something. I have the following db structure (Sorry but I was not allowed to post an image unfortunately):
Table - Products
Table - RelatedProducts
1-Many on Products.ProductID -> RelatedProducts.ProductID
1-Many on Products.ProductID -> RelatedProducts.RelatedProductID
Basically I have a product that can have a series of products that are related to it. These are kept in the RelatedProducts table with the relationship defined by the ProductID and the ProductID of the related product which I have named RelatedProductID. In my code I has produced the following classes:
public class MyDBEntities : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<RelatedProduct> RelatedProducts { get; set; }
}
public class Product
{
public Guid ProductID { get; set; }
public string Name { get; set; }
public string Heading { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public Guid CategoryID { get; set; }
public string ImageURL { get; set; }
public string LargeImageURL { get; set; }
public string Serves { get; set; }
public virtual List<RelatedProduct> RelatedProducts { get; set; }
}
public class RelatedProduct
{
public Guid ProductID { get; set; }
public Guid RelatedProductID { get; set; }
public virtual Product Product { get; set; }
public virtual Product SimilarProduct { get; set; }
}
I then try to access these in code using:
myDB.Products.Include("RelatedProducts").Where(x => x.ProductID == productID).FirstOrDefault();
But I keep getting the following error:
{"Invalid column name 'ProductProductID2'.\r\nInvalid column name 'ProductProductID2'.\r\nInvalid column name 'ProductProductID'.\r\nInvalid column name 'ProductProductID1'.\r\nInvalid column name 'ProductProductID2'."}
What am I doing wrong? I basically want to get a product then iterate through the RelatedProducts and display that product info.
The first part of the answer is that EF4 CTP5 is not correctly mapping your POCOs to the database because it's not smart enough. If you check out the database, you get:
CREATE TABLE RelatedProducts(
RelatedProductID uniqueidentifier NOT NULL,
ProductID uniqueidentifier NOT NULL,
ProductProductID uniqueidentifier NULL,
ProductProductID1 uniqueidentifier NULL,
ProductProductID2 uniqueidentifier NULL,
PRIMARY KEY CLUSTERED
(
RelatedProductID ASC
)
) ON [PRIMARY]
Yuck! This needs to be fixed with some manual work. In your DbContext, you add rules like so:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.Property(p => p.ProductID)
.HasDatabaseGenerationOption(DatabaseGenerationOption.Identity);
modelBuilder.Entity<RelatedProduct>()
.HasKey(rp => new { rp.ProductID, rp.RelatedProductID });
modelBuilder.Entity<Product>()
.HasMany(p => p.RelatedProducts)
.WithRequired(rp => rp.Product)
.HasForeignKey(rp => rp.ProductID)
.WillCascadeOnDelete();
modelBuilder.Entity<RelatedProduct>()
.HasRequired(rp => rp.SimilarProduct)
.WithMany()
.HasForeignKey(rp=> rp.RelatedProductID)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
Related
I have a similar problem to the one posted here:
Entity Framework Code First - two Foreign Keys from same table, however it's very old and doesn't apply to Core and I can't get the suggestions to work for me.
Basically, I'm trying to create a fixture table which will have two foreign keys to the team table. A fixture is made up of a home team and an away team. Having nullable fields isn't an option.
Consider a fixture, with two teams.
public class Fixture
{
public int Id { get; set; }
public Team HomeTeam { get; set; }
public int HomeTeamId { get; set; }
public Team AwayTeam { get; set; }
public int AwayTeamId { get; set; }
public virtual Team HomeTeam { get; set; }
public virtual Team AwayTeam { get; set; }
}
public class Team
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public String Name { get; set; }
public ICollection<Fixture> HomeFixtures { get; set; } = new List<Fixture>();
public ICollection<Fixture> AwayFixtures { get; set; } = new List<Fixture>();
}
I get the error...
Unable to determine the relationship represented by navigation property 'Fixture.HomeTeam' of type 'Team'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
So I tried to add some OnModelCreating code in the database context:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Fixture>()
.HasOne(m => m.HomeTeam)
.WithMany(t => t.HomeFixtures)
.HasForeignKey(m => m.HomeTeamId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Fixture>()
.HasOne(m => m.AwayTeam)
.WithMany(t => t.AwayFixtures)
.HasForeignKey(m => m.AwayTeamId)
.OnDelete(DeleteBehavior.Restrict);
}
Then I got the error:
Introducing FOREIGN KEY constraint 'FK_Fixtures_Teams_HomeTeamId' on table 'Fixtures' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Can anyone help with getting this setup please?
Thanks.
public class Fixture
{
public int Id { get; set; }
public int HomeTeamId { get; set; }
public int AwayTeamId { get; set; }
[ForeignKey("HomeTeamId")]
public virtual Team HomeTeam { get; set; }
[ForeignKey("AwayTeamId")]
public virtual Team AwayTeam { get; set; }
}
This way navigation will work. Also as suggested by #Ivan remove duplicate getters and setters.
Solution below worked for me for EF Core 3:
Make sure to make foreign keys nullable
Specify default behavior on Delete
public class Match
{
public int? HomeTeamId { get; set; }
public int? GuestTeamId { get; set; }
public float HomePoints { get; set; }
public float GuestPoints { get; set; }
public DateTime Date { get; set; }
public Team HomeTeam { get; set; }
public Team GuestTeam { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Match>()
.HasRequired(m => m.HomeTeam)
.WithMany(t => t.HomeMatches)
.HasForeignKey(m => m.HomeTeamId)
.OnDelete(DeleteBehavior.ClientSetNull);
modelBuilder.Entity<Match>()
.HasRequired(m => m.GuestTeam)
.WithMany(t => t.AwayMatches)
.HasForeignKey(m => m.GuestTeamId)
.OnDelete(DeleteBehavior.ClientSetNull);
}
I created a post with an issue and another issue.
These can be looked at for references but i consider them as handled.
My question arising from these issues and the action i (need or not need) to apply bothers me because i don't quite understand EF its behavior and expectations.
I have a Product, PurchasePrice and SalesPrice entity where my initial thought was that 1 Product can have multiple PurchasePrices but that 1 PurchasePrice only can exist in 1 Product (same for SalesPrice).
Therefore these relations:
// NOTE that BaseEntity just has a "int ID" prop and datetimes/stamps
public class Product : BaseEntity
{
public ICollection<PurchasePrice> PurchasePrices { get; set; }
public ICollection<PurchasePrice> SalesPrices { get; set; }
}
public class PurchasePrice:BaseEntity
{
public Product Product { get; set; }
}
public class SalesPrice:BaseEntity
{
public Product Product { get; set; }
}
Now, lets add a Supplier Entity to it because that is why i seperate Sales & Purchase apart and don't create an Enum out of it, because 1 Product (in database) can have multiple suppliers, each having their own Sales/Purchase prices AND another Productnumber value.
So above becomes:
public class Product : BaseEntity
{
public ICollection<PurchasePrice> PurchasePrices { get; set; }
public ICollection<PurchasePrice> SalesPrices { get; set; }
// added
public ICollection<Supplier> Suppliers { get; set; }
}
public class PurchasePrice:BaseEntity
{
public Product Product { get; set; }
// added
public Supplier Supplier { get; set; }
}
public class SalesPrice:BaseEntity
{
public Product Product { get; set; }
// added
public Supplier Supplier { get; set; }
}
// added Entity Supplier into the party
public class Supplier : BaseEntity
{
public ICollection<Product> Products { get; set; }
public ICollection<PurchasePrice> PurchasePrices { get; set; }
public ICollection<SalesPrice> SalesPrices { get; set; }
}
Lets continue a little furhter because it doesn't stop there, i want to keep track of these Product-Supplier-Prices relations so i created a Entity called 'ProductSupplierForContract' which would have the following structure:
public class ProductSupplierForContract:BaseEntity
{
public string ProductnumberValue { get; set; }
public int Product_Id { get; set; }
public int Supplier_Id { get; set; }
public int? Contract_Id { get; set; }
public virtual Product Product { get; set; }
public virtual Supplier Supplier { get; set; }
public virtual Contract Contract { get; set; }
}
Finally i have a Contract Entity which has the following structure:
public class Contract:BaseEntity
{
[Required]
public ICollection<Product> Products { get; set; }
public ICollection<ProductSupplierForContract> ProductSupplierForContracts { get; set; }
}
So Product becomes:
public class Product : BaseEntity
{
public ICollection<PurchasePrice> PurchasePrices { get; set; }
public ICollection<PurchasePrice> SalesPrices { get; set; }
public ICollection<Supplier> Suppliers { get; set; }
// added
public ICollection<Contract> Contracts { get; set; }
}
Custom Seeding (inherits from DropCreateDatabaseAlways):
protected override void Seed(ApplicationDbContext context)
{
PurchasePrice purchaseprice = new PurchasePrice((decimal)17.70);
ctx.PurchasePrices.Add(purchaseprice);
Product product1 = new Product("test product 1",purchaseprice);
ctx.Products.Add(product1);
base.Seed(ctx);
}
I also have mappings defined in Fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// setting the Product FK relation required + related entity
modelBuilder.Entity<Entity.ProductSupplierForContract>().HasRequired(psfc => psfc.Product)
.WithMany(p => p.ProductSupplierForContracts)
.HasForeignKey(psfc => psfc.Product_Id);
// setting the Supplier FK relation required + related entity
modelBuilder.Entity<Entity.ProductSupplierForContract>().HasRequired(psfc => psfc.Supplier)
.WithMany(s => s.ProductSupplierForContracts)
.HasForeignKey(psfc => psfc.Supplier_Id);
// setting the Contract FK relation required + related entity
modelBuilder.Entity<Entity.ProductSupplierForContract>().HasOptional(psfc => psfc.Contract)
.WithMany(c => c.ProductSupplierForContracts)
.HasForeignKey(psfc => psfc.Contract_Id);
}
Now, initially i didn't had any issues and i really really don't understand what has brought up this sudden change that i now got duplicates Products when i seed my database. I can strip it down to just adding a simple PurchasePrice with a value and a Product having a reference to this PurchasePrice and there is my duplicate.
Changing the relation inside the PurchasePrice class of the Entity Product, to a ICollection doesn't create a duplicate but i don't want this collection because it is not a Many to Many relation ...
I have tried enormous amounts of things but nothing that "resolved" this (if this is a problem to start with, for me yes but maybe not for EF) like removing inhertance BaseEntity, changinge Mapping (Fluent AND annotations), changed the way i seeded and initialized everthing, defining ID's myself, you name it ...
Mind that the purpose is not to optimize the way i seed in anyway but to have a decent working Model AND to understand what EF does and what it wants.
My questions:
Why is this duplicate occuring/appearing ?
If i want to be able to have 1 instance holding the relation of
Price-Supplier-Product-Contract, how should i do this? Answer is here
I fixed my problem by redesigning the model. I have added a additional Entity ProductForSupplier which holds the relation of a Product & Supplier and a Productnumber.
public class ProductForSupplier:BaseEntity
{
public string ProductnumberValue { get; set; }
[Required]
public Product Product { get; set; }
[Required]
public Supplier Supplier { get; set; }
}
Added a Entity ProductsForContract which will hold the amount of a Product-Supplier relation for 1 contract:
public class ProductsForContract
{
public int ProductsForContractId { get; set; }
public int Amount { get; set; }
public ProductForSupplier ProductForSupplier { get; set; }
public Contract Contract { get; set; }
}
And the Existing Entity ProductSupplierForContract becomes:
public class ProductSupplierForContract:BaseEntity
{
public ICollection<ProductsForContract> ProductsForContract { get; set; }
[Required]
public Contract Contract { get; set; }
}
This gives me the flexibility to keep relations of any kind between the entities and also has taken care of the duplicate (which i still don't know the cause of).
IQueryable<Product> product = objContext.Set<Product>().Include(p =>
p.Categories.Name).Where(p => p.Id == 2);
As per the current view, I'm getting an error. It says add other model with their properties. i.e. to include Category model and corresponding Name property.
#model IEnumerable<>crudOneToMany.Models.Product>
using viewmodel, is it possible to join two tables?
View
Error
A specified Include path is not valid. The EntityType 'crudOneToMany.Models.Category' does not declare a navigation property with the name 'Name'.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CategoryId { get; set; }
public virtual Category Categories { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
public class ProductDBContext : DbContext
{
public ProductDBContext()
: base("ProductDBContext")
{
}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().HasRequired(o => o.Categories).WithMany(o => o.Products).HasForeignKey(o => o.CategoryId);
base.OnModelCreating(modelBuilder);
}
}
Your problem is here:
.Include(p => p.Categories.Name)
Instead you should write .Include(p => p.Categories)
This means that in output there will be loaded Categories navigation collection to product.
Name is simple string property (is not navigation property so it should not be included)
Here is the proposed ViewModel for you.
ProductViewModel.cs
public class ProductViewModel
{
public int Id { get; set; }
[Required(ErrorMessage = "required")]
public string ProductName { get; set; }
public Category Category { get; set; }
public ICollection<Category> Categories { get; set; }
}
I have two entities in my MVC application and I populated the database with Entity Framework 6 Code First approach. There are two city id in the Student entity; one of them for BirthCity, the other for WorkingCity. When I define the foreign keys as above an extra column is created named City_ID in the Student table after migration. Id there a mistake or how to define these FKs? Thanks in advance.
Student:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int BirthCityID { get; set; }
public int LivingCityID { get; set; }
[ForeignKey("BirthCityID")]
public virtual City BirthCity { get; set; }
[ForeignKey("LivingCityID")]
public virtual City LivingCity { get; set; }
}
City:
public class City
{
public int ID { get; set; }
public string CityName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
To achieve what you want you need to provide some aditional configuration.Code First convention can identify bidirectional relationships, but not when there are
multiple bidirectional relationships between two entities.You can add configuration (using Data Annotations or the Fluent API) to present this
information to the model builder. With Data Annotations, you’ll use an annotation
called InverseProperty. With the Fluent API, you’ll use a combination of the Has/With methods to specify the correct ends of these relationships.
Using Data Annotations could be like this:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int BirthCityID { get; set; }
public int LivingCityID { get; set; }
[ForeignKey("BirthCityID")]
[InverseProperty("Students")]
public virtual City BirthCity { get; set; }
[ForeignKey("LivingCityID")]
public virtual City LivingCity { get; set; }
}
This way you specifying explicitly that you want to relate the BirthCity navigation property with Students navigation property in the other end of the relationship.
Using Fluent Api could be like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasRequired(m => m.BirthCity)
.WithMany(m => m.Students).HasForeignKey(m=>m.BirthCityId);
modelBuilder.Entity<Student>().HasRequired(m => m.LivingCity)
.WithMany().HasForeignKey(m=>m.LivingCityId);
}
With this last solution you don't need to use any attibute.
Now, the suggestion of #ChristPratt in have a collection of Student in your City class for each relationship is really useful. If you do that, then the configurations using Data Annotations could be this way:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int BirthCityID { get; set; }
public int LivingCityID { get; set; }
[ForeignKey("BirthCityID")]
[InverseProperty("BirthCityStudents")]
public virtual City BirthCity { get; set; }
[ForeignKey("LivingCityID")]
[InverseProperty("LivingCityStudents")]
public virtual City LivingCity { get; set; }
}
Or using Fluent Api following the same idea:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasRequired(m => m.BirthCity)
.WithMany(m => m.BirthCityStudents).HasForeignKey(m=>m.BirthCityId);
modelBuilder.Entity<Student>().HasRequired(m => m.LivingCity)
.WithMany(m => m.LivingCityStudents).HasForeignKey(m=>m.LivingCityId);
}
Sheesh. It's been a long day. There's actually a very big, glaring problem with your code, actually, that I completely missed when I commented.
The problem is that you're using a single collection of students on City. What's actually happening here is that EF can't decide which foreign key it should actually map that collection to, so it creates another foreign key specifically to track that relationship. Then, in effect you have no navigation properties for the collections of students derived from BirthCity and LivingCity.
For this, you have to drop down to fluent configuration, as there's no way to configure this properly using just data annotations. You'll also need an additional collection of students so you can track both relationships:
public class City
{
...
public virtual ICollection<Student> BirthCityStudents { get; set; }
public virtual ICollection<Student> LivingCityStudents { get; set; }
}
Then, for Student:
public class Student
{
...
public class StudentMapping : EntityTypeConfiguration<Student>
{
public StudentMapping()
{
HasRequired(m => m.BirthCity).WithMany(m => m.BirthCityStudents);
HasRequired(m => m.LivingCity).WithMany(m => m.LivingCityStudents);
}
}
}
And finally in your context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new Student.StudentMapping());
}
I'm traying to do a mapping with One to One relationship with id as "foreign", I can't change the database
Those are the tables
Cutomer
int CustomerId
string Name
CustomerDetail
int CustomerId
string Details
Entity Splittitng does not works for me since i need a left outter join.
Any Ideas?
Thanks in advance,
and sorry about my english.
You can use the Shared Primary Key mapping here.
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
public virtual CustomerDetail CustomerDetail { get; set; }
}
public class CustomerDetail
{
public int CustomerId { get; set; }
public string Details { get; set; }
public virtual Customer Customer { get; set; }
}
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<CustomerDetail>().HasKey(d => d.CustomerId);
modelBuilder.Entity<Customer>().HasOptional(c => c.CustomerDetail)
.WithRequired(d => d.Customer);
}
}