Entity Framework - Parent Child relational table - entity-framework-core

I have an Organization entity table
public class Organization
{
public int OrganizationId { get; set; }
public string Name { get; set; }
public int OrganizationTypeId { get; set; }
public OrganizationType OrganizationType { get; set; }
public ICollection<OrganizationRelation> OrganizationRelations { get; set; }
}
then I have my relational table with a self-referencing parent column
public class OrganizationRelation
{
public int OrganizationRelationId { get; set; }
public int OrganizationId { get; set; }
public int? ParentOrganizationId { get; set; }
public Organization Organization { get; set; }
public Organization ParentOrganization { get; set; }
}
public class OrganizationRelationModelConfiguration : IEntityTypeConfiguration<OrganizationRelation>
{
public void Configure(EntityTypeBuilder<OrganizationRelation> builder)
{
builder.HasKey(c => c.OrganizationRelationId);
builder.Property(c => c.OrganizationRelationId).ValueGeneratedOnAdd();
builder.Property(c => c.OrganizationId).IsRequired();
builder.Property(c => c.ParentOrganizationId);
builder.HasOne(r => r.Organization).WithMany().HasForeignKey(fk => fk.OrganizationId);
builder.HasOne(r => r.ParentOrganization).WithMany().HasForeignKey(fk => fk.ParentOrganizationId);
builder.ToTable("OrganizationRelation", "dbo");
}
}
When I deploy my db with migration, I see this table created:
CREATE TABLE [mdo].[OrganizationRelation](
[OrganizationRelationId] [int] IDENTITY(1,1) NOT NULL,
[OrganizationId] [int] NOT NULL,
[ParentOrganizationId] [int] NULL,
[OrganizationId1] [int] NULL,
CONSTRAINT [PK_OrganizationRelation] PRIMARY KEY CLUSTERED
(
[OrganizationRelationId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
I'm using EF 5.0
I don't get it why is creating the column OrganizationId1

You didn't map OrganizationRelation.ParentOrganization to Organization.OrganizationRelations, so EF is adding an additional FK to OrganizationRelation for the second navigation property.
But that model seems overly complex. Why not just
public class Organization
{
public int OrganizationId { get; set; }
public string Name { get; set; }
public int OrganizationTypeId { get; set; }
public int? ParentOrganizationId { get; set; }
public Organization ParentOrganization { get; set; }
public ICollection<Organization> ChildOrganizations{ get; } = new HashSet<Organization>();
}
which creates
CREATE TABLE [Organization] (
[OrganizationId] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
[OrganizationTypeId] int NOT NULL,
[ParentOrganizationId] int NULL,
CONSTRAINT [PK_Organization] PRIMARY KEY ([OrganizationId]),
CONSTRAINT [FK_Organization_Organization_ParentOrganizationId] FOREIGN KEY ([ParentOrganizationId]) REFERENCES [Organization] ([OrganizationId]) ON DELETE NO ACTION
);
CREATE INDEX [IX_Organization_ParentOrganizationId] ON [Organization] ([ParentOrganizationId]);
?
To factor a foreign key out into a separate table, you just introduce a 1-1 dependent table, where the dependent table's FK and PK are the same as the main table's PK.
So to do the same thing with a separate entity would look like this:
public class Organization
{
public int OrganizationId { get; set; }
public string Name { get; set; }
public int OrganizationTypeId { get; set; }
public OrganizationRelation OrganizationRelation { get; set; }
public ICollection<OrganizationRelation> ChildOrganizations { get; } = new HashSet<OrganizationRelation>();
}
public class OrganizationRelation
{
public int OrganizationId { get; set; }
public int ParentOrganizationId { get; set; }
public Organization Organization { get; set; }
public Organization ParentOrganization { get; set; }
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<OrganizationRelation>().HasKey(c => c.OrganizationId);
builder.Entity<OrganizationRelation>()
.HasOne(r => r.Organization)
.WithOne(o => o.OrganizationRelation)
.HasForeignKey(nameof(OrganizationRelation), nameof(OrganizationRelation.OrganizationId));
builder.Entity<OrganizationRelation>()
.HasOne(r => r.ParentOrganization)
.WithMany(o => o.ChildOrganizations)
.HasForeignKey(r => r.ParentOrganizationId)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<OrganizationRelation>().ToTable("OrganizationRelation", "dbo");
base.OnModelCreating(builder);
}

Related

Create composite in multiple foreign key using entity framework

In multi tenant application, we can force the ID that is in another tenant in dropdown for example
My models is:
public class Tenant
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Customer
{
public int Id { get; set; }
public int TenantId { get; set; }
public string Name { get; set;}
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public int TenantId { get; set; }
public float Price { get; set; }
}
this code should generate a table similar to this:
CREATE TABLE tenant (
id INT NOT NULL,
nome VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE customer (
id INT NOT NULL,
id_tenant INT NOT NULL,
nome VARCHAR(255) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (id_tenant) REFERENCES tenant(id),
-- pra ter a FK composta, tem que ter chave composta na tabela de origem
UNIQUE (Id,id_tenant)
);
CREATE TABLE [order] (
id INT NOT NULL,
id_customer INT NOT NULL,
id_tenant INT NOT NULL,
nome VARCHAR(255) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (id_customer, id_tenant) REFERENCES customer (id, id_tenant),
FOREIGN KEY (id_tenant) REFERENCES tenant(id)
);
But, how create composite using FLUENT API?
EDIT:
I want to ensure that the CustomerID passed to the Order.cs has the same TenantId
That is, Customer and Order must have the same TenantId
I want to ensure that the CustomerID passed to the Order.cs has the same TenantId
To declare a compound foreign key in the Fludent API is just like declaring a compound primary key. Use an anonymous type.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.Data.SqlClient;
using System.Linq;
namespace ConsoleApp8
{
public class Tenant
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Customer
{
public int Id { get; set; }
public int TenantId { get; set; }
public virtual Tenant Tenant {get;set;}
public string Name { get; set; }
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public int TenantId { get; set; }
public Customer Customer { get; set; }
public Tenant Tenant { get; set; }
public decimal Price { get; set; }
}
class Db : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Tenant>().HasKey(e => e.Id);
modelBuilder.Entity<Customer>().HasKey(e => new { e.TenantId, e.Id });
modelBuilder.Entity<Order>().HasKey(e => new { e.TenantId, e.CustomerId, e.Id });
modelBuilder.Entity<Customer>()
.HasRequired<Tenant>(e => e.Tenant)
.WithMany()
.HasForeignKey(e => e.TenantId);
modelBuilder.Entity<Order>()
.HasRequired<Customer>(e => e.Customer)
.WithMany()
.HasForeignKey(e => new { e.TenantId, e.CustomerId });
modelBuilder.Entity<Order>()
.HasRequired<Tenant>(e => e.Tenant)
.WithMany()
.HasForeignKey(e => e.TenantId)
.WillCascadeOnDelete(false);
}
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<Db>());
using (var db = new Db())
{
db.Database.Log = m => Console.WriteLine(m);
db.Database.Initialize(true);
}
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
}
}
}

Many-to-many relationship on the same table using a junction table and a primary key in EF

I have the following tables:
Sub_Option: Sub_Option_ID as PK, Name
Sub_Option_To_Sub_Option: Sub_Option_To_Sub_Option_ID as PK, Sub_Option_ID_Primary, Sub_Option_ID_Secondary
I would like to be able to access all the secondary sub options associated with the primary sub option via EF and vice-versa. Directly using .Map won't work as the junction table Sub_Option_To_Sub_Option has a primary key.
public class Sub_Option
{
public int Sub_Option_ID { get; set; }
public string Name { get; set; }
}
corresponding to Table
CREATE TABLE Sub_Option(
Sub_Option_ID int,
Name varchar(255)
);
and Table
CREATE TABLE Sub_Option_To_Sub_Option(
Sub_Option_To_Sub_Option int PK,
Sub_Option_ID_Primary int,
Sub_Option_ID_Secondary int
);
This should work i think:
public class OptionToOption
{
[Key]
public int ID { get; set; }
[ForeignKey("PrimaryOption")]
public int PrimaryID { get; set; }
[ForeignKey("SecondaryOption")]
public int SecondaryID { get; set; }
public virtual Option PrimaryOption { get; set; }
public virtual Option SecondaryOption { get; set; }
}
public class Option
{
public Option()
{
OptionToOption = new HashSet<OptionToOption>();
}
[Key]
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<OptionToOption> OptionToOption { get; set; }
}
And in fluent api map like this (don't even think it's necessary to do this though):
modelBuilder.Entity<Option>()
.HasMany(e => e.OptionToOption)
.WithRequired(e => e.PrimaryOption)
.HasForeignKey(e => e.PrimaryID);
modelBuilder.Entity<Option>()
.HasMany(e => e.OptionToOption)
.WithRequired(e => e.SecondaryOption)
.HasForeignKey(e => e.SecondaryID);

"Introducing FOREIGN KEY constraint"

Using: VS 2013, Entity Framework Code First, ASP.NET Web Project MVC
I have 2 models, in one need 2 FK for the same table:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
public class B
{
public int Id { get; set; }
public int Id1 { get; set; }
[ForeignKey("Id1")]
public virtual A A1 { get; set; }
public int Id2 { get; set; }
[ForeignKey("Id2")]
public virtual A A2 { get; set; }
}
After enable-migration and Add-Migration Test, when I run Update-Database, I get this message:
Introducing FOREIGN KEY constraint 'FK_dbo.B_dbo.A_Id2' on table 'B' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
use this code
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
public class B
{
public int Id { get; set; }
public int Id1 { get; set; }
[ForeignKey("Id1")]
public virtual A A1 { get; set; }
public int Id2 { get; set; }
[ForeignKey("Id2")]
public virtual A A2 { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<B>()
.HasRequired(e => e.A1)
.WithMany()
.HasForeignKey(c => c.Id1)
.WillCascadeOnDelete(false)
.HasRequired(e => e.A2)
.WithMany()
.HasForeignKey(c => c.Id2)
.WillCascadeOnDelete(false)
;
}
alse you could use inverseProperty attribute.

Entity Framework Inheritance share database fields

So, I am using Entity Framework Code First. This is the model:
public class StockMove
{
public int Id { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
public class StockMoveOut : StockMove
{
public int CustomerId { get; set; }
public virtual Customer Customer { get; set; }
public int OriginLocalId { get; set; }
public virtual Local OriginLocal { get; set; }
}
public class StockMoveIn : StockMove
{
public int SupplierId { get; set; }
public virtual Supplier Supplier { get; set; }
public int DestinationLocalId { get; set; }
public virtual Local DestinationLocal { get; set; }
}
public class StockMoveTransfer : StockMove
{
public int OriginLocalId { get; set; } //should be the same for StockMoveOut
public virtual Local OriginLocal { get; set; }
public int DestinationLocalId { get; set; } //should be the same for StockMoveIn
public virtual Local DestinationLocal { get; set; }
}
public class DataContext : DbContext
{
public DbSet<StockMove> StockMoves { get; set; }
}
I need to refer the fields (OriginLocalId, DestinationLocalId) in StockMoveTransfer to be the same for the StockMoveIn and StockMoveOut, but Entity Framework expect to new fields.
The SQL DDL generated is:
create table [dbo].[StockMoves] (
[Id] [int] not null identity,
[ProductId] [int] not null,
[CustomerId] [int] null,
[OriginLocalId] [int] null,
[SupplierId] [int] null,
[DestinationLocalId] [int] null,
[OriginLocalId1] [int] null, -- should not exists
[DestinationLocalId1] [int] null, -- should not exists
[Discriminator] [nvarchar](128) not null,
primary key ([Id])
);
Well, how can I configure the Entity Framework to point for the existing fields?

Entity Framework's DbModel: How to map a one to many relationship using a connection table?

I'm trying to map via DbModel this relationship present on the database.
CREATE TABLE core.Institutes
(
ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(128) NOT NULL,
OldID INT NULL
)
GO
CREATE TABLE core.InstitutePlaces
(
FKInstituteID INT NOT NULL PRIMARY KEY REFERENCES core.Institutes(ID),
FKPlaceID INT NOT NULL REFERENCES core.Places(ID)
)
GO
CREATE TABLE core.Places
(
ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(128) NOT NULL,
FKParentID INT NULL REFERENCES core.Places(ID),
OldID INT NULL
)
GO
on this model
public class Place
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public Place Parent { get; set; }
}
public class Institute
{
public int Id { get; set; }
public string Name { get; set; }
public Place Place { get; set; }
}
we're using something like this to do the mapping
modelBuilder.Entity<Institutes.Institute>().HasOptional(i => i.Place);
but it doesn't work :(
This scenario is perfectly managed by the EDML file, so the problem is only about the mapping.
Something like this will give you (almost) the desired schema with one caveat: Code First does not create a 1:1 relationship in entity splitting scenarios which your desired schema (creating a 1:* association using a join table) is a special case of it.
public class Place
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public Place Parent { get; set; }
}
public class Institute
{
[DatabaseGenerated(DatabaseGenerationOption.None)]
public int Id { get; set; }
public string Name { get; set; }
public int? PlaceId { get; set; }
public Place Place { get; set; }
}
public class Context : DbContext
{
public DbSet<Place> Places { get; set; }
public DbSet<Institute> Institutes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Institute>().Map(mc =>
{
mc.Properties(p => new { p.Id, p.Name });
mc.ToTable("Institutes");
})
.Map(mc =>
{
mc.Properties(p => new { p.Id, p.PlaceId });
mc.ToTable("InstitutePlaces");
});
modelBuilder.Entity<Place>()
.HasOptional(p => p.Parent)
.WithMany()
.HasForeignKey(p => p.ParentId);
}
}
I had to switch off identity generation due to a bug that I explained here.