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();
}
}
}
Related
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);
}
I have the following tables : Products, Users and ProductApproval. Whenever user create a new product, the ProductApproval will have a new record with the product ID and a null ApprovedByUserId because it is not approve yet. User can have many ProductApproval but Product only can have one ProductApproval.
The structure is like this:
User.cs
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public virtual ICollection<ProductApproval> ProductApprovals { get; set; }
public User()
{
ProductApprovals = new Collection<ProductApproval>();
}
}
Product.cs
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ProductApproval ProductApproval { get; set; }
}
ProductApproval.cs
public class ProductApproval
{
public int Id { get; set; }
public virtual Product Product { get; set; }
public int? ProductId { get; set; }
public virtual User User { get ;set; }
public int? ApprovedByUserId { get; set; }
}
DataContext.cs
modelBuilder.Entity<User>()
.HasMany(u => u.ProductApprovals)
.WithOne(pa => pa.User)
.HasForeignKey(pa => pa.ApprovedByUserId)
.IsRequired(false);
modelBuilder.Entity<Product>()
.HasOne(p => p.ProductApproval)
.WithOne(pa => pa.Product)
.HasForeignKey<ProductApproval>(pa => pa.ProductId);
I already declared the foreign key ApprovedByUserId as nullable, but i still get the following error when i insert a record into productapprovals :
MySqlException: Cannot add or update a child row: a foreign key constraint fails (mysite.productapprovals, CONSTRAINT FK_ProductApprovals_Users_ApprovedByUserId FOREIGN KEY (ApprovedByUserId) REFERENCES users (Id) ON DELETE RESTRICT)
Please advice is there any place i did wrong
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);
How to make a configuration with this schema?
CREATE TABLE Entity
(
Id int identity primary key,
Name nvarchar(30)
)
CREATE TABLE Member
(
ParentEntityId references Entity(Id),
ChildEntityId references Entity(Id)
)
Like so:
Model class:
public class Entity
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Entity> Parents { get; set; }
public ICollection<Entity> Children { get; set; }
}
Mapping:
modelBuilder.Entity<Entity>()
.HasMany(e => e.Parents)
.WithMany(e => e.Children)
.Map(m =>
{
m.ToTable("Member");
m.MapLeftKey("ParentEntityId");
m.MapRightKey("ChildEntityId");
});
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.