I'm trying to create a tables using Code-First in EF.
I created many versions of my code but I put here only one. It almost works, it means this code creates sql but doesn't work properly.
What I want to get:
I have 2 simple tables : Order and Payer.
Also, I want to create a third table based on foreign keys of these 2.
It wont create it that meet the following rules:
Each Order has exactly one Base Payer. (So I don't need complex key in this case. OrderId can exist only one in this table if record is type of (Base)OrderPayer
Each Order can have 0,1 or many (Extra)OrderPayer. ( So in this case i need put into table complex Key (OrderId + PayerId)
here is my last C# code
public class Order
{
[Key]
public int OrderId { get; set; }
public string OrderName { get; set; }
public virtual BaseOrderPayer BaseOrderPayer { get; set; }
public virtual ICollection<ExtraOrderPayer> ExtraOrderPayers { get; set; }
}
public class Payer
{
[Key]
public int PayerId { get; set; }
public string PayerName { get; set; }
}
public abstract class OrderPayer
{
[Column(Order = 0), Key, ForeignKey("Order")]
public int OrderId { get; set; }
public virtual Order Order { get; set; }
}
public class BaseOrderPayer : OrderPayer
{
[ForeignKey("BasePayer")]
public virtual int? BasePayerId { get; set; }
public virtual Payer BasePayer { get; set; }
}
public class ExtraOrderPayer : OrderPayer
{
[Column(Order = 1), Key, ForeignKey("ExtraPayer")]
public virtual int? ExtraPayerId { get; set; }
public virtual Payer ExtraPayer { get; set; }
}
and SQL that was generated by Update-Database -Script
CREATE TABLE [dbo].[OrderPayers] (
[OrderId] [int] NOT NULL,
[ExtraPayerId] [int],
[BasePayerId] [int],
[Discriminator] [nvarchar](128) NOT NULL,
[Order_OrderId] [int],
CONSTRAINT [PK_dbo.OrderPayers] PRIMARY KEY ([OrderId])
)
CREATE TABLE [dbo].[Payers] (
[PayerId] [int] NOT NULL IDENTITY,
[PayerName] [nvarchar](30),
CONSTRAINT [PK_dbo.Payers] PRIMARY KEY ([PayerId])
)
CREATE TABLE [dbo].[Orders] (
[OrderId] [int] NOT NULL IDENTITY,
[OrderName] [nvarchar](30),
[BaseOrderPayer_OrderId] [int],
CONSTRAINT [PK_dbo.Orders] PRIMARY KEY ([OrderId])
)
CREATE INDEX [IX_OrderId] ON [dbo].[OrderPayers]([OrderId])
CREATE INDEX [IX_ExtraPayerId] ON [dbo].[OrderPayers]([ExtraPayerId])
CREATE INDEX [IX_BasePayerId] ON [dbo].[OrderPayers]([BasePayerId])
CREATE INDEX [IX_Order_OrderId] ON [dbo].[OrderPayers]([Order_OrderId])
CREATE INDEX [IX_BaseOrderPayer_OrderId] ON [dbo].[Orders]([BaseOrderPayer_OrderId])
ALTER TABLE [dbo].[OrderPayers] ADD CONSTRAINT [FK_dbo.OrderPayers_dbo.Payers_BasePayerId] FOREIGN KEY ([BasePayerId]) REFERENCES [dbo].[Payers] ([PayerId])
ALTER TABLE [dbo].[OrderPayers] ADD CONSTRAINT [FK_dbo.OrderPayers_dbo.Payers_ExtraPayerId] FOREIGN KEY ([ExtraPayerId]) REFERENCES [dbo].[Payers] ([PayerId])
ALTER TABLE [dbo].[OrderPayers] ADD CONSTRAINT [FK_dbo.OrderPayers_dbo.Orders_Order_OrderId] FOREIGN KEY ([Order_OrderId]) REFERENCES [dbo].[Orders] ([OrderId])
ALTER TABLE [dbo].[OrderPayers] ADD CONSTRAINT [FK_dbo.OrderPayers_dbo.Orders_OrderId] FOREIGN KEY ([OrderId]) REFERENCES [dbo].[Orders] ([OrderId])
ALTER TABLE [dbo].[Orders] ADD CONSTRAINT [FK_dbo.Orders_dbo.OrderPayers_BaseOrderPayer_OrderId] FOREIGN KEY ([BaseOrderPayer_OrderId]) REFERENCES [dbo].[OrderPayers] ([OrderId])
but his SQL it doesn't even work
I can't insert Order because it need OrderPayer and vice versa :)
Is it possible to fix it according to my expectations ?
p.s.
the tables are very simplified, in fact they contain many other fields
I've been learning ADO.NET, and then EF Core. My assignment was to create a database application in C#, first in ADO.NET and then convert that app so it would use Entity Framework instead.
This was my design in ADO.NET, and it seemed to work fine.
CREATE TABLE Categories (
Id INT IDENTITY,
Name NVARCHAR(50) NOT NULL,
CONSTRAINT PK_Categories
PRIMARY KEY (Id)
)
CREATE TABLE CategoryCategories (
ParentCategoryId INT,
ChildCategoryId INT,
CONSTRAINT PK_CategoryCategories
PRIMARY KEY (ParentCategoryId, ChildCategoryId),
CONSTRAINT FK_CategoryCategories_ParentCategoryId
FOREIGN KEY (ParentCategoryId)
REFERENCES Categories (Id),
CONSTRAINT FK_CategoryCategories_ChildCategoryId
FOREIGN KEY (ChildCategoryId)
REFERENCES Categories (Id)
ON DELETE CASCADE
)
CREATE TABLE Products (
Id INT IDENTITY,
ArticleNumber NVARCHAR(50) NOT NULL UNIQUE,
Name NVARCHAR(50) NOT NULL,
Description NVARCHAR(500) NOT NULL,
Price DECIMAL(18,2) NOT NULL,
CONSTRAINT PK_Products
PRIMARY KEY (Id)
);
CREATE TABLE Products_Categories (
ProductId INT,
CategoryId INT NOT NULL,
CONSTRAINT PK_Products_Categories
PRIMARY KEY (ProductId, CategoryId),
CONSTRAINT FK_ProdCats_Prods
FOREIGN KEY (ProductId)
REFERENCES Products (Id),
CONSTRAINT FK_ProdCats_Cats
FOREIGN KEY (CategoryId)
REFERENCES Categories (Id)
ON DELETE CASCADE
);
The connection between Products and Categories - no problem, just an ICollection property in each of the two responding classes. Entity Framework immediately created a CategoryProducts junction table.
When creating a connection between an entity and itself, that's where I ran into a wall. I tried simply copying all the model building code for CategoryProducts generated by the migrations into an OnModelCreating method for CategoryCategories, and inserting the right names of the columns and the tables.
Didn't work, got complaints that CategoryCategories was in shadow mode. I've vacuum-cleaned the internet trying to find a solution, but I can't find any clear instructions. Is it really so much harder to do this in Entity Framework than in regular SQL, or ADO.NET? I thought Entity Framework was supposed to make things easier.
Any suggestions? If you need more information, please let me know.
Edit
Thought I might add the classes, for clarity's sake.
class Category
{
public int Id { get; protected set; }
[Required]
public string Name { get; protected set; }
public ICollection<Product> Products { get; protected set; }
// public ICollection<Category> ChildCategories { get; protected set; }
public Category(string name)
{
Name = name;
Products = new List<Product>();
// ChildCategories = new List<Category>();
}
}
class CategoryCategory
{
// EFC forced me to have an Id. Could not run Add-migr Initial without it
public int Id { get; protected set; }
[Required]
public int ParentCategoryId { get; protected set; }
[Required]
public int ChildCategoryId { get; protected set; }
public CategoryCategory(int parentCategoryId, int childCategoryId)
{
ParentCategoryId = parentCategoryId;
ChildCategoryId = childCategoryId;
}
}
class Product
{
public int Id { get; protected set; }
[Required]
public string ArticleNumber { get; protected set; }
[Required]
// EFC forcing me to have unprotected set
// Without it, I can not update articles :(
public string Name { get; set; }
[Required]
public string Description { get; set; }
[Required]
public decimal Price { get; set; }
public ICollection<Category> Categories { get; protected set; }
public Product(string articleNumber, string name, string description, decimal price)
{
ArticleNumber = articleNumber;
Name = name;
Description = description;
Price = price;
Categories = new List<Category>();
}
public Product(int id, string articleNumber, string name, string description, decimal price)
: this(articleNumber, name, description, price)
{
Id = id;
}
}
With these classes, EF says The entity type 'CategoryCategories' is in shadow state. A valid model requires all entity types to have corresponding CLR type.
If I uncomment the ICollection ChildCategories part in Category, then EF creates the third column, CategoryId in CategoryCategories, which is very undesirable in my opinion. Why should I want the third ID referring to Categories, when I already have two of them?
The connection between Products and Categories - no problem, just an ICollection property in each of the two responding classes. Entity Framework immediately created a CategoryProducts junction table.
If this works, then you are using EF Core 5.0+ which added support for many-to-many relationship with implicit join entity, as can be seen, creating such relationship is quite easy.
But your CategoryCategories represents exactly the same type of relationship, with the only difference that both related ends are one and the same entity.
So how you create such relationship between two entities? You do that by adding 2(!) collection navigation properties in each related entity:
class Product
{
public ICollection<Category> Categories { get; set; }
}
class Category
{
public ICollection<Product> Products { get; set; }
}
(side note: the type of the property setter (public, internal, protected, private) doesn't matter for EF Core).
Which leads us to the answer of question how do you define such relationship between the same entity? Well, exactly the same - by adding 2(!) collection navigation properties in each related entity, which in this case is one and the same:
class Category
{
public ICollection<Category> ParentCategories { get; set; }
public ICollection<Category> ChildCategories { get; set; }
}
And that's it. No need of explicit CategoryCategory entity. EF Core creates automatically something like this
migrationBuilder.CreateTable(
name: "CategoryCategory",
columns: table => new
{
ChildCategoriesId = table.Column<int>(type: "int", nullable: false),
ParentCategoriesId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CategoryCategory", x => new { x.ChildCategoriesId, x.ParentCategoriesId });
table.ForeignKey(
name: "FK_CategoryCategory_Categories_ChildCategoriesId",
column: x => x.ChildCategoriesId,
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_CategoryCategory_Categories_ParentCategoriesId",
column: x => x.ParentCategoriesId,
principalSchema: "SO14",
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
If you don't like the generated table/column names, they are all configurable through fluent API. You can even create and map explicit join entity if you like, and still use the benefits of the so called "skip navigations".
But the two collection navigation properties are the bare minimum, and are the thing which makes the EF Core approach much easier.
In my Code First for this application I have defined a foreign key as nullable. However, when I try to add a record without that key, I still get a Foreign Key constraint error:
The INSERT statement conflicted with the FOREIGN KEY constraint \"FK_dbo.RequestReview_dbo.CustomForm_ReviewFormId\". The conflict occurred in database \"Mkp\", table \"dbo.CustomForm\", column 'CustomFormId'.\r\nThe statement has been terminated.
How should I be defining the relationship so that the key constraint is not enforced?
My code first model defines the field like this:
[Key, ForeignKey("Resource"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid RequestReviewId { get; set; } // My Primary Key
public virtual Resource Resource { get; set; }
public Guid? ReviewFormId { get; set; } // Foreign Key
[ForeignKey("ReviewFormId")]
public CustomForm ReviewForm { get; set; }
(I did try searching, but I'm not sure I searched with the right terminology.)
Edit/Update:
If I remove the ForeignKey tag, I still get migration trying to create a relationship, but this time called ReviewForm_CustomFormId. How can I avoid this?
Updated version of the model:
[Key, ForeignKey("Resource"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid RequestReviewId { get; set; } // My Primary Key
public virtual Resource Resource { get; set; }
public Guid? ReviewFormId { get; set; } // Foreign Key
public CustomForm ReviewForm { get; set; }
Give it a separate primary key field as well as your foreign key:
public int ID { get; set; }
public Guid? ReviewFormId { get; set; }
[ForeignKey("ReviewFormId")]
public CustomForm ReviewForm { get; set; }
Doing this I was able to insert records that have a null ReviewFormId.
One solution i see is that you could use this colunm ReviewFormId as an implicite foreign key. What i mean is : don't define it as a foreign key, just a regular data instead, and only YOU will know that the data inside it represent a foreign key, but don't tell entity that it is a foreign key because GUID just dosen't accept nullable foreign key.
How to implement fluent mapping for the below scenario, I tried but it ends in vain.
I have two table Product and State, Product have column name State which hold StateCode like "WA", "NJ" etc which of string type. So i need to populate the State column into StateCode and the related State object into State property of the product entity.
Below is the classes i am using. I don't want to change the columns of table
public class Product
{
public int ID { get; set; }
public State State { get; set; }
public string StateCode { get; set; }
public string Description { get; set; }
}
public class State
{
public int Id { get; set; }
public string Code{get;set;}
public string Description{get;set;}
}
I tried the below mapping for Product
this.Property(t => t.StateCode).HasColumnName("State");
HasRequired(t => t.State).WithMany().HasForeignKey(t => t.StateCode);
No, currently it's not possible to have a Foreign Key column that refers to non PK in entity framework. Check this feature suggestion.
If you really want to have that feature, you need to have custom Seed that execute.
alter table Products add constraint FK_Products_States foreign key(State) references States(Code)
But you will not be able to populate State object. Putting public State State { get; set; } property will automatically create a Foreign Key column State_ID that refers to States::ID.
Otherwise you need to change the StateCode to be StateId (integer) that refers to State::Id.
The type of ForeignKey and PrimaryKey of referenced table must be the same. So you need to set State class Id property type to string. In EF you can only use foreig keys pointing to primary keys.
My table is as follows:
create table Entities(
EntityId bigint not null identity(1, 1),
Name nvarchar(64) not null,
ParentEntityId bigint null
)
alter table Entities add constraint PK primary key (EntityId)
alter table Entities add constraint FK foreign key (ParentEntityId) references Entities(EntityId)
My model looks like this:
public class Entity
{
[Required]
public virtual long EntityId { get; set; }
[Required]
public virtual string Name { get; set; }
public virtual long? ParentEntityId { get; set; }
public virtual Entity ParentEntity { get; set; }
}
and I'm trying to map the ParentEntity property with fluent api mapping, but I couldn't get this to work. How can I do it?
Thanks in advance!
EDIT: Fixed code discrepancy.
This fluent will work for you:
modelBuilder.Entity<Entity>()
.HasOptional(e => e.ParentEntity)
.WithMany()
.HasForeignKey(e => e.ParentEntityId );
Created this database for me: