2 Foreign Keys, 1 Navigation Property to pull through both - c#

Given the following rough code-first schema, the goal would appear to be quite simple. An Invoice can either be from or to a Company, and the Invoices collection should contain all invoices regardless of which it is.
public class Company
{
public int Id { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
}
public class Invoice
{
public int Id { get; set; }
public int FromCompanyId { get; set; }
public int ToCompanyId { get; set; }
public virtual Company FromCompany { get; set; }
public virtual Company ToCompany { get; set; }
}
You'll note in a Migration that a third Company_Id is generated for obvious reasons to support the Invoices navigation property as EF only appears to support a 1 Nav Prop -> 1 FK arrangement.
My question is whether or not it is possible to have the Invoices property contain both, or if I should map them individually (ie. IC<Inv> InvoicesFrom, IC<Inv> InvoicesTo) and create a client-side collection to have both manually.
I have tried:
Using InverseProperty on both FromCompany and ToCompany, which confuses EF as it can't determine the principal end of the relationship.
[ForeignKey(nameof(FromCompanyId)), InverseProperty(nameof(Company.Invoices))]
public virtual Company FromCompany { get; set; }
[ForeignKey(nameof(ToCompanyId)), InverseProperty(nameof(Company.Invoices))]
public virtual Company ToCompany { get; set; }
Using fluent API to map them, but it only takes into account the second which makes sense from a code perspective.
modelBuilder.Entity<Company>()
.HasMany(m => m.Invoices)
.WithRequired(m => m.ToCompany)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Company>()
.HasMany(m => m.Invoices)
.WithRequired(m => m.FromCompany)
.WillCascadeOnDelete(false);
There's of course no major issue if this isn't possible, I just could have sworn I've done it before.

For posterity, here is a complete version of the workaround to maintain an IEnumerable<Invoices> from company that contains both of the sets put together.
public class MyContext : DbContext
{
public MyContext() : base("DefaultConnection") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Company>().HasMany(c => c.InvoicesFrom).WithRequired(i => i.FromCompany).WillCascadeOnDelete(false);
modelBuilder.Entity<Company>().HasMany(c => c.InvoicesTo).WithRequired(i => i.ToCompany).WillCascadeOnDelete(false);
}
public DbSet<Company> Companies { get; set; }
public DbSet<Invoice> Invoices { get; set; }
}
public class Company
{
public int Id { get; set; }
public virtual ICollection<Invoice> InvoicesFrom { get; set; }
public virtual ICollection<Invoice> InvoicesTo { get; set; }
[NotMapped]
public IEnumerable<Invoice> Invoices
{
get {
return InvoicesFrom.Union(InvoicesTo);
}
}
}
public class Invoice
{
public int Id { get; set; }
public int FromCompanyId { get; set; }
public int ToCompanyId { get; set; }
public virtual Company FromCompany { get; set; }
public virtual Company ToCompany { get; set; }
}

Related

How to do Many To Many relationship in Ef Core using Fluent API?

I'm trying to create a many to many relation in the Fluent API with Ef Core 6 but i am having trouble understanding how to do so.
I've looked around here in stackoverflow but couldn't understand this relation and how to reproduce it in my code.
I have a table in my SQL database called People:
People.cs:
public class People : PeopleBase
{
public People()
{
RegistrationList = new HashSet<Registration>();
}
public virtual ICollection<Registration> RegistrationList { get; set; }
public virtual User User { get; set; }
public virtual ActivityGroup ActivityGroup { get; set; }
}
PeopleBase.cs:
public abstract class PeopleBase: ModelBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid PeopleId { get; set; }
public Guid? UserId { get; set; }
public Guid? ActivityGroupId { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public PeopleActiveType Active { get; set; }
}
And then i have another table called ActivityGroup:
ActivityGroup.cs:
public class ActivityGroup : ActivityGroupBase
{
public ActivityGroup()
{
PeopleList = new HashSet<People>();
ActivityList = new HashSet<Activity>();
}
public ICollection<People> PeopleList { get; set; }
public ICollection<Activity> ActivityList { get; set; }
}
ActivityGroupBase.cs:
public abstract class ActivityGroupBase : ModelBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ActivityGroupId { get; set; }
[Required]
[StringLength(20)]
public string Name { get; set; }
public StatusRecord Status { get; set; }
}
How would i do the mapping in the modelBuilder given that:
ActivityGroupId is the foreing key in the People database, pointing to the other table
One PeopleId can have multiple (many) ActivityGroupId
One ActivityGroupId can be assigned to multiple people.
What i've done so far:
modelBuilder.Entity<People>()
.HasOne(x => x.ActivityGroup)
.WithMany(x => x.PeopleList)
.HasForeignKey(x => x.ActivityGroupId);
Wouldn't i have to do this instead?
modelBuilder.Entity<People>()
.Hasmany(x => x.ActivityGroupList) //this is a ICollection<ActivityGroup> inside People class
.WithMany(x => x.PeopleList)
.HasForeignKey(x => x.ActivityGroupId); // this is not recognized by Ef Core
Can anyone help me please?
There are two main approaches for many-to-many relationships - with implicit junction table:
public class People : PeopleBase
{
// ...
public virtual List<Activity> Activities { get; set; }
}
public class Activity // : ...
{
// ...
public virtual List<People> PeopleList { get; set; }
}
modelBuilder.Entity<People>()
.HasMany(x => x.Activities)
.WithMany(x => x.PeopleList);
Or with explicit one:
public class People
{
// ...
public ICollection<PeopleActivity> PeopleActivities { get; set; }
}
public class Activity
{
// ...
public virtual ICollection<PeopleActivity> PeopleActivities { get; set; }
}
public class PeopleActivity
{
public Guid ActivityId { get; set; }
public Guid PeopleId { get; set; }
public Activity Activity { get; set; }
public People People { get; set; }
}
modelBuilder.Entity<PeopleActivity>()
.HasOne(pt => pt.People)
.WithMany(t => t.PeopleActivities)
.HasForeignKey(pt => pt.PeopleId);
Also maybe it worth changing entity name from People to Person (you can change table name with .ToTable("People") call)?

Entity Created OnModelCreating is not accessible in DbContext

I have a many to many relationship mapping, and the mapping table has an additional field. I created the ApplicationDbContext as below:
public virtual DbSet<Country> Countries { get; set; }
public virtual DbSet<Region> Regions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<CountryRegionMapping>()
.HasKey(um => um.CountryRegionMappingId)
.ToTable("CountryRegionMapping");
modelBuilder.Entity<CountryRegionMapping>()
.HasRequired(um => um.Region).WithMany(g => g.CountryMappings)
.HasForeignKey(um => um.RegionId);
modelBuilder.Entity<CountryRegionMapping>()
.HasRequired(um => um.Country).WithMany(g => g.RegionMappings)
.HasForeignKey(um => um.CountryId);
base.OnModelCreating(modelBuilder);
}
I was refering this link to create the Many-to-Many relationship having an extra field in the mapping table.
The entity classes are :
public class Country
{
public int Id { get; set; }
public string SystemOneName { get; set; }
public string SystemTwoName { get; set; }
public virtual ICollection<CountryRegionMapping> RegionMappings { get; set; }
}
public class Region
{
public int Id { get; set; }
public string SystemOneName { get; set; }
public string SystemTwoName { get; set; }
public virtual ICollection<CountryRegionMapping> CountryMappings { get; set; }
}
public class CountryRegionMapping
{
public int CountryRegionMappingId { get; set; }
public int CountryId { get; set; }
public virtual Country Country { get; set; }
public int RegionId { get; set; }
public virtual Region Region { get; set; }
public bool CheckField { get; set; }
}
When I try to access the Country or Region tables I can simply access it through the code as below using dbcontext.Regions
ApplicationDbContext db = new ApplicationDbContext();
db.Regions.SingleOrDefault(r => r.Id == Id);
But when I try to access the "CountryRegionMapping" entity I cannot access through a code as db.CountryRegionMapping
Why is this not available in the Db Context. How can I access this entity in the middle of a Many-to-Many relationship.
If you want to access the middle-mapping entities directly, add those as a DbSet on your context, along with the other ones:
public virtual DbSet<CountryRegionMapping> CountryRegionMappings { get; set; }
public virtual DbSet<Country> Countries { get; set; }
public virtual DbSet<Region> Regions { get; set; }
Then you can access them same as you access Countries, or Regions:
ApplicationDbContext db = new ApplicationDbContext();
db.CountryRegionMappings//.SingleOrDefault, etc.

Entity Framework Core cascade on delete,

I try to configure my entities that they will have appropriate relations.
I want to configure it that, when I remove category entity, it should disappear from database with its translation and all of products of this category(including products translations of course).
It works but not fully.
At this moment when I remove category, all products of this category are removed, but translations(category and products) stays.
My entities:
public class Product
{
public Guid Id { get; protected set; }
public Category Category { get; protected set; }
public Translation Translation { get; protected set; }
}
public class Category
{
public Guid Id { get; protected set; }
public IEnumerable<Product> Products { get; protected set; }
public Translation Translation { get; protected set; }
}
public class Translation
{
public Guid Id { get; protected set; }
public string English { get; protected set; }
public string Polish { get; protected set; }
public string Gernam { get; protected set; }
}
DbContext configuration
modelBuilder.Entity<Category>().HasMany(x => x.Products).WithOne(x => x.Category)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Product>().HasOne(x => x.Category).WithMany(x => x.Products)
.OnDelete(DeleteBehavior.Cascade);
It it possible to configure dbcontext that it will works as I want WITHOUT adding Category, and Product properties to Translation entity?

Conventions.Remove<OneToManyCascadeDeleteConvention>() in EF Core?

In my regular .NET Framework application, I was using EF 6.x and was also using some Inheritance, specifically:
PurchaseOrder.cs and SaleOrder.cs both inherit from Order.cs
And in the OnModelCreating() on my context class inheriting from IdentityDbContext, I was doing:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
This used to work, but now I am moving my application to .NET Core 2.0 and I am using EF Core. What achieves the same thing in EF Core? Because right now I am getting the error:
System.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_Order_Business_CustomerId' on table 'Order' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
UPDATE
Here's the code after Ahmar's answer. In my context class, I have:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.HasDefaultSchema("PD");
builder.Entity<Customer>()
.HasMany(c => c.SaleOrders)
.WithOne(e => e.Customer)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<Supplier>()
.HasMany(po => po.PurchaseOrders)
.WithOne(e => e.Supplier)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<PurchaseOrder>()
.HasMany(li => li.LineItems)
.WithOne(po => po.PurchaseOrder)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<SaleOrder>()
.HasMany(li => li.LineItems)
.WithOne(po => po.SaleOrder)
.OnDelete(DeleteBehavior.SetNull);
}
And as far the Entities, they are:
public abstract class Business : IEntity
{
protected Business()
{
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public string Name { get; set; }
public string TaxNumber { get; set; }
public string Description { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public string Email { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public ICollection<Address> Addresses { get; set; } = new List<Address>();
public ICollection<Contact> Contacts { get; set; } = new List<Contact>();
}
[Table("Customers")]
public class Customer : Business
{
public decimal AllowedCredit { get; set; }
public decimal CreditUsed { get; set; }
public int NumberOfDaysAllowedToBeOnMaxedOutCredit { get; set; }
public ICollection<SaleOrder> SaleOrders { get; set; }
}
[Table("Suppliers")]
public class Supplier : Business
{
public ICollection<PurchaseOrder> PurchaseOrders { get; set; }
}
public abstract class Order : IEntity
{
protected Order()
{
Date = DateTime.UtcNow;
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public DateTime Date { get; set; }
public decimal ShippingCost { get; set; }
public Currency ShippingCurrency { get; set; }
public decimal ShippingConversionRate { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public ICollection<Invoice> Invoices { get; set; }
public ICollection<Note> Notes { get; set; }
}
[Table("PurchaseOrders")]
public class PurchaseOrder : Order
{
public int SupplierOrderNumber { get; set; }
public PurchaseOrderStatus Status { get; set; }
public decimal Vat { get; set; }
public decimal ImportDuty { get; set; }
public int SupplierId { get; set; }
public Supplier Supplier { get; set; }
public ICollection<PurchaseOrderLineItem> LineItems { get; set; }
}
[Table("SaleOrders")]
public class SaleOrder : Order
{
public decimal AmountToBePaidOnCredit { get; set; }
public SaleOrderStatus Status { get; set; }
public ICollection<SaleOrderLineItem> LineItems { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
So after doing what Ahmar suggested, I still get the same error when I do update-database.
You need to configure cascade delete behavior on each entity in .Net Core EF.
The Entity Framework Core Fluent API OnDelete method is used to specify the action which should take place on a dependent entity in a relationship when the principal is deleted.
The OnDelete method takes a DeleteBehavior enum as a parameter:
Cascade - dependents should be deleted
Restrict - dependents are
unaffected
SetNull - the foreign key values in dependent rows should
update to NULL
Example:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int? CompanyId { get; set; }
public Company Company { get; set; }
}
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.HasMany(c => c.Employees)
.WithOne(e => e.Company).
.OnDelete(DeleteBehavior.SetNull);
}
When deleting the Company, it will set CompanyId property in Employee table to null.
Get more detail at Configuring One To Many Relationships
PS. Please make sure your all referencing properties should be null able so, EF Core can set them null on delete. like CompanyId in about example.

EF6 doesn't generate Entity Data Model as expected

I have two entities. Event and User. I want to save the creator (= user) of the event as contact in my Event entity. Additionally i want to have several guides (= user as well) associated with my event.
This is (part of) my code:
public class User
{
public int ID { get; set; }
public string LoginName { get; set; }
public string DisplayName { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
public class Event
{
public int ID { get; set; }
public string EventName { get; set; }
public User ContactName { get; set; }
public virtual ICollection<Room> Rooms { get; set; }
public virtual ICollection<User> Users { get; set; } // These are the guides
public virtual ICollection<Equipment> Equipments { get; set; }
[Required]
public EventType Type { get; set; }
}
I'm expecting EF6 (6.1.3) to create 1:n and n:m relationships between User and Event. What it does instead can be seen in the screenshot below:
There is one 1:n relation - as expected. But instead of one n:m relation there is one 1:n and one n:1 relationship. Please help.
For many to many link, You have to write it in the function :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany<Event>(s => s.Events)
.WithMany(c => c.Users)
.Map(cs =>
{
cs.MapLeftKey("UserRefId");
cs.MapRightKey("EventRefId");
cs.ToTable("UserEvent");
});
}
It's imply a new table between the two others
You can follow this guide
There isnt a foreign key relation mapped in Event class:
public class Event
{
public int ID { get; set; }
public string EventName { get; set; }
public int ContactId {get ; set;}
[ForeignKey("ContactId")]
public virtual User Contact { get; set; }
public virtual ICollection<Room> Rooms { get; set; }
public virtual ICollection<User> Users { get; set; } // These are the guides
public virtual ICollection<Equipment> Equipments { get; set; }
[Required]
public EventType Type { get; set; }
}

Categories