For one to many relationship, what's the difference between HasMany and OwnsMany? When should I use one over another?
For example:
public class xxx
{
public virtual IReadOnlyCollection<xxxHistoryEntity> Histories => _histories;
private readonly List<xxxHistoryEntity> _histories = new List<xxxHistoryEntity>();
}
public class xxxHistoryEntity : Entity<string>
{
public string State { get; set; }
public string NodeId { get; set; }
public string Message { get; set; }
}
The Entity Configuration:
class xxxConfiguration
: IEntityTypeConfiguration<xxx>
{
public void Configure(EntityTypeBuilder<xxx> builder)
{
builder.OwnsMany(itm => itm.Histories, collbuilder =>
{
collbuilder.HasForeignKey("xxxid");
});
}
}
class xxxHistoryConfiguration
: IEntityTypeConfiguration<xxxHistoryEntity>
{
public void Configure(EntityTypeBuilder<xxxHistoryEntity> builder)
{
builder.ToTable("xxx_histories");
builder.HasKey(itm => itm.Id);
builder.Property(itm => itm.Id)
.ValueGeneratedOnAdd();
}
}
The generated migration is below:
migrationBuilder.CreateTable(
name: "xxx_histories",
columns: table => new
{
id = table.Column<string>(nullable: false),
xxxid = table.Column<string>(nullable: false),
state = table.Column<string>(nullable: true),
nodeid = table.Column<string>(nullable: true),
message = table.Column<string>(nullable: true),
xmin = table.Column<uint>(type: "xid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_xxx_histories", x => new { x.id, x.xxxid });
table.ForeignKey(
name: "fk_xxxhistoryentity_xxx_xxxarid",
column: x => x.xxxid,
principalTable: "xxx",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
if I update the xxxConfiguration by replacing the OwnsMany with HasMany, like:
class xxxConfiguration
: IEntityTypeConfiguration<xxx>
{
public void Configure(EntityTypeBuilder<xxx> builder)
{
builder.HasMany(itm => itm.Histories)
.WithOne()
.HasForeignKey("xxxid");
}
}
The generated migration is below:
migrationBuilder.CreateTable(
name: "xxx_histories",
columns: table => new
{
id = table.Column<string>(nullable: false),
xxxid = table.Column<string>(nullable: false),
state = table.Column<string>(nullable: true),
nodeid = table.Column<string>(nullable: true),
message = table.Column<string>(nullable: true),
xmin = table.Column<uint>(type: "xid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_xxx_histories", x => new { x.id, x.xxxid });
table.ForeignKey(
name: "fk_xxxhistoryentity_xxx_xxxid",
column: x => x.xxxid,
principalTable: "xxx",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
As you can see, the migration generated by both are the same. So what's the point of OwnsMany?
From documentation:
EF Core allows you to model entity types that can only ever appear on
navigation properties of other entity types. These are called owned
entity types. The entity containing an owned entity type is its owner.
Owned entities are essentially a part of the owner and cannot exist
without it, they are conceptually similar to aggregates.
https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities
One of the differences is that relationships configured with OwnsMany() will include the owned entities by default when querying the owner from the database, whilst when using WithMany() you have to specify AutoInclude() manually if you want them to be included every time you get the owner entity form the database.
Also from documentation: Querying owned types
Related
I am trying to create a one-to-one foreign-key relationship between the below columns from Room to RoomBase.
Room.RoomId > RoomBase.RoomId
Room.StageId > RoomBase.RoomId
Room.ParentLobbyId > RoomBase.RoomId
public class Room
{
public int RoomRootId { get; set; }
public int RoomId { get; set; }
public RoomBase RoomObj { get; set; }
public int StageId { get; set; }
public RoomBase StageObj { get; set; }
public int ParentLobbyId { get; set; }
public RoomBase ParentLobbyObj { get; set; }
}
public class RoomBase
{
public int RoomId { get; set; }
public string Name { get; set; }
}
I have attempted to do this by using the following code in OnModelCreating.
modelBuilder.Entity<Room>(u =>
{
u.ToTable("Rooms");
u.Property(e => e.RoomId).HasColumnName("RoomId");
u.HasOne<RoomBase>().WithOne().HasForeignKey<Room>(e => e.RoomId);
});
modelBuilder.Entity<Room>(u =>
{
u.ToTable("Rooms");
u.Property(e => e.StageId).HasColumnName("StageId");
u.HasOne<RoomBase>().WithOne().HasForeignKey<Room>(e => e.StageId);
});
modelBuilder.Entity<Room>(u =>
{
u.ToTable("Rooms");
u.Property(e => e.ParentLobbyId).HasColumnName("ParentLobbyId");
u.HasOne<RoomBase>().WithOne().HasForeignKey<Room>(e => e.ParentLobbyId);
});
This results in the following migration script:
migrationBuilder.CreateTable(
name: "Rooms",
columns: table => new
{
RoomRootId = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
RoomId = table.Column<int>(type: "INTEGER", nullable: false),
StageId = table.Column<int>(type: "INTEGER", nullable: false),
ParentLobbyId = table.Column<int>(type: "INTEGER", nullable: false),
ParentLobbyObjRoomId = table.Column<int>(type: "INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Rooms", x => x.RoomRootId);
});
migrationBuilder.CreateTable(
name: "RoomBases",
columns: table => new
{
RoomId = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
HostName = table.Column<string>(type: "TEXT", nullable: true),
RoomRootId = table.Column<int>(type: "INTEGER", nullable: true),
RoomRootId1 = table.Column<int>(type: "INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_RoomBases", x => x.RoomId);
table.ForeignKey(
name: "FK_RoomBases_Rooms_RoomRootId",
column: x => x.RoomRootId,
principalTable: "Rooms",
principalColumn: "RoomRootId",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_RoomBases_Rooms_RoomRootId1",
column: x => x.RoomRootId1,
principalTable: "Rooms",
principalColumn: "RoomRootId",
onDelete: ReferentialAction.Restrict);
});
Question 1: I don't think it should be adding "FK_Room_RoomBases_ParentLobbyObjRoomId" but why dont we seem similar for the RoomObj and StageObj?
Question 2: I believe I am missing the navigation propery as when I perform a linq query using the "Include", the obj properties of Room is not populated. How do I implement this for RoomObj, StageObj and ParentLobbyObj?
Question 3: Why are additional columns being added to "RoomBases"? Maybe an incorrect FK config? I expected something like this under the CreateTable for Room, not RoomBase:
table.ForeignKey(
name: "FK_Rooms_RoomBases_RoomId",
column: x => x.RoomId,
principalTable: "RoomBases",
principalColumn: "RoomId";
table.ForeignKey(
name: "FK_Rooms_RoomBases_StageId",
column: x => x.StageId,
principalTable: "RoomBases",
principalColumn: "RoomId";
table.ForeignKey(
name: "FK_Rooms_RoomBases_ParentLobbyId",
column: x => x.ParentLobbyId,
principalTable: "RoomBases",
principalColumn: "RoomId";
I have checked numerous sites such as the below but I have not found an appropriate answer.
efcore-map-2-entities-to-same-table
how-to-fix-unable-to-determine-the-relationship-represented-by-navigation-prope
Entity Framework Core One To One Relationships Conventions
The answer of all your questions is: because of incorrect relationships configuration, and more specifically, not mapping the Room navigation properties (the three u.HasOne<RoomBase>() calls).
By doing so, these navigation properties are left out from the fluently configured relationships, and EF Core will map them conventionally to a separate relationships with the corresponding conventional FKs.
The correct configuration is to specify navigation properties for each relationship
u.HasOne(e => e.RoomObj) // <--
.WithOne().HasForeignKey<Room>(e => e.RoomId);
u.HasOne(e => e.StageObj) // <--
.WithOne().HasForeignKey<Room>(e => e.StageId);
u.HasOne(e => e.ParentLobbyObj) // <--
.WithOne().HasForeignKey<Room>(e => e.ParentLobbyId);
I have following entities:
public class Subscription
{
public int Id { get; set; }
public int? BillingContractId { get; set; }
public BillingContract BillingContract { get; set; }
//other properties
}
public class BillingContract
{
public int Id { get; set; }
public int SubscriptionId { get; set; }
public Subscription Subscription { get; set; }
//other properties
}
So each subscription might have only one billing contract and each billing contract belongs to a single subscription.
I'm trying to configure this relationship in my dbcontext:
builder.Entity<Subscription>()
.HasOne(subscription => subscription.BillingContract)
.WithOne(billingContract => billingContract.Subscription)
.HasForeignKey<BillingContract>(billingContract => billingContract.SubscriptionId)
.IsRequired(true);
builder.Entity<BillingContract>()
.HasOne(billingContract => billingContract.Subscription)
.WithOne(subscription => subscription.BillingContract)
.HasForeignKey<Subscription>(subscription => subscription.BillingContractId)
.IsRequired(false);
But from the generated migration(or from the snapshot or from the actual DB schema) I can tell that only FK in Subscription table is created. I cannot make EF to create a FK(and index) in the BillingContract table. I also tried to use annotation attributes with the same result.
Did I miss something? Or it's a bug in EF?
I'm using EF Core 2.2
To eliminate a possibility of a corrupted db snapshot I created a brand new console project using EF Core 3.1. After adding initial migration I have the same result with missing FK:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BillingContracts",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
SubscriptionId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BillingContracts", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Subscriptions",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
BillingContractId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Subscriptions", x => x.Id);
table.ForeignKey(
name: "FK_Subscriptions_BillingContracts_BillingContractId",
column: x => x.BillingContractId,
principalTable: "BillingContracts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Subscriptions_BillingContractId",
table: "Subscriptions",
column: "BillingContractId",
unique: true,
filter: "[BillingContractId] IS NOT NULL");
}
This is not an EF bug. Usually, two tables have an association relationship, and you only need to create one foreign key in one of the tables. The two-way foreign key is for the entity and does not exist in the database design. This docuement has give the detail example.
I am trying to map a class where i have a list of related items and a selected related item. Basically, I have a workflow with a collection of tasks, and at any given time one of those tasks is the selected as the current task.
public class Flow
{
public int FlowId { get; set; }
public int CurrentFlowTaskId { get; set; }
public bool IsActive { get; set; }
public virtual FlowTask CurrentFlowTask { get; set; }
public virtual ICollection<FlowTask> FlowTasks { get; set; }
}
public class FlowTask
{
public int FlowTaskId { get; set; }
public int FlowId { get; set; }
public string Discription { get; set; }
public virtual Flow Flow { get; set; }
}
And my mapping looks like this:
public class FlowMap : EntityTypeConfiguration<Flow>
{
public FlowMap()
{
HasKey(x => x.FlowId);
Property(x => x.IsActive).IsRequired();
HasOptional(x => x.CurrentFlowTask)
.WithOptionalPrincipal()
.WillCascadeOnDelete(false);
HasMany(x => x.FlowTasks)
.WithRequired(x => x.Flow)
.HasForeignKey(x => x.FlowId)
.WillCascadeOnDelete(false);
}
}
public class FlowTaskMap : EntityTypeConfiguration<FlowTask>
{
public FlowTaskMap()
{
HasKey(x => x.FlowTaskId);
Property(x => x.Discription).HasMaxLength(25).IsRequired();
}
}
This creates a migration that looks like this:
CreateTable(
"dbo.Flows",
c => new
{
FlowId = c.Int(nullable: false, identity: true),
CurrentFlowTaskId = c.Int(nullable: false),
IsActive = c.Boolean(nullable: false),
})
.PrimaryKey(t => t.FlowId);
CreateTable(
"dbo.FlowTasks",
c => new
{
FlowTaskId = c.Int(nullable: false, identity: true),
FlowId = c.Int(nullable: false),
Discription = c.String(nullable: false, maxLength: 25),
Flow_FlowId = c.Int(),
})
.PrimaryKey(t => t.FlowTaskId)
.ForeignKey("dbo.Flows", t => t.Flow_FlowId)
.ForeignKey("dbo.Flows", t => t.FlowId)
.Index(t => t.FlowId)
.Index(t => t.Flow_FlowId);
The first thing that seems amiss here is the Flow_FlowId column that is created in the flow tasks.
When I run the following block of code in LinqPad I do not see the results I expect; A Flow and a FlowTask are created, but Flow.CurrentTaskId is null, and the off Flow_FlowId column is set to the same value as Flow.FlowId
var fi = new CompWalk.Data.FlowTask
{
Discription = "Task 1",
};
var f = new CompWalk.Data.Flow {
IsActive = true,
CurrentFlowTask = fi,
FlowTasks = new[] {
fi
}
};
Flows.Add(f);
SaveChanges();
This code was adapted from an almost identical question here, but is several years old so may no longer be applicable.
Is what I am attempting possible without doing a multiple inserts and saves?
Also, what is causing the generation of the Flow_FlowId column?
You have 2 different relationship types between Flow and FlowTask. You configured one-to-many relationship by adding FlowId to FlowTask and configured it so we have this line in migration
.ForeignKey("dbo.Flows", t => t.FlowId)
which is correct. And there is one-to-one relationship where you marked Flow as principal and therefore FlowTask is dependent. Entity Framework adds principal Id as foreign key to dependent entity to create such relationship. It's not possible to configure foreign key for one-to-one relationship with entity's property (like you did for one-to-many) in Entity Framework. And because of it framework generates foreign key for you and adds it to dependent entity (FlowTask).
.ForeignKey("dbo.Flows", t => t.Flow_FlowId)
And your public int CurrentFlowTaskId { get; set; } property is just a regular column and not a foreign key so indeed it's not set in your example. And Flow_FlowId is set to Flow.FlowId because it is foreign key.
If you want the one-to-one relationship foreign key be added to Flow just change this line
HasOptional(x => x.CurrentFlowTask)
.WithOptionalPrincipal()
.WillCascadeOnDelete(false);
to
HasOptional(x => x.CurrentFlowTask)
.WithOptionalDependent()
.WillCascadeOnDelete(false);
If you want to specify name of the key change it to following
HasOptional(x => x.CurrentFlowTask)
.WithOptionalDependent()
.Map(m => m.MapKey("KeyName"))
.WillCascadeOnDelete(false);
But if you decide to add KeyName property to Flow entity, migration will yield an exception:
KeyName: Name: Each property name in a type must be unique. Property name 'KeyName' is already defined.
So you won't be able to access this value directly in code and I don't know how fix this. Haven't researched it yet. But I think it should be possible to configure this relationship as one-to-many and use it as one-to-one somehow but I'm not sure.
I have a many to many relationship defined as follows:
public class Ticket
{
public int Id { get; set; }
public ICollection<ApplicationUser> TicketSubscribers { get; set; }
}
public class ApplicationUser : IdentityUser<string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
public ICollection<Ticket> SubscriberTickets { get; set; }
}
Fluent api mappings as follows:
public ApplicationUserConfiguration()
{
HasMany<Ticket>(u => u.SubscriberTickets)
.WithMany(t => t.TicketSubscribers)
.Map(
ts =>
{
ts.MapLeftKey("UserId");
ts.MapRightKey("TicketId");
ts.ToTable("TicketSubscriber");
});
}
Migration is as follows:
CreateTable(
"dbo.TicketSubscriber",
c => new
{
UserId = c.String(nullable: false, maxLength: 128),
TicketId = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.UserId, t.TicketId })
.ForeignKey("dbo.User", t => t.UserId, cascadeDelete: true)
.ForeignKey("dbo.Ticket", t => t.TicketId, cascadeDelete: true)
.Index(t => t.UserId)
.Index(t => t.TicketId);
When I run update database I get the following error:
Introducing FOREIGN KEY constraint 'FK_dbo.TicketSubscriber_dbo.Ticket_TicketId' on table 'TicketSubscriber' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint or index. See previous errors.
I dont know how you can specify cascade delete false on a many to many mapping?
Why would it generate this error on a many to many mapping. How do I fix this?
How can I prevent EF Core migrations from adding a shadow property to TimeZone:
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Core_TimeZone",
schema: "dbo",
columns: table => new
{
StateOrProvinceId = table.Column<int>(nullable: true),
...
},
constraints: table =>
{
table.PrimaryKey("PK_Core_TimeZone", x => x.Id);
table.ForeignKey(
name: "FK_Core_TimeZone_Core_StateOrProvince_StateOrProvinceId",
column: x => x.StateOrProvinceId,
principalSchema: "dbo",
principalTable: "Core_StateOrProvince",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
}
}
Here are the POCO classes:
public partial class TimeZone : BaseEntity
{
…
// no StateOrProvince related properties
}
public partial class StateOrProvince : BaseEntity
{
…
private ICollection<Dna.NetCore.Core.BLL.Entities.Common.TimeZone> _timeZones;
public virtual ICollection<Dna.NetCore.Core.BLL.Entities.Common.TimeZone> TimeZones
{
get { return _timeZones ?? (_timeZones = new List<Dna.NetCore.Core.BLL.Entities.Common.TimeZone>()); }
set { _timeZones = value; }
}
}
and the configuration class:
public class StateOrProvinceConfiguration : IEntityTypeConfiguration<StateOrProvince>
{
public void Map(EntityTypeBuilder<StateOrProvince> builder)
{
…
builder.HasMany(d => d.TimeZones)
.WithOne()
.OnDelete(DeleteBehavior.Restrict);
}
}
I'm getting the shadow property regardless of whether or not I include the HasMany().WithOne() configuration.
The full source code is located in this GitHub repository.