EF Core 2 1-1 relationship unexpected behaviour - entity-framework-migrations

I have a typical Parent : Child relationship. The classes are as follows:
public class Parent
{
public Guid Id { get; set; }
public string Name { get; set; }
public Child Child { get; set; }
}
and
public class Child
{
public Guid Id { get; set; }
public string Name { get; set; }
public Parent Parent { get; set; }
}
The model is quite simple too:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Child>().HasKey(p => p.Id);
modelBuilder.Entity<Child>().Property(p => p.Name).HasMaxLength(256);
modelBuilder.Entity<Child>().HasOne(p => p.Parent).WithOne(p => p.Child).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Parent>().HasKey(p => p.Id);
modelBuilder.Entity<Parent>().Property(p => p.Name).HasMaxLength(256).IsRequired();
}
The migration looks like this:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Parent",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Parent", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Child",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
ParentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Child", x => x.Id);
table.ForeignKey(
name: "FK_Child_Parent_ParentId",
column: x => x.ParentId,
principalTable: "Parent",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Child_ParentId",
table: "Child",
column: "ParentId",
unique: true);
}
which is pretty much what was expected. The FK is declared in the "Child" table.
If I add a new Child, however, things change quite unexpectedly. Here's the second child and the modified Parent:
public class Child2
{
public Guid Id { get; set; }
public string Name { get; set; }
public /*virtual */Parent Parent { get; set; }
}
and
public class Parent
{
public Guid Id { get; set; }
public string Name { get; set; }
public Child Child { get; set; }
public Child2 Child2 { get; set; }
}
Again, nothing special about the model:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Child>().HasKey(p => p.Id);
modelBuilder.Entity<Child>().Property(p => p.Name).HasMaxLength(256);
modelBuilder.Entity<Child>().HasOne(p => p.Parent).WithOne(p => p.Child).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Child2>().HasKey(p => p.Id);
modelBuilder.Entity<Child2>().Property(p => p.Name).HasMaxLength(256);
modelBuilder.Entity<Child2>().HasOne(p => p.Parent).WithOne(p => p.Child2).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Parent>().HasKey(p => p.Id);
modelBuilder.Entity<Parent>().Property(p => p.Name).HasMaxLength(256).IsRequired();
}
And now the surprise. The new migration:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Child2",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Child2", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Parent",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Child2Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Parent", x => x.Id);
table.ForeignKey(
name: "FK_Parent_Child2_Child2Id",
column: x => x.Child2Id,
principalTable: "Child2",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Child",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
ParentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Child", x => x.Id);
table.ForeignKey(
name: "FK_Child_Parent_ParentId",
column: x => x.ParentId,
principalTable: "Parent",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Child_ParentId",
table: "Child",
column: "ParentId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Parent_Child2Id",
table: "Parent",
column: "Child2Id",
unique: true);
}
One can easily spot the
Child2Id
FK in Parent table while there's no such key for the Child table. Also, there's another surprise - the missing
ParentId
FK in the Child2 table.
Things appear to be quite asymmetric and look much more like a chain of 1:1 relations than a parent with two children.
If, however, I add another child (code not pasted to avoid bloating) the "chain" is broken - the third child looks like the second, etc.
My questions are:
Why such asymmetry provided code is pretty much "copy-paste"?
Whatever I do (change Child2 code location in OnModelCreating() and/or swap Child and Child2 property order in Parent), Child2 is generated the same way, that is FK in Parent. So, what makes EF choose Child2 over Child for such generation? If there are three or more children Child is only generated as expected, all the rest are like Child2. What makes Child so "special" if I may "reverse" my surprise?
Any ideas?
Thanks.
PS: No explicit FKs are allowed in code!
EDIT: Commented out virtual in Child2 to avoid asymmetry. In fact it is unrelated to the problem.

The answer is basically contained in the following Note inside the Other Relationship Patterns - One-to-one section of the Relationships documentation:
EF will choose one of the entities to be the dependent based on its ability to detect a foreign key property. If the wrong entity is chosen as the dependent, you can use the Fluent API to correct this.
EF has no problem with one-to-many relationships because the many side is always the dependent. Also it has no problem with one-to-one relationships having explicit FK because the side with FK is the dependent.
But for one-to-one relationships without explicit FK like in your case, it's unclear which side the dependent, hence the choice is sort of random (it should probably being an exception) and thus unreliable.
As a rule of thumb, always explicitly specify the FK (thus the dependent entity) with HasForeignKey fluent API using the corresponding overload for explicit/shadow property:
modelBuilder.Entity<Child>()
.HasOne(p => p.Parent)
.WithOne(p => p.Child)
.HasForeignKey<Child>("ParentId")
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Child2>()
.HasOne(p => p.Parent)
.WithOne(p => p.Child2)
.HasForeignKey<Child2>("ParentId")
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
The generated migration is:
migrationBuilder.CreateTable(
name: "Parent",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Parent", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Child",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
ParentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Child", x => x.Id);
table.ForeignKey(
name: "FK_Child_Parent_ParentId",
column: x => x.ParentId,
principalTable: "Parent",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Child2",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
ParentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Child2", x => x.Id);
table.ForeignKey(
name: "FK_Child2_Parent_ParentId",
column: x => x.ParentId,
principalTable: "Parent",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});

Related

Cascade delete in Typeorm for one-to-one

I read and searched a lot but did not find any solution to my problem. I read this, this, ...
My database is MySQL. There is no problem with one-to-many and many-to-many. in one-to-one relation
// Student.ts(Parent)
#Entity({ name: "student" })
export class StudentEntity {
#PrimaryGeneratedColumn()
public id: number;
#Column({ nullable: true })
public name: string;
#OneToOne(() => StudentProfile, (profile) => profile.student, { onDelete: "CASCADE", cascade: ['insert', 'update'] })
public profile: StudentProfile
}
// Profile.ts
#Entity({ name: "profile" })
export class StudentProfile {
#PrimaryGeneratedColumn()
id: number
#Column({ nullable: true })
username: string
#OneToOne(() => StudentEntity, (student) => student.profile, { onDelete: "CASCADE" })
public student: StudentEntity
}
Now, With The following code I want to remove the student and their profile:
const student = await this._studentRepository.findOne({ where: { id: 4 } })
await this._studentRepository.delete(student)
The code above does not work. there is another way: I can remove the student and profile individually, I do not want to do this.
any help would be appreciated. Thanks in advance.
As the stackoverflow you included, mentions: You have to delete referencing side to take cascade deletion to take in effect.
I guess you've to delete like:
const student = await this._studentRepository.findOne({ where: { id: 4 } })
const profile = await this._profileRepository.findOne({ where: { id: student.profile } }) // if you don't have an eager relationship
await this._profileRepository.delete(profile)
or in case of an eager relationship between student and profile:
const student = await this._studentRepository.findOne({ where: { id: 4 } })
await this._profileRepository.delete(student.profile) // if you've an eager relationship

Typeorm: joined relation data is not showing in final result

I have a moving table which is in a one-to-many relation with the moving_activities table. moving_activities table records all the activities happening on the moving table.
I'm trying to fetch all data from moving along with the latest activity from the moving_activites table.
My query:
this.repository
.createQueryBuilder('moving')
.leftJoinAndSelect(
(subQuery) => {
return subQuery
.from(MovingActivityEntity, 'ma')
.where('ma.moving_id = :movingId', { movingId: id })
.orderBy('ma.created_at', 'DESC')
.limit(1);
},
'movingActivities',
'movingActivities.moving_id = moving.id'
)
.where('moving.id = :id', { id })
.getOneOrFail();
MovingEnity
#Entity('movings')
export class MovingEntity {
#PrimaryColumn()
readonly id: string = ulid();
#CreateDateColumn()
readonly createdAt: Date;
#UpdateDateColumn()
readonly updatedAt: Date;
#Column({ nullable: true })
historyId?: string;
#Column({ nullable: true })
postId?: string;
// #OneToMany((type) => MovingActivityEntity, (movingActivity) => movingActivity.moving)
movingActivities: MovingActivityEntity;
}
MovingActivityEntity
#Entity('moving_activities')
export class MovingActivityEntity {
#PrimaryColumn()
readonly id: string = ulid();
#CreateDateColumn()
readonly createdAt: Date;
#Column({ nullable: false })
movingId: string;
#ManyToOne((_type) => MovingEntity)
#JoinColumn({ name: 'moving_id' })
moving?: MovingEntity;
#Column({
type: 'enum',
enum: activityTypes,
default: 'undefined',
})
latestActivityType: ActivityType = 'undefined';
constructor(movingId: string) {
this.movingId = movingId;
}
}
when I run the query I only get the movings table data. there is no moving_activities object.
MovingEntity {
id: '01FVV2VPD7SZ87GWR4PQ840PRX',
createdAt: 2022-02-14T03:01:47.049Z,
updatedAt: 2022-04-22T07:20:05.000Z,
historyId: null,
postId: '01FVV2VPA8M12TV39W4861EVZV',
}
I have also tried to map the data in movingActivities with leftJoinAndMapOne with no luck.
If I use execute() in place of getOneOrFail() I get the intended result but execute() return the raw data.
Custom select work only with raw results.
You can use also getRawOne().
The method leftJoinAndSelect say:
LEFT JOINs given subquery and add all selection properties to SELECT..
so not add the object but all properties to the selection. These added properties are not in the model that you are getting with getOne..., but you can return this data with getRawOne().
Maybe leftJoinAndMapOne can be what you looking for.
Try this query:
this.repository
.createQueryBuilder('moving')
.leftJoinAndMapOne('moving.movingActivities',
(subQuery) => {
return subQuery.select()
.from(MovingActivityEntity, 'ma')
.where('ma.moving_id = :movingId', { movingId: id })
.orderBy('ma.created_at', 'DESC')
.limit(1);
},
'movingActivities',
'movingActivities.moving_id = moving.id'
)
.where('moving.id = :id', { id })
.getOneOrFail();
There is also an old question for the same problem: 61275599
Edit:
Checked in typeorm source code, actually in the current version if we use subquery the mapping is skipped. So the only way to get full results from join with sub query is using getRaw methods.

ASP.net MVC 5 EF6 Error While updating the database

I am quite new to Asp.net MVC 5 EF6. I am developing the application for Contoso Unversity which is provided by Microsoft on asp.net website. In chapter no.11 Implementing the Inheritance after adding the inheritance and adding the migration through migration command it worked but when i tried to apply the update-database command to the PMC, I faced this error:
Error Number:15248,State:1,Class:11 Either the parameter #objname is
ambiguous or the claimed #objtype (OBJECT) is wrong.
This is the code of My /inheritance migration class.
please guide me to a fix.
namespace ContosoUniversity.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class Inheritance : DbMigration
{
public override void Up()
{
// Drop foreign keys and indexes that point to tables we're going to drop.
DropForeignKey("dbo.Enrollment", "StudentID", "dbo.Student");
DropIndex("dbo.Enrollment", new[] { "StudentID" });
RenameTable(name: "dbo.Instructor", newName: "Person");
AddColumn("dbo.Person", "EnrollmentDate", c => c.DateTime());
AddColumn("dbo.Person", "Discriminator", c => c.String(nullable: false, maxLength: 128, defaultValue: "Instructor"));
AlterColumn("dbo.Person", "HireDate", c => c.DateTime());
AddColumn("dbo.Person", "OldId", c => c.Int(nullable: true));
// Copy existing Student data into new Person table.
Sql("INSERT INTO dbo.Person (LastName, FirstName, HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName, null AS HireDate, EnrollmentDate, 'Student' AS Discriminator, ID AS OldId FROM dbo.Student");
// Fix up existing relationships to match new PK's.
Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator = 'Student')");
// Remove temporary key
DropColumn("dbo.Person", "OldId");
DropTable("dbo.Student");
// Re-create foreign keys and indexes pointing to new table.
AddForeignKey("dbo.Enrollment", "StudentID", "dbo.Person", "ID", cascadeDelete: true);
CreateIndex("dbo.Enrollment", "StudentID");
}
public override void Down()
{
CreateTable(
"dbo.Student",
c => new
{
ID = c.Int(nullable: false, identity: true),
LastName = c.String(nullable: false, maxLength: 20),
FirstName = c.String(nullable: false, maxLength: 20),
EnrollmentDate = c.DateTime(nullable: false),
})
.PrimaryKey(t => t.ID);
AlterColumn("dbo.Person", "HireDate", c => c.DateTime(nullable: false));
DropColumn("dbo.Person", "Discriminator");
DropColumn("dbo.Person", "EnrollmentDate");
RenameTable(name: "dbo.Person", newName: "Instructor");
}
}
}
Consider:
1. Deleting all Migration files in your Migrations folder
2. Renaming your database in the connection string
3. Running PM> add-migration
4. Running PM> update-database

Grails GORM One-to-one relation issue: column: (should be mapped with insert="false" update="false")

i've two db tables as follow:
CREATE TABLE `customer` (
`id` char(36) NOT NULL,
`name` varchar(50) NOT NULL,
`lastname` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `customer_detail` (
`customer_id` char(36) NOT NULL,
`creation_date` date DEFAULT NULL,
`deletion_date` date DEFAULT NULL,
PRIMARY KEY (`customer_id`),
CONSTRAINT `FK_customer_detail_customer_id` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
and two domain classes that map these tables
class Customer {
String name
String lastname
static hasOne = [detail: CustomerDetail]
static mapping = {
id generator: "assigned"
version false
}
static constraints = {
name maxSize: 50
lastname maxSize: 50
detail nullable: true, unique: true
}
}
class CustomerDetail {
Date creationDate
Date deletionDate
static belongsTo = [customer:Customer]
static mapping = {
id generator: "assigned", column: "customer_id", insert: false, update: false
version false
}
}
when I run the application (run-app) I get the following error:
Caused by MappingException: Repeated column in mapping for entity: my.data.domain.CustomerDetail column: customer_id (should be mapped with insert="false" update="false")
If I remove GORM sets the one-to-one reference on Customer.id and CustomerDetail.id but
the field customer_detail.id doesn't exists in db and I can not modify the db structure.
What is the solution to that?
Thank you in advance
Mono
I've found the solution here Grails domain-classes mapping in one-to-one relation so these are the working domain classes:
class Customer {
String id;
String name
String lastname
static hasOne = [detail: CustomerDetail]
static mapping = {
id generator: "assigned"
version false
}
static constraints = {
name maxSize: 50
lastname maxSize: 50
detail nullable: true, unique: true
}
}
class CustomerDetail {
String id;
Date creationDate
Date deletionDate
Customer customer
static mapping = {
id column: '`customer_id`', generator: 'foreign', params: [ property: 'customer'], type: "text"
customer column: '`customer_id`', insertable: false, updateable: false, type: "text"
version false
}
}

Grails static mapping fails (repeated id)

I'm trying to do reverse engineering and create a model class for a given table schema in a database.
The table's name is infopac_usersProva and it has two columns:
strCip varchar(15) which is the id
USERNM varchar(75)
I have written the model like this:
class Infopac_usersProva {
String strCip
String usernm
static mapping={
datasource 'gpaq'
table 'infopac_usersProva'
version false
columns{
id column: 'strCip'
usernm column: 'USERNM', sqlType: "varchar(75)"
strCip column: 'strCip', sqlType: "varchar(15)"
}
}
static constraints = {
strCip (nullable:true, insert:false, update:false)
}
}
But I get this error:
Repeated column in mapping for entity: edu.upc.gpaq.domain.generic.Infopac_usersProva column: strCip (should be mapped with insert="false" update="false")
I need to specify the column name for strCip because if I take out that line the model is trying to fetch str_cip instead of strCip. And if I take out "id column: 'strCip' then I get an error saying that there is no id column.
What am I doing wrong?
I think that you can get rid of strCip definition.
Instead define the id field properly.
See if this works for you:
class Infopac_usersProva {
String usernm
static mapping={
datasource 'gpaq'
table 'infopac_usersProva'
version false
columns{
id generator: 'assigned', name: 'strCip', type: 'string'
usernm column: 'USERNM', sqlType: "varchar(75)"
}
}
I didn't check this...
This one should work:
class Infopack_usersProva {
String strCip
String usernm
static constraints = {
strCip(nullable: false, maxSize: 15)
usernm(nullable: true, maxSize: 75)
}
static mapping = {
datasource('gpaq')
table('infopac_usersProva')
version(false)
autoTimestamp(false)
usernm(column: 'USERNM')
strCip(column: 'strCip')
id(column: 'strCip', name: 'strCip', generator: 'assigned')
}
}
But it has strCip as not null. But AFAIK you need an id column that is not null so i do not see any way around this. At least with grails/hibernate.
And if you want strCip never be included in any save() you need to specify
strCip(column: 'strCip', insertable: false, updateable: false)
in the mappings block. I am not aware of any constraints called insertor update and would expect them to just get ignored there.
Might be a little late for this, but you need to use the updateable and insertable properties instead. It worked for me:
class Infopac_usersProva {
String strCip
String usernm
static mapping={
datasource 'gpaq'
table 'infopac_usersProva'
version false
columns{
id column: 'strCip'
usernm column: 'USERNM', sqlType: "varchar(75)"
strCip column: 'strCip', updateable: false, insertable: false
}
}
}
I ended up doing the following (see the bold text)
class Infopac_usersProva {
String usernm
String id
static mapping={
datasource 'gpaq'
table 'infopac_usersProva'
version false
autoTimestamp false
columns{
**id column: 'strCip', sqlType: "varchar(15)"**
usernm column: 'USERNM', sqlType: "varchar(75)"
}
}
static constraints = {
}
}
It works now. Thank you a lot!

Resources