Entity Framework Many to Many association without FK - c#

So we have this legacy DB to which we have no control and cannot modify in any way or form. Any of the tables in said DB have foreign keys and we are using EF 6 to access it. We created our model and, up until now, things were OK.
Now we are dealing with the following, very common, structure:
[Product] [ProductGroupDetails] [ProductGroup]
ProductID ProductID ProductGroupID
... ProductGroupID .....
Both Product and ProductGroup tables have additional fields which are not relevant right now. The two fields in the ProductGroupDetails conform its primary key.
After adding these three tables to the model I manually added the associations between them by right clicking on the diagram and selected the Add New -> Association option.
After running the tt file I get these entities:
public partial class ProductGroup
{
public ProductGroup()
{
this.ProductGroupDetails = new HashSet<ProductGroupDetail>();
}
public int ProdGroupID { get; set; }
public virtual ICollection<ProdGroupDetail> ProdGroupDetails { get; set; }
}
public partial class ProdGroupDetail
{
public int ProdGroupID { get; set; }
public string ProductID { get; set; }
public virtual Product Products { get; set; }
public virtual ProdGroupHead ProdGroupHead { get; set; }
}
public partial class Product
{
public Product()
{
this.ProductGroupDetails = new HashSet<ProductGroupDetail>();
}
public string ProductID { get; set; }
public virtual ICollection<ProductGroupDetail> ProductGroupDetails { get; set; }
}
I would have expected for the ProductGroupDetails entity not to exist, the Product one having a ProductGroups collection and the ProductGroup to have a Products collection.
Of course this makes it challenging when creating a new Product Group since to the new ProductGroup instance I would have to add an instance of ProductGroupDetail for each group but the ProdGroupID property would be empty since I do not know it yet. Normally I would just add instances of Product to the Products collection and upon save EF would work its magic.
Is there a way I can coerce the edmx so that the ProductGroupDetail entity is not created and the other ones end up having the expected collections?

Related

How to prevent creating relations by EntityFramework Core

I am making a school project which is a shop.
I have created a Product class:
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
public string PhotoUrl { get; set; }
public int Quantity { get; set; }
and an Order class:
public Guid Id { get; set; }
public List<Product> Products { get; set; }
public decimal TotalPrice { get; set; }
//address
public string Street { get; set; }
public string HouseNumber { get; set; }
public string PostCode { get; set; }
public string City { get; set; }
public Order()
{
Products = new List<Product>();
}
As you see in Order.cs there is a list of Products, but entity framework always sets a relationship between my product and a order but I just want to add a Product to this list with no relation ship.
As a response I want to get something like this
{
"id" :"someID",
"products": [
{
first product
},
{
second product
}]
}
etc. How can I prevent creating by ef relationships and do simple lists?
Or how can I do a relationship many products to many orders?
You can add the NotMapped attribute to the Property:
...
[NotMapped]
public List<Product> Products { get; set; }
...
You will need to import System.ComponentModel.DataAnnotations
Either I'm missing something, or you are trying to do something that doesn't make much sense. You say there is no relation - but you do want to save both Order and it's Products to the database? That means that there IS relation (of 1:N kind) and EF is right to create it. It can't work without it.
You didn't include full output that you expect, only the Order part with first_product and second_product placeholders. If the placeholders look like your Product class, just let EF create the relation and you are done. If you want them to look different in JSON (omit some properties for example), you should still let EF create the relation, and then write transformation from your Entity classes (Product, Order) to DTO classes (ProductDTO, and OrderDTO that has List<ProductDto>). Which is good practice anyway, even if Entity and DTO match 1:1, in real project it rarely stays that way for long.

Configure One-None/One Relationship with Multiple Tables using Entity

I'm in a situation where one table has two One-None/One Relationships. How do I implement this using Entity Framework Code-First?
I've seen the following links
https://www.safaribooksonline.com/library/view/programming-entity-framework/9781449317867/ch04s07.html
https://cpratt.co/0-1-to-1-relationships-in-entity-framework/
https://www.tektutorialshub.com/one-to-one-relationship-entity-framework/
Where essentially it's said that the dependent end needs to have a primary key that is the same as that of the principal end. But I'm weary of implementing this with more than one One-None/One Relationship without confirmation and proper knowledge of what's going on. Furthermore I am not sure how to construct statements as it does not have a conventional Foreign Key.
I've also seen Configuring multiple 1 to 0..1 relationships between tables entity framework which confused me beyond recognition.
See below for the relevant part of my DB Diagram:
So Essentially, a Player shouldn't be saved without a DKImage, similarly a Product shouldn't be saved without a DKImage.
Below is the code for Models: Players, Products, DKImages (I know it's not correct, I only implemented it this way so I can generate the database and show the diagram)
Player
public enum Positions { PG, SG, SF, PF, C }
public class Player
{
[Key]
[ForeignKey("Images")]
public int PlayerID { get; set; }
[Required]
public string PlayerName { get; set; }
[Required]
public string PlayerLastName { get; set; }
[Required]
public int PlayerAge { get; set; }
[Required]
public Positions Position { get; set; }
[Required]
public bool Starter { get; set; }
[Required]
[Display(Name = "Active / Not Active")]
public bool Status { get; set; }
//Foreign Keys
public int PlayerStatsID { get; set; }
//Navigation Properties
[ForeignKey("PlayerStatsID")]
public virtual IQueryable<PlayerStats> PlayerStats { get; set; }
public virtual DKImages Images { get; set; }
}
DKImages
public class DKImages
{
[Key]
public int ImageID { get; set; }
[Required]
public string ImageURL { get; set; }
[Required]
public DateTime DateUploaded { get; set; }
//Foreign Keys
[Required]
public int CategoryID { get; set; }
//Navigation Properties
public virtual Products Products { get; set; }
public virtual Category Category { get; set; }
public virtual Player Player { get; set; }
}
Products
public class Products
{
[ForeignKey("Images")]
[Key]
public int ProductID { get; set; }
[Required]
public string ProductName { get; set; }
[Required]
public DateTime DateAdded { get; set; }
//Foreign Keys
[Required]
public int ProductTypeID { get; set; }
//Navigation Properties
[ForeignKey("ProductTypeID")]
public virtual ProductType ProductType { get; set; }
public virtual DKImages Images { get; set; }
}
Edit
I have been told that the code above is correct. If so then how do I create CRUD LINQ Statements (Or any method of constructing CRUD statements for that matter) with the above code.
What you want here is referred to as polymorphic associations: several entities having child entities of one type. They're typically used for comments, remarks, files etc. and usually applied to 1:n associations. In your case there are polymorphic 1:1 associations. Basically these associations look like this (using a bit more generic names):
How to implement them?
Entity Framework 6
In EF6 that's problem. EF6 implements 1:1 associations as shared primary keys: the child's primary key is also a foreign key to its parent's primary key. That would mean that there should be two FKs on Image.ID , one pointing to Person.ID and another one pointing to Product.ID. Technically that's not a problem, semantically it is. Two parent entities now own the same image or, stated differently, an image should always belong to two different parents. In real life, that's nonsense.
The solution could be to reverse the references:
But now there's another problem. The entity that's referred to is named the principal, the other entity is dependent. In the second diagram, Image is the principal, so in order to create a Person, its image must be inserted first and then the person copies its primary key. That's counter-intuitive and most likely also impractical. It's impossible if images are optional.
Nevertheless, since in your case you want images to be required let me show how this association is mapped in EF6.
Let's take this simple model:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Image Image { get; set; }
}
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Image Image { get; set; }
}
public class Image
{
public int ImgID { get; set; } // Named for distinction
public string Url { get; set; }
}
The required mapping is:
modelBuilder.Entity<Image>().HasKey(pd => pd.ImgID);
modelBuilder.Entity<Person>().HasRequired(p => p.Image).WithRequiredDependent();
modelBuilder.Entity<Product>().HasRequired(p => p.Image).WithRequiredDependent();
As you see, Image has two required dependents. Perhaps that's better than two required parents, but it's still weird. Fortunately, in reality it's not a problem, because EF doesn't validate these associations. You can even insert an image without a "required" dependent. I don't know why EF doesn't validate this, but here it comes in handy. The part WithRequiredDependent might as well have been WithOptional, it doesn't make a difference for the generated data model, but at least this mapping conveys your intentions.
An alternative approach could be inheritance. If Person and Product inherit from one base class this base class could be the principal in a 1:1 association with Image. However, I think this is abusing a design pattern. People and products have nothing in common. From a design perspective there's no reason for them to be part of one inheritance tree.
Therefore, in EF6 I think the most feasible solution is to use the third alternative: separate image tables per entity.
Entity Framework Core
In EF-core 1:1 associations can be implemented the EF6 way, but it's also possible to use a separate foreign key field in the dependent entity. Doing so, the polymorphic case looks like this:
The Image class is different:
public class Image
{
public Image()
{ }
public int ImgID { get; set; }
public int? PersonID { get; set; }
public int? ProductID { get; set; }
public string Url { get; set; }
}
And the mapping:
modelBuilder.Entity<Person>().Property(p => p.ID).UseSqlServerIdentityColumn();
modelBuilder.Entity<Person>()
.HasOne(p => p.Image)
.WithOne()
.HasForeignKey<Image>(p => p.PersonID);
modelBuilder.Entity<Product>().Property(p => p.ID).UseSqlServerIdentityColumn();
modelBuilder.Entity<Product>()
.HasOne(p => p.Image)
.WithOne()
.HasForeignKey<Image>(p => p.ProductID);
modelBuilder.Entity<Image>().HasKey(p => p.ImgID);
Watch the nullable foreign keys. They're necessary because an image belongs to either a Person or a Product. That's one drawback of this design. Another is that you need a new foreign key field for each new entity you want to own images. Normally you want to avoid such sparse columns. There's also an advantage as compared to the EF6 implementation: this model allows bidirectional navigation. Image may be extended with Person and Product navigation properties.
EF does a pretty good job translating this into a database design. Each foreign key has a filtered unique index, for example for Person:
CREATE UNIQUE NONCLUSTERED INDEX [IX_Image_PersonID] ON [dbo].[Image]
(
[PersonID] ASC
)
WHERE ([PersonID] IS NOT NULL)
This turns the association into a genuine 1:1 association on the database side. Without the unique index it would be a 1:n association from the database's perspective.
An exemple in your Player table would be this :
public class Player
{
// All the rest you already coded
[Required]
public int ImageID
[ForeignKey("ImageID")]
public virtual DKImage DKImage {get;set;}
}
This would force a player to have a DKImage, but as said in the comments, this create a one to many relationship.
Another way out would be to put all Player fields into the DKImage table, those fields would be null if there is no player associated to this DKImage.
Edit for 1 to 1..0
Ivan Stoev's link got some pretty interesting insight on how to accomplish this :
https://weblogs.asp.net/manavi/associations-in-ef-4-1-code-first-part-3-shared-primary-key-associations
It seems like you will have to put a bit more code in your class :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<DKImage>().HasOptional(t => t.Player).WithRequired();
}
If the tutorial is correct, this would read as :
"DKImage entity has an optional association with one Player object but this association is required for Player entity".
I have not tested it yet.

Child elements insert in Entity Framework 7

I'm using ASP.NET 5 with Entity Framework 7.
I have this models:
public class ParentModel
{
public Guid Id { get; set; }
public virtual ChildModel Children { get; set; }
}
public class ChildModel
{
public Guid Id { get; set; }
public virtual AnotherChildModel AnotherChild { get; set; }
}
public class AnotherChildModel
{
public Guid Id { get; set; }
public string Text { get; set; }
}
When I'm trying to add ParentModel to database, it doesn't automatically add ChildModel and AnotherChildModel to database, while ParentModel completely correct in code, for example:
var parent = new ParentModel() { Children = new ChildModel() { AnotherChild = new AnotherChildModel() { Text = "sometext" }}};
So, simple parentSet.Add(parent) doesn't work, is there another way, except for manually adding all models in sets?
EDIT:
Exception I have:
DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_ParentModel_ChildModel_ChildrenId". The conflict occurred in database "aspnet5-WebApplication1-922849d0-b7da-4169-8150-9a2d05240a47", table "dbo.ChildModel", column 'Id'. The statement has been terminated.
In the current RC1 version of EF7, Add() only recursively adds adhering objects in collections (i.e. true children), not referenced entities, as EF6 did.
So if you'd have the following model (that's more consistent with the names you chose) ...
public class ParentModel
{
public Guid Id { get; set; }
public virtual ICollection<ChildModel> Children { get; set; }
}
... the children would also be added by the single statement parentSet.Add(parent).
I don't know if this is intended behavior. The RC has already proven to come with issues a "release candidate" shouldn't have. But maybe it's an OO-inspired design decision that parents encapsulate their children and not the reverse.

Entity Framework is adding an extra foreign key to my table

I have a very simple data model: a bog standard 1:m relationship. But I want it slightly denormalised for performance reasons and the EF is not doing what I expect it to do.
I am getting an extra foreign key on a join table when I have a 1:m relationship and also slightly denormalise the data so that my Product table has a reference to a specific order as well as a list of orders.
Take 2 classes: Customer and Product. They have a m:m relationship, which is to be joined by the Orders class.
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public virtual Customer Customer { get; set; }
public virtual Product Product { get; set; }
}
So far so simple. Everything is as I expect it to be with the table definitions:
Customers
CustomerId int
Name nvarchar(MAX)
Products
ProductId int
Name nvarchar(MAX)
Orders
OrderId int
Customer_CustomerId int
Product_ProductId int
Now the issue. For reasons I won't go into right now, I want to hold the latest order for a product on the product table itself, rather than have to query and do a WHERE OrderId = MAX(OrderId) on the Orders table.
So I change my model class by adding a single line:
public virtual Order MostRecentOrder { get; set; }
The DB definition for the Product table looks just as I would expect:
MostRecentOrder_OrderId int
However, EF has also added a foreign key to the Orders table:
Product_ProductId1 int
That shouldn't be there. Only one Order can be the LATEST order, and I have a single instance of the order in my Product class.
So I tried doing it a bit more explicitly in the Product class:
public int MostRecentOrderId { get; set; }
[ForeignKey("MostRecentOrderId")]
public virtual Order MostRecentOrder { get; set; }
The Product field gets a name change to reflect the explicitly named column in my class, but the Orders table still has that extra foreign key to the Product table.
I kept playing and found that I could get rid of the erroneous foreign key on the Orders table by unmapping the Product class:
public int? MostRecentOrderId { get; set; }
[ForeignKey("MostRecentOrderId")]
[NotMapped]
public virtual Order MostRecentOrder { get; set; }
I also missed the nullable requirement in my prototype ;)
However, now I cannot make use of pre-loading the data.
This code:
ApplicationDbContext db = new ApplicationDbContext();
var products = db.Products.Include("Orders").Include("MostRecentOrder").ToList();
throws this exception:
A specified Include path is not valid. The EntityType 'WebApplication1.Models.Product' does not declare a navigation property with the name 'MostRecentOrder'.
What am I missing here? I just wish the Product table to have a list of orders, and a reference to one (special) order. In traditional client/server dev, I would code this SQL to get the data back:
-- to go into the "Product" object
SELECT *
FROM Products
LEFT JOIN Orders ON Products.MostRecentOrderId = Orders.OrderId;
WHERE ProductId = 4
and
-- to go into the "Product.Orders collection"
SELECT *
FROM Orders
WHERE ProductId = 4;
So, after you add the MostRecentOrder navigation property, the Product class would look like this:
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public virtual ICollection<Order> Orders { get; set; }
public virtual Order MostRecentOrder { get; set; }
}
What you can do next is use the Fluent API to configure the relationships inside your context class like this:
public class Context : DbContext
{
public DbSet<Order> Orders { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Customer> Customers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder
.Entity<Order>()
.HasRequired(x => x.Product)
.WithMany(x => x.Orders);
modelBuilder
.Entity<Product>()
.HasOptional(x => x.MostRecentOrder);
}
}
It seems to me that convention based configuration are not explicit enough to tell EF about your relationships in such case. Fluent API is more explicit.

Entity Framework, code-first, how to add primary key to table out of many-to-many relationship mapping?

I am building ASP.NET MVC 5 application. I am using Entity Framework 6.1, code first approach to generate a database. I have a many-to-many relationship between Product and Category.
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
// navigation
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public long Id { get; set; }
public string Name { get; set; }
// navigation
public virtual ICollection<Product> Products { get; set; }
}
In the Dbcontext class I override OnModelCreating method to create table for many-to-many relationship as below:
modelBuilder.Entity<Product>().HasMany<Category>(s => s.Categories).WithMany(c => c.Products)
.Map(cs =>
{
cs.MapLeftKey("ProductId");
cs.MapRightKey("CategoryId");
cs.ToTable("ProductCategories");
});
The table comes out as joining the two foreign keys. How do I add an Id (as primary key) to this junction table?
ProductCategories
- Id // add id as primary key
- ProductId
- CategoryId
Let me expand #bubi's answer:
By default, when you define many-to-many relationship (using attributes or FluentAPI), EF creates it (add additional table to DB) and allows you to add many products to a category and many categories to a product. But it doesn't allow you to access the linking table rows as entities.
If you need such feature, for example you what to manage these links some way like mark them as "deleted" or set a "priority", you need to:
Create new Entity (ProductCategoryLink)
Add it to your Context as another DbSet
Update relations in Product and Category entities accordingly.
For you it could like:
Entities
public class Product
{
[Key]
public long ProductId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
[InverseProperty("Product")]
public ICollection<ProductCategoryLink> CategoriesLinks { get; set; }
}
public class Category
{
[Key]
public long CategoryId { get; set; }
public string Name { get; set; }
[InverseProperty("Category")]
public ICollection<ProductCategoryLink> ProductsLinks { get; set; }
}
public class ProductCategoryLink
{
[Key]
[Column(Order=0)]
[ForeignKey("Product")]
public long ProductId { get; set; }
public Product Product { get; set; }
[Key]
[Column(Order=1)]
[ForeignKey("Category")]
public long CategoryId { get; set; }
public Category Category { get; set; }
}
I used attribute-way to define relations as I prefer this approach more. But you can easily replace it by a FluentAPI with two one-to-many relations:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Product to Links one-to-many
modelBuilder.Entity<ProductCategoryLink>()
.HasRequired<Product>(pcl => pcl.Product)
.WithMany(s => s.CategoriesLinks)
.HasForeignKey(s => s.ProductId);
// Categories to Links one-to-many
modelBuilder.Entity<ProductCategoryLink>()
.HasRequired<Category>(pcl => pcl.Category)
.WithMany(s => s.ProductsLinks)
.HasForeignKey(s => s.CategoryId);
}
Context
It's not required but most likely you'll need to save links directly to context, so let's define a DbSet for them.
public class MyContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category > Categories{ get; set; }
public DbSet<ProductCategoryLink> ProductCategoriesLinks { get; set; }
}
Two ways of implementation
Another reason why I used attributes to define relations is that it shows (both marked with [Key] attribute (also pay attention to [Column(Order=X)] attribute])) that two FKs in ProductCategoriesLink entity become a composite PK so you don't need to define another property like "ProductCategoryLinkId" and mark it as a special PK field.
You always could find desired linking entity all you need is just both PK's:
using(var context = new MyContext)
{
var link = context.ProductCategoriesLinks.FirstOrDefault(pcl => pcl.ProductId == 1
&& pcl.CategoryId == 2);
}
Also this approach restricts any chance to save several links with the same Product and Category as they are complex key. If you prefer the way when Id is separated from FK's you'll need to add UNIQUE constraint manually.
Whichever way you choose you'll reach your aim to manipulate the links as you need and add additional properties to them if you need.
Note 1
As we defined many-to-many links as separate entity Product and Category don't have direct relation to each other anymore. So you'll need to update your code:
Instead of adding Product directly to Category or Category directly to Product now you need to define a ProductCategoryLink entity and save it using one of three ways depending on your logic's context:
public void AddProductToCategory(Product product, Company company)
{
using (var context = new MyContext())
{
// create link
var link = new ProductCategoryLink{
ProductId = product.ProductId, // you can leave one link
Product = product, // from these two
CategoryId = category.CategoryId, // and the same here
Category = category
};
// save it
// 1) Add to table directly - most general way, because you could
// have only Ids of product and category, but not the instances
context.ProductCategoriesList.Add(link);
// 2) Add link to Product - you'll need a product instance
product.CategoriesLinks.Add(link);
// 3) Add link to Category - you'll need a category instance
category.ProductLinks.Add(link);
context.SaveChanges();
}
}
Note 2
Also remember as your properties now navigate to ProductCategoryLinks (not to Products for categories and not for Categories for products) if you need to query the second linked entity you need to .Include() it:
public IEnumerable<Product> GetCategoryProducts(long categoryId)
{
using (var context = new MyContext())
{
var products = context.Categories
.Include(c => c.ProductsCategoriesLinks.Select(pcl => pcl.Product))
.FirstOrDefault(c => c.CategoryId == categoryId);
return products;
}
}
UPD:
There is a same question with detailed answer on SO:
Create code first, many to many, with additional fields in association table
If you need a model like you posted (a "clean" model) you need to disable automatic migration and create the table by yourself. EF will not handle Id so it has to be autonumbering.
If you need to handle and see Id inside your app, your model is different and the Junction table must have a class in the model.

Categories