How to create many-to-many relationship on the same entity? - c#

For example I have Poduct entity:
public class Product : DatabaseEntity
{
public int Id {get; set;}
public int Name {get; set;}
public decimal Price {get; set;}
...
}
The idea is I want to create the editable collection of similar products for product. So it is like many-to-many but on the same entity - product So I updated my model like below:
public class Product : DatabaseEntity
{
public int Id {get; set;}
public int Name {get; set;}
public decimal Price {get; set;}
public ICollection<Product> SimilarProducts { get; private set; }
public void AddSimilar(Product product)
{
SimilarProducts.Add(product);
}
...
}
also I updated my DbContext class:
modelBuilder.Entity<Product>()
.HasMany(p => p.SimilarProducts)
.WithOptional()
.WillCascadeOnDelete(false);
implemented edit product action:
public ActionResult Edit(ProductEditModel productEditModel)
{
if(!string.IsNullOrEmpty(productEditModel.SelectedSimilarProductLinkName))
{
var similarProduct = _productRepository.GetProduct(productEditModel.SelectedSimilarProductId);
product.AddSimilar(similarProduct);
}
_productRepository.AddProduct(product);
}
void IProductRepository.AddProduct(Product product);
public void AddProduct(Product product)
{
_repository.InsertOrUpdate(product);
}
But I get strange results: To product was added Product_Id field in my database and there is no such as ProductProduct table or something like that which stores related products ids as in usual many to many entities implementations. How can I create this table manually? What am I missing or what am doing wrong?

Thanks Swell for advice, I've already figured out the solution:
Model:
public class Product : DatabaseEntity
{
public int Id {get; set;}
public int Name {get; set;}
public decimal Price {get; set;}
public ICollection<Product> ParentSimilars { get; set; }
public ICollection<Product> ChildSimilars { get; set; }
[NotMapped]
public IEnumerable<Product> SimilarProducts
{
get
{
return ChildSimilars.Concat(ParentSimilars);
}
}
...
}
DbContext setup:
modelBuilder.Entity<Product>()
.HasMany(p => p.ChildSimilars)
.WithMany(p => p.ParentSimilars)
.Map(m =>
{
m.MapLeftKey("Product_Id");
m.MapRightKey("SimilarProduct_Id");
});
That's, basically, all.

Related

Entity Framework: many-to-many relationship referencing same entity type without join entity?

I am learning .NET and Entity Framework and am trying to create a many-to-many relationship in EF where the relationship (actually two different relationships) is with the same type of entity.
Each Educational Unit can have a list of equivalent educational units.
Each Educational Unit can have a list of prerequisite Educational units.
Here are the classes:
public class EducationalUnit : IAutoAssignable
{
public Guid Id { get; init; }
public string Title {get; set;}
public bool HasAutoAssign {get; set;}
public bool IsTopLevel {get; set;}
public int OrderInParent {get; set;}
public double Duration {get; set;}
public List<EducationalUnit>? Prerequisites {get; set;}
public List<EducationalUnit>? Equivalents {get; set;}
public List<Enrollment>? Enrollments {get; set;}
public List<Competency>? Competencies {get; set;}
public List<Tag>? Tags {get; set;}
public string Type {get; set;}
public string AssignmentLogicString {get; set;}
public User CreatedBy {get; set;}
public User Owner {get; set;}
}
I am struggling to find a way how to implement this in EF. Is the approach here to create two join entities for these relationships (like below) and have two join tables in the DB? is there a best practice for these types of relationships?
public class Equivalent
{
public EducationalUnit EducationalUnitOne {get; set;}
public EducationalUnit EducationalUnitTwo {get; set;}
}
public class Prerequisite
{
public EducationalUnit MainEduationalUnit {get; set;}
public EducationalUnit PrerequisiteEducationalUnit {get; set;}
}
From EF Core 7, shadow property navigation is supported. You can configure the navigation like :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<EducationalUnit>()
.HasMany(u => u.Prerequisites)
.WithMany("Mains");
var manyMany = modelBuilder.Entity<EducationalUnit>()
.HasMany(u => u.Equivalents)
.WithMany("Equivalents1");
}
In EF Core 6/5, shadow navigation property isn't supported. You need to define a CLR property for navigation property like :
public class EducationalUnit
{
public int Id { get; init; }
...
public List<EducationalUnit>? Prerequisites { get; set; }
public List<EducationalUnit>? NeededTo { get; set; }
public List<EducationalUnit>? Equivalents { get; set; }
public List<EducationalUnit>? Equivalents1 { get; set; }
}
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<EducationalUnit>()
.HasMany(u => u.Prerequisites)
.WithMany(u => u.NeededTo);
modelBuilder.Entity<EducationalUnit>()
.HasMany(u => u.Equivalents)
.WithMany(u => u.Equivalents1);
}

Create many-to-many relationship between two entities via multiple columns

I have the following four tables:
Customer
Id
FirstName
...
ConsumptionPoint
Id
Address
...
Invoice
Id
InvoiceNumber
CustomerId
ConsumptionPointId
...
ContractAccount
Id
ContractAccountNumber
CustomerId
ConsumptionPointId
IsCurrentDelivery
...
I want to get the ContractAccountNumber for an Invoice.
Is it possible to create some kind of a relation between these both to access the ContractAccount(s) of an Invoice directly?
Currently I'm doing something like:
invoice.Customer.ContractAccounts
.Where(ca => ca.ConsumptionPoint == invoice.ConsumptionPoint &&
ca.IsCurrentDelivery == true).FirstOrDefault();
update in SQL I would simply do a join with multiple conditions:
SELECT i.Id AS InvoiceId, ca.Id AS ContractAccountId,
ca.ContractAccountNumber
FROM Invoices i
LEFT JOIN ContractAccounts ca
ON i.ConsumptionPointId = ca.ConsumptionPointId
AND i.CustomerId = ca.CustomerId
WHERE ca.IsCurrentDelivery = 1
update 2:
Basically I just want to get rid of the ca.ConsumptionPoint == invoice.ConsumptionPoint in the Where-Clause and want to define this inside the relation.
Actually this is a many-to-many relationship: one Invoice can link to many ContractAccounts (via different Customer/ConsumptionPoint combinations) and one ContractAccount can link to many Invoices. Is there no way to tell .net to build a many-to-many relationship, based on the combination of two custom columns?
I do not think that you need two keys for that. Using EF core you can create a relationship like that:
Invoice:
public class Invoice
{
public int Id {get; set;}
public int InvoiceNumber {get; set;}
public int CustomerId {get; set;}
public Customer Customer {get; set;}
public int ConsumptionPointId {get; set;}
public int ContractAccountId {get; set;}
public ContractAccount ContractAccount {get; set;}
}
public class ContractAccount
{
public int Id {get; set;}
public int ContractAccountNumber {get; set;}
public bool IsCurrentDelivery {get; set;}
}
Then configuration:
modelBuilder.Entity<Invoice>()
.HasOne(b => b.ContractAccount)
.WithOne()
.HasForeignKey<Invoice>(b => b.ContractAccountId);
//probably already exists in your code
modelBuilder.Entity<Invoice>()
.HasOne(b => b.Customer )
.WithMany()
.HasForeignKey(b => b.CustomerId);
And then you can access it directly from invoice: invoice.ContractAccount
After some deeper investigation I have found a solution.
Basically it's just a many-to-many relationship. So we can add the appropriate properties to Invoice and ContractAccount Model:
public class Invoice
{
[Key]
public long Id { get; set; }
public Customer Customer { get; set; }
public ConsumptionPoint ConsumptionPoint { get; set; }
public virtual ICollection<ContractAccount> ContractAccounts { get; set; }
}
public class ContractAccount
{
[Key]
public long Id { get; set; }
public Customer Customer { get; set; }
public ConsumptionPoint ConsumptionPoint { get; set; }
public ICollection<Invoice> Invoices { get; set; }
}
Now, we just have to configure the relationship manually:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Invoice>()
.HasMany(i => i.ContractAccounts)
.WithOne()
.HasForeignKey(ca => new { ca.CustomerId, ca.ConsumptionPointId })
.HasPrincipalKey(i => new { i.CustomerId, i.ConsumptionPointId });
modelBuilder.Entity<ContractAccount>()
.HasMany(ca => ca.Invoices)
.WithOne()
.HasForeignKey(i => new { i.CustomerId, i.ConsumptionPointId })
.HasPrincipalKey(ca => new { ca.CustomerId, ca.ConsumptionPointId });
}
And that's it. Now I can do something like:
invoice.ContractAccounts
.Where(ca => ca.IsCurrentDelivery == true).FirstOrDefault();
which is way better than before.
Thanks for your comments, which pointed me to the right direction.

One to one or zero mapping in Entity Framework Core 2.2

I have a series of models that relate to a parent model, and the series of models have a child table that relates back to the original parent, so:
Public Class A
{
[Key]
public long AId {get; set;}
public string Name {get; set;}
}
Public Class B1
{
[Key]
public long Id {get; set;}
[ForeignKey("AId")]
public virtual A a {get;set;}
public virtual ICollection<C> Cs {get;set}
}
Public Class B2
{
[Key]
public long AId {get; set;}
[ForeignKey("AId")]
public virtual A a {get;set;}
public virtual ICollection<C> Cs {get;set}
}
Public Class C
{
public long CId {get; set;}
public long AId {get; set;}
[ForeignKey("AId")]
public virtual A a {get;set;}
}
The ApplicationDBContext ties the one to one/zero entities together with fluent api, but I can't figure out how to tie c. The context looks like this:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
public DbSet<A> As { get; set; }
public DbSet<B1> B1s { get; set; }
public DbSet<B2> B2s { get; set; }
public DbSet<C> Cs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<B1>().HasOne(x => x.a).WithOne().HasForeignKey<A>(p => p.Id);
}
I want to tie C in with something like this:
modelBuilder.Entity<B1>().HasMany(x => x.Cs).WithOne(y => y.a);
But it doesn't like this because it is referencing table A, the parent, which is what I want it to do. This is my first attempt at using Core and I am not finding a way to tie 3 tables together with fluent api. I was able to do this in EF 4.6, so I am assuming this should be possible in Core.
Any help would be appreciated.
I figured it out....
modelBuilder.Entity<B1>().HasMany(x => x.Cs).WithOne().HasForeignKey(y => y.AId);
Hopefully this helps someone else out.

Entity Framework Include Record with certain value in a Navigation Property

I am using Entity Framework 6 Code First and I'm configuring the mapping of my domain model with Fluent API. I don't see how to create a navigation properties for a Table which is a little tricky.
I have several objects which can make noise, I would like to record that noise in a NoiseRecord Table.
I need some kind of conditional mapping, something like that :
modelBuilder.Entity<NoiseRecord>().HasRequired(n=>n.Origine.OrigineType()=="Car").WithMany(c=>c.NoiseRecords);
That would be the mapping of the Car Navigation Property to avoid that, for example, it includes record related to Planes.
Here is my code
public interface INoisy
{
int ID {get; set;}
string OriginType()
...
//And other useful things not related to persistence
}
public class Car : INoisy
{
...
ICollection<NoiseRecord> NoiseRecords { get; set; }
string OrigineType()
{
return "Car";
}
}
public class Plane : INoisy
{
...
ICollection<NoiseRecord> NoiseRecords {get; set;}
string OrigineType()
{
return "Plane";
}
}
And a couple of other classes implement INoisy also.
Below is the NoiseRecord Table.
public class NoiseRecord
{
public int RecordID {get; set;}
public INoisy NoiseOrigine {get; set;}
public double NoiseMagnitude {get; set;}
}
I'm looking for a way to achieve that with Fluent API.
Thank you !
First of all, it is not possible to use interfaces as navigation properties. But you could use an abstract base class for your noise origins
public abstract class NoiseOrigin
{
public NoiseOrigin()
{
this.NoiseRecords = new Collection<NoiseRecord>();
}
public int Id { get; set; }
public ICollection<NoiseRecord> NoiseRecords { get; set; }
}
public class Car : NoiseOrigin {}
public class Plane : NoiseOrigin { }
public class NoiseRecord
{
public int Id { get; set; }
public int OriginId { get; set; }
public NoiseOrigin Origin { get; set; }
public double NoiseMagnitude { get; set; }
}
Your fluent API mapping whould look like this
public class NoiseModelContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Car>().Map(p => p.Requires("Type").HasValue("Car"));
modelBuilder.Entity<Plane>().Map(p => p.Requires("Type").HasValue("Plane"));
}
public DbSet<NoiseOrigin> NoiseOrigins { get; set; }
public DbSet<NoiseRecord> NoiseRecords { get; set; }
}
To get all car noise records your query will look like
using (var db = new NoiseModelContext()) {
var records = db.NoiseRecords.Where(p => p.Origin is Car);
// or like this - the result is the same.
var records2 = db.NoiseOrigins.OfType<Car>().SelectMany(p => p.NoiseRecords);
}

Fluent API EF 6 - How to configure 1:M (with M being of the same type)

I'm making my first steps with Fluent Api and I'm trying to understand how to do the following: This is my model.
public class Person
{
public int Id {get ; set;}
public List<View> Viewers {get; set;}
}
public class View
{
public int Id {get; set;}
public Person Viewer {get; set;}
}
This is a simplification of my model, I want to keep track of Person's that access some others profiles. I want to know for every person who has seen their profile.
I have tried this:
var view = modelBuilder.Entity<View>();
view.HasKey(v=>v.Id);
var person = modelBuilder.Entity<Person>();
person.HasKey(r => r.Id);
person.HasMany(t => t.Viewers).WithRequired(t=>t.Viewer);
I know this seems super silly, but from my code I want to be able to navigate:
Person -> Viewers (let's take viewer 1) -> Viewers, and so on...
Is this the right approach?
Thanks in advanced!
try this
public class Person
{ [Key]
public int Id { get; set; }
public ICollection<View> Viewers { get; set; }
}
public class View
{ [Key]
public int Id { get; set; }
public int ViewerId { get; set; } //this is a ForeingKey
public Person Viewer { get; set; }
}
public class MyDbContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<View> Views { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<View>().HasRequired(a => a.Viewer).WithMany().HasForeignKey(a => a.ViewerId);
base.OnModelCreating(modelBuilder);
}
}
you may do the same with DataAnnotation Attributes and then it does't need to use Fluent API.
public class View
{
public int Id { get; set; }
public int ViewerId { get; set; }
[ForeignKey("ViewerId")] // here is a foreignkey property
[InverseProperty("Viewers")] // here is a navigation property in Person class
public Person Viewer { get; set; }
}

Categories

Resources