Entity Framework and Same Table Foreign Keys - c#

To describe what I want: assume I have a table User in SQL Server, which contains the primary key field, and two fields that refer back to the same table using foreign keys.
CREATE TABLE [User] (
ID INT IDENTITY(1, 1) NOT NULL,
-- other properties omitted for brevity
CreatedByUserID INT NOT NULL,
LastModifiedByUserID INT NOT NULL,
CONSTRAINT PK_User PRIMARY KEY (ID),
CONSTRAINT FK_User_CreatedByUser FOREIGN KEY (CreatedByUserID) REFERENCES User
(ID),
CONSTRAINT FK_User_LastModifiedByUser FOREIGN KEY (LastModifiedByUserID) REFERENCES User (ID),
)
I can setup the "root" user by executing the following SQL:
INSERT [User] (CreatedByUserID, LastModifiedByUserID) VALUES (1, 1)
SQL Server doesn't throw any errors.
Now, if I try to create the same table using Entity Framework Code First, I get mixed results:
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[ForeignKey(nameof(CreatedByUser))]
public int CreatedByUserID { get; set; }
//[ForeignKey(nameof(ModifiedByUser))]
//public int ModifiedByUserID { get; set; }
public virtual User CreatedByUser { get; set; }
//public virtual User ModifiedByUser { get; set; }
}
This works as shown, but when I uncomment the ModifiedByUser* lines then I get a variety of "Multiplicity is not valid in Role" errors.
I've tried converting my annotation-driven class to a fluent-style configuration, but I have no luck when I run Add-Migration: there's always some error that crops up for some combination of configuration methods, and I never seem able to specify the CreatedByUserID or LastModifiedByUserID column names.
user.HasRequired(x => x.CreatedBy);
//.WithRequiredPrincipal();
user.HasRequired(x => x.LastModifiedBy);
//.WithRequiredPrincipal();
Am I trying to do something that's forbidden by Entity Framework? Or have I missed a line or two of configuration? How can I use EF to recreate the table structure shown at the top of this post?
EDIT:
The same pattern works just fine for a number of other entities in the model: for example a WorkItem can have both CreatedByUser and ModifiedByUser; the problem only seems to arise when the foreign keys refer back to an entity of the same type as their enclosing type.

Related

EF Core 6 - How to add AUTO_INCREMENT to a column used by other foreign keys

I have a EF Core Code-First table like:
public class Entity
{
public byte ID { get; set; }
public string Name { get; set; }
...
}
And many other tables with:
public class Others
{
...
public byte EntityID { get; set; }
...
[ForeignKey("EntityID")]
public Entity Entity { get; set; }
}
I just know that EF Core 6 will not set AUTO_INCREMENT if you use byte as the primary key, so I'm trying to add it with:
public class Entity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public byte ID { get; set; }
public string Name { get; set; }
...
}
The migration cs is:
migrationBuilder.AlterColumn<byte>(
name: "ID",
table: "Entity",
type: "tinyint unsigned",
nullable: false,
oldClrType: typeof(byte),
oldType: "tinyint unsigned")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
But when I try to migrate, I get this error:
MySqlConnector.MySqlException: Cannot change column 'ID': used in a foreign key constraint 'FK_Others_Entity_EntityID' of table 'Others'
I just want to add AUTO_INCREMENT so the foreign keys should be safe. How can I add it, or is there a way for EF Core 6 migration to turn off foreign key checks only during this migration?
As #Kiran Joshi's comment I can use raw SQL to pause foreign key check and the MySQL version is:
migrationBuilder.Sql("SET FOREIGN_KEY_CHECKS=0");
Put this and migrationBuilder.Sql("SET FOREIGN_KEY_CHECKS=1"); (maybe not required) in Up and Down does the work.
Your best bet may be to remove the foreign key constraint, modify the column, and then re-add the foreign key constraint. This method is described in detail in this answer.
Is this column the primary key of the table? Does it allow null values?
My recommendation is that you consider performing this change outside of an EF migration, even if you update your migrations to implement the change on future clean DB deployments. Dropping constraints to manipulate the schema can have drastic unintended consequences. Backups are your friend.

Create a one to zero-or-one relation on a non-key field

We use Entity Framework 6 with CodeFirst and an Oracle.ManagedDataAccess.
I just created a 1 to 0..1 (one to zero or one) relation between two tables, and it works like a charm. But when adding a second relation, I got into trouble, because the original primary key column was demoted to just a data column, and a surrogate (sequence) primary key column was added. The foreign key constraint is still on the old field.
Code:
public class Node
{
[Key, Column("ID"), Required]
public int Id { get; set; }
[Column("POINT_CODE"), Required, StringLength(10)]
public string PointCode { get; set; }
// ...columns left out...
[ForeignKey("PointCode"), Required]
public NetworkPoint PointCodeFk { get; set; }
}
public class Point
{
[Key, Column("POINT_CODE"), Required, StringLength(10)]
public string PointCode { get; set; }
// ...columns left out...
[ForeignKey("PointCode")]
public Node NodeFk { get; set; }
}
public class MyDbContext : EntityContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Point>()
.HasOptional(m => m.NodeFk)
.WithRequired(o => o.PointCodeFk);
}
// ...stuff left out...
}
The difference with the working example is, the property Node.PointCode has the attribute Key, and there is no Node.Id column.
When running this example, I get the error message:
Multiplicity is not valid in Role 'Point_NodeFk_Target' in relationship 'Point_NodeFk'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be '*'.
So I guess EntityFramework now thinks it should be an 1:N relation.
How can this be solved? I can't change the database (although I would like to very much).
Entity Framework requires a primary key on each table. You need to add a primary without which EF will complain. So looking at your code Point class require ID, Id ,PointId or PointID property so EF will know it has a primary key. Any of the conventions I showed here as a class property will be acceptable to EF.

Entity Framework inherited one to zero to one relationship configuration with fluent API

I can't find an answer by searching through SO questions and by Googling around.
I have a very simple structure:
public class Employee
{
property int Id { get; set; }
property int Name { get; set; }
}
public class Developer : Employee
{
property string Level { get; set; }
}
and I have two tables for this domain model:
create table Employees
(
Id int not null primary key identity(1, 1),
Name nvarchar(100) not null
)
create table Developers
(
Id int not null primary key,
Level nvarchar(100)
)
alter table Developers
add constraint FK_Developers_Employees
foreign key ([Id]) references Employees ([Id])
This C# model and SQL Server database schema can not be changed. The scenario is:
First add an employee (One-employee)
This employee is not a developer yet (One-employee-to-Zero-developer)
Then promote that employee to a developer (One-employee-to-One-developer)
How should I configure my Context class? I would also appreciate sample code for this scenario.
You can not do this as simple as it seems. You have to delete the first entity from context, clone it in a new object -Developer here-, and add the new one to the context.
var emp = _context.Employees.Find(id);
_context.Delete(emp);
var dev = new Developer(/* clone from emp */);
_context.Developers.Add(dev);
_context.SaveChanges();
Which will change the Id of employee. However, if you want to avoid this (changing Id), you have to use raw SQL - e.g. calling a stored procedure - to add a row to Developers and reload the context.
Another option is to set Id column by custom, which as you mentioned in Q, it's not possible to change the scheme. So ignore it.

What's the difference when I create Code First relation setting only a collection

I am using Entity Framework 5 with Code First. I update my model(entities) while I'm writing the business logic which leads to some problems. Since now when I wanted to create 1 : N relationship I was using this approach:
Entity-1
{
public int Entity-1ID { get; set; }
public virtual ICollection<Entity-N> Entity-Ns { get; set; }
}
and
Entity-N
{
public int Entity-NID { get; set; }
public int Entity-1ID { get; set; }
public virtual Entity-1 Entity-1 { get; set; }
}
bur recently I faced the problem with the need of null Foreign Keys (I needed to add more relations to some entities) and since I am already using GenericRepository where all types are value types (no nullables) and also have some code written based on that I decided that it's too late to change all this.
Since I already have Foreign Key for some entity and I need to relate that entity with a new one I have faced a problem.
My solution is - when I have a new entity I only add collection from the existing entity to the new one (or vice-verca). What bothers me is - what kind of problems this may cause if I use it.
Right now I have entity Page where
Page
{
public virtual ICollection<SomeEntity> SomeEntities { get; set; }
}
and as expected in SomeEntity I don't have a FK for Page but when I look at the Microsoft SQL Management Studio I see that the SomeEntities table has a column called Page_PageID.
Can someone explain me what exactly happens when I make a relation like this. Why even though I have this column Page_PageID which practically acts as FK I can't use it from my code and why if I explicitly define public int PageID { get; set; } in SomeEntitiy and I try to change int to int? I get all kinds of errors for trying to use null but with this auto-created column Page_PageID there's no problem to have records with null values for it.
Your question:Can someone explain me what exactly happens when I make a relation like this?
Page
{
public virtual ICollection<SomeEntity> SomeEntities { get; set; }
}
In Code First,if there is a collection property between two entities,Entity Framework will create a one-to-many relationship.That means the entity has collection property is the principal end of the relationship,the other entity is the dependent end.And,in the table of the dependent entity,Entity Framework will generate a foreign key to the principal table.
So,The collection property "SomeEntities" of entity Page will cause the Entity Framework to generate a foreign key in the table of entity "SomeEntity".
There are three scenarios that Code Frist will treat as a one-to-manay relationship between to entities.
1.There is a reference navigation property in one entity.
2.There is a collection navigation property in one entity(Your Page entity belong to this).
3.There is a reference navigation property in one entity,and a collection navigation property in the other entity.
Your question:Why even though I have this column Page_PageID which practically acts as FK I can't use it from my code .
If you want to use the foreign key,you should define a foreign key property in entity "SomeEntity".By default,Entity Framework generate a foreign key with the below patterns:
[Target Type Key Name],[Target Type Name] + [Target Type Key Name],or [Navigation Property Name] + [Target Type Key Name].that is why your foreign key named "Page_PageID".You can use the annotation "ForeignKey".
Your question: why if I explicitly define public int PageID { get; set; } in SomeEntitiy and I try to change int to int? I get all kinds of errors for trying to use null but with this auto-created column Page_PageID there's no problem to have records with null values for it..
By default,if the primary key is value types,the foreign key to it will be not null.And that means the relationship is required.
I can not see the key of your Page entity.If it's type is int,the auto-created column Page_PageId(foreign key to Page) will be not null too.Did you show your whole Page entity?
If you did not define a foreign key(like public int PageId) in SomeEntity,code first will generate a foreign key named with pattern [Target Type Name]_[Target Type Key Name],that is Page_PageId.And in this case(there is no foreign key property in the dependent entity),the foreign key generated by Entity Framework in silence will be null.
But if you explicitly define a foreign key property,like public int PageId,the nullability of the foreign key in the database will be determined by the type of the foreign key property(value types is not null).
So,like your question,when you with the auto-created FK column Page_PageID,the FK is null,you can insert leave it null.When you explicitly defined a foreign key property,public int PageID { get; set; },the FK PageId will be not null,because PageId is integer.
Some more,you can control the nullability of FK int the database by specifying the PageId as int?.Again,By convention, Code First is using the nullability of the foreign key
property in your class to determine if the relationship is required or optional.

Mapping one entity to multiple tables

I have a question related to the Fluent NHibernate. I can not describe the schema mapping one entity to multiple tables. There is the following structure of the database:
Create table CeTypes (Id int not null PRIMARY KEY, Name nvarchar(100) not null)
Create table CeValues (Id int not null PRIMARY KEY, Name nvarchar(100) not null)
Create table Ces (Id int not null PRIMARY KEY, CeType_id int not null FOREIGN KEY REFERENCES CeTypes(Id), CeValue_id int not null FOREIGN KEY REFERENCES CeTypes(Id))
there is the following entity:
public class Ce
{
public virtual int Id { get; set; }
public virtual string Type { get; set; }
public virtual string Value { get; set; }
}
CeType, CeValue entities in the domain and there is no. I do not know how to describe the mapping Ce entity.
Tried to describe:
public class CeMap : ClassMap<Ce>
{
public CeMap()
{
Table("Ces");
Id(c => c.Id);
Join("CeTypes", m => m.Map(ce => ce.Type).Column("Name"));
Join("CeValues", m => m.Map(ce => ce.Value).Column("Name"));
}
}
But with such a scheme CeType, CeValue tables should have a field Ce_id. How can I describe scheme mapping under the current structure of the database?
I tried doing the same thing when I first started using nHibernate and couldn't find a way to do it. I actually don't believe that you can map multiple tables to a single object. Usually you would have one entity per table. Each entity will be mapped to their table and would have references/hasmany links between them.
You'll probably find that having one entity per table is better in the long run as well because it allows for simpler mapping to the database.

Categories