I have a Base class, a Derived class and Derived has a collection of Items.
I want to configure EF to delete Items when their parent Derived is deleted.
The following minimal (LinqPad) example shows how I'm trying to achieve that, but it doesn't generate the on delete cascade part, just regular FKs.
I tried [Required] attribute - didn't work.
How do I make it add delete cascade option to the FK specification?
[System.ComponentModel.DataAnnotations.Schema.Table("Bases")]
public class Base
{
public int Id {get;set;}
public string Name {get; set;}
}
[System.ComponentModel.DataAnnotations.Schema.Table("Derived")]
public class Derived : Base
{
public virtual ICollection<Item> Items {get;set;}
public Derived()
{
Items = new HashSet<Item>();
}
}
public class Item
{
public int Id {get;set;}
public int ParentId {get;set;}
public Derived Parent {get;set;}
}
public class TestDbContext : System.Data.Entity.DbContext
{
public System.Data.Entity.DbSet<Base> Bases { get; set; }
public System.Data.Entity.DbSet<Derived> Derived { get; set; }
public System.Data.Entity.DbSet<Item> Items { get; set; }
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
System.Data.Entity.Database.SetInitializer<TestDbContext>(null);
modelBuilder.Entity<Item>().HasRequired(x=>x.Parent).WithMany(x=>x.Items).HasForeignKey(x=>x.ParentId).WillCascadeOnDelete(true);
}
}
void Main()
{
var ctx = new TestDbContext();
var ddl = (ctx as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext.CreateDatabaseScript();
Console.WriteLine(ddl);
}
This is the DDL it generates:
create table [dbo].[Bases] (
[Id] [int] not null identity,
[Name] [nvarchar](max) null,
primary key ([Id])
);
create table [dbo].[Derived] (
[Id] [int] not null,
primary key ([Id])
);
create table [dbo].[Items] (
[Id] [int] not null identity,
[ParentId] [int] not null,
primary key ([Id])
);
alter table [dbo].[Derived] add constraint [Derived_TypeConstraint_From_Base_To_Derived] foreign key ([Id]) references [dbo].[Bases]([Id]);
alter table [dbo].[Items] add constraint [Item_Parent] foreign key ([ParentId]) references [dbo].[Derived]([Id]);
Related
I'm trying to make a one-to-many relationship with a composite primary key:
public class Bom
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; set; }
public string Name { get; set; }
}
public class ChildReference
{
[Key]
public int Id { get; set; }
public string BomId { get; set; } // Should be the foreign key from the bom-table
public ICollection<Bom> Boms { get; set; }
}
......
builder.Entity<ChildReference>().HasKey(t => new { t.Id, t.BomId });
When I run this, Entity Framework Core creates two columns in the Bom-table called ChildReferenceBomId and ChildReferenceId. I don't want that. I want it to only create one column caled ChildReferenceId that should be the foreign key to the ChildReference table.
The reason why I want to create a composite primary key inside the ChildReference table is because I want to add rows to the table like this:
INSERT INTO ChildReference(Id, BomId) VALUES(1, '1')
INSERT INTO ChildReference(Id, BomId) VALUES(1, '2')
I'm not sure if I'm doing this the right way. Can anyone help me?
EDIT:
I basically want to do the following with entity framework core:
CREATE TABLE [dbo].BOMChildren (
[BOMChildId] [int] NOT NULL,
[BOMId] [int] NOT NULL,
CONSTRAINT [PK_BOMChildId_BOMId] PRIMARY KEY CLUSTERED
(
[BOMChildId] ASC,
[BOMId] ASC
))
CREATE TABLE [dbo].BOM (
[BOMId] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL,
[BOMPartId] [nvarchar](64) NOT NULL,
[Qty] [int] NOT NULL,
[UnitOfMeasure] [nvarchar](32),
[ParentId] [int] NULL,
[ChildReference] [int] NULL,
[BOMItemDataId] [int]
)
ALTER TABLE [dbo].[BOMChildren]
ADD CONSTRAINT [FK_BOMChildren_BOM]
FOREIGN KEY([BOMId]) REFERENCES [dbo].[BOM] ([BOMId])
GO
ALTER TABLE dbo.Bom
ADD CONSTRAINT FK_Bom_BomChild
FOREIGN KEY(ChildReference, BOMId) REFERENCES [dbo].BOMChildren([BOMChildId], [BOMId])
Anyone who can push me in the right direction?
You can use set it up like this:
public class Bom
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; set; }
public string Name { get; set; }
public int ChildReferenceId { get; set; }
public ChildReference CurrentChildReference{ get; set; }
}
public class ChildReference
{
[Key]
public int Id { get; set; }
public ICollection<Bom> Boms { get; set; }
}
You can after that configure a one-to-many relationship for the above entities using Fluent API by overriding the OnModelCreating method in the context class, as shown here:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// configures one-to-many relationship
modelBuilder.Entity<Bom>()
.HasRequired<ChildReference>(s => s.CurrentChildReference)
.WithMany(g => g.Boms)
.HasForeignKey<int>(s => s.ChildReferenceId);
}
You can use the Create/Alter SQL statements that you posted above to create the tables within your database.
After that you can do a Reverse Engineer Model to generate EF code first DbContext based on your existing Database tables. You can follow the steps within chapter 3 (https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/workflows/existing-database)
I am creating a new Blazor server side application with EF Core 6. I am more used to using database first and Dapper. However I am trying to get more familiar with EF Core and code first approach as requested by the department.
While reviewing the documentation on the changes for EF Core 6 in relation to the many to many relationships, I am stuck on stuck on how to configure the relationships properly for what I call a nested many to many. I believe I have the basic concept of the base 2 tables with many to many but this is a little more complex. I have searched MS and SO but only found some articles that are close but not fitting my situation.
The situation is this, for example 1 table Applications, 1 table Environments and a table Databases. There are an application can have multiple environments at which point the combination of an application(s) / environment(s) can have multiple databases.
Here is how the tables were designed.
CREATE TABLE [dbo].[App] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] VARCHAR (100) NOT NULL,
CONSTRAINT [PK_Applications] PRIMARY KEY CLUSTERED ([Id] ASC))
CREATE TABLE [dbo].[Env] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] VARCHAR (80) NOT NULL,
[Value] VARCHAR (100) NOT NULL,
CONSTRAINT [PK_Env] PRIMARY KEY CLUSTERED ([Id] ASC))
CREATE TABLE [crosswalk].[App_Env] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[AppId] INT NOT NULL,
[EnvId] INT NOT NULL,
CONSTRAINT [PK_App_Env] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_App_Env_App] FOREIGN KEY ([AppId]) REFERENCES [dbo].[App] ([Id]),
CONSTRAINT [FK_App_Env_EnvId] FOREIGN KEY ([EnvId]) REFERENCES [dbo].[Env] ([Id]))
CREATE TABLE [crosswalk].[App_Env_Db] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[AppEnvtId] INT NOT NULL,
[DbId] INT NOT NULL,
CONSTRAINT [PK_App_Env_Db] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_App_Env_Db_App_Env] FOREIGN KEY ([AppEnvtId]) REFERENCES [crosswalk].[App_Env] ([Id]),
CONSTRAINT [FK_App_Env_Db] FOREIGN KEY ([DbId]) REFERENCES [dbo].[Db] ([Id]))
The application is not complete so not able to run it. Was able to come up with one of the configurations but not sure on how to code the rest.
entity.HasMany(d => d.Envs)
.WithMany(p => p.Apps)
.UsingEntity<Dictionary<string, object>>(
"AppEnv",
l => l.HasOne<Env>().WithMany().HasForeignKey("AppId"),
r => r.HasOne<App>().WithMany().HasForeignKey("EnvId"),
j =>
{
j.HasKey("EnvId", "AppId");
j.ToTable("Crosswalk.App_Env");
j.HasIndex(new[] { "EnvId" }, "IX_AppEnv_EnvId");
j.HasIndex(new[] { "AppId" }, "IX_AppEnv_AppId");
});
The models are as follows:
[Table("App")]
public partial class App
{
public App() => Envs = new HashSet<Env>();
[Key]
public int Id { get; set; }
[Required]
[StringLength(100)]
[Unicode(false)]
public string Name { get; set; }
[InverseProperty("App")]
public virtual ICollection<LookupCode> Envs { get; set; }
}
[Table("Env")]
public partial class LookupCode
{
public LookupCode() => Apps = new HashSet<Apps>();
[Key]
public int Id { get; set; }
[Required]
[StringLength(80)]
[Unicode(false)]
public string Name { get; set; }
[Required]
[StringLength(100)]
[Unicode(false)]
public string Value { get; set; }
public int SortOrder { get; set; }
[InverseProperty("Env")]
public virtual ICollection<App> Apps { get; set; }
}
[Table("Db")]
public partial class Db
{
public Db() => AppEnvDbs = new HashSet<AppEnvDb>();
[Key]
public int Id { get; set; }
[Required]
[StringLength(50)]
[Unicode(false)]
public string Name { get; set; }
[InverseProperty("Db")]
public virtual ICollection<AppEnvDb> AppEnvDbs { get; set; }
}
If this is not possible I can go back to creating the DTO's for the link tables and manually adding/creating/deleting the link records.
EDIT: Added missing table definition for Db. Although the DTO model was shown I forgot to include the data table.
CREATE TABLE [dbo].[Db] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[DbServerId] INT NOT NULL,
[Name] VARCHAR (50) NOT NULL,
CONSTRAINT [PK_Db] PRIMARY KEY CLUSTERED ([Id] ASC)
);
I'm having an issue with EF Core automatically adding foreign keys while I didn't ask for it.
So basically I'm using code first migrations in C# with SQL Server, some models and the basic context down below. But as you can see with my example models I'm adding a dependency from Event -> Item, one to many, what I want is many to many. What I think is happening is that EFCore is switching the foreign key dependencies around? I had linktables before like EventItems.
Am I required to use the linktables again or is there an other way to fix this?
Event model
namespace Models
{
public class Event
{
[Required]
public int Id { get; set; }
public string Description { get; set; }
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; }
public ICollection<Item> Rewards { get; set; }
}
}
Event SQL
CREATE TABLE [dbo].[Events] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Description] NVARCHAR (MAX) NULL,
[StartDate] DATETIME2 (7) NOT NULL,
[EndDate] DATETIME2 (7) NOT NULL,
CONSTRAINT [PK_Events] PRIMARY KEY CLUSTERED ([Id] ASC)
);
Item model
namespace Models
{
public class Item
{
[Required]
public int Id { get; set; }
[Required]
public string Path { get; set; }
}
}
Item SQL
CREATE TABLE [dbo].[Items] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[EventId] INT NULL,
[Path] NVARCHAR (MAX) DEFAULT (N'') NOT NULL,
[QuestId] INT NULL,
CONSTRAINT [PK_Items] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Items_Events_EventId] FOREIGN KEY ([EventId]) REFERENCES [dbo].[Events] ([Id])
);
GO
CREATE NONCLUSTERED INDEX [IX_Items_EventId]
ON [dbo].[Items]([EventId] ASC);
Edited
I have 2 tables, Nurse and Person. Person has a 1 to 0 or 1 relationship to Nurse. In other words some Persons in my application will also be a Nurse. I would like to use the same primary key in both tables. When I run my code querying the Nurse table, I end up with a "System.Data.Entity.Core.EntityCommandExecutionException", and then on the inner exception: "SqlException: Invalid column name 'Person_Id'."
My Nurse model does not have a "Person_Id" field in it. The generated query seems to have added it. I must be missing something in my model definition, as removing "public virtual Person Person { get; set; }" makes the query generate correctly. EF seems to get hung up on the fact that the PK is Id in the Person table, but NurseId in the Nurse table.
I've found multiple places on the internet that show you how to map this with Fluent API, but nothing that matches my case. Because most Persons in my application are not Nurse, I don't want to have to reference the Nurse model in my Person model, which takes away the solution I keep seeing online:
//won't work because I am trying to no have a reference to Nurse in my Person model. So i.Nurse below doesn't exist
modelBuilder.Entity<Nurse>().HasRequired(i => i.Person).WithOptional(i => i.Nurse)
The query EF generates:
{SELECT
1 AS [C1],
[Extent1].[NurseId] AS [NurseId],
[Extent1].[CredentialId] AS [CredentialId],
[Extent1].[DisciplineId] AS [DisciplineId],
[Extent1].[UnitId] AS [UnitId],
[Extent1].[YearsOfService] AS [YearsOfService],
[Extent1].[Person_Id] AS [Person_Id]
FROM [dbo].[Nurse] AS [Extent1]}
Here are the tables:
CREATE TABLE [dbo].[Person]
(
[Id] BIGINT IDENTITY (1, 1) NOT NULL,
CONSTRAINT [PK__Person] PRIMARY KEY CLUSTERED ([Id] ASC)
)
CREATE TABLE [dbo].[Nurse]
(
[NurseId] BIGINT NOT NULL,
CONSTRAINT [PK_Nurse] PRIMARY KEY ([NurseId]),
CONSTRAINT [FK_Nurse_Person] FOREIGN KEY ([PersonId]) REFERENCES [dbo].[Person] ([Id])
)
And the Entities:
public class Person
{
[Key]
public long Id { get; set; }
}
public class Nurse
{
[Key]
public long NurseId { get; set; }
public long? CredentialId { get; set; }
public long? DisciplineId { get; set; }
public long? UnitId { get; set; }
public int? YearsOfService { get; set; }
[ForeignKey("PersonId")]
public virtual Person Person { get; set; }
[ForeignKey("CredentialId")]
public virtual Credential Credential { get; set; }
[ForeignKey("DisciplineId")]
public virtual Discipline Discipline { get; set; }
[ForeignKey("UnitId")]
public virtual Unit Unit { get; set; }
}
ApplicationDBContext:
public class ApplicationDBContext : DbContext
{
public ApplicationDBContext() : base("ApplicationDbContext")
{
}
public DbSet<Nurse> Nurses { get; set; }
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Nurse>().HasKey(n => n.NurseId);
}
}
Is it possible to do this, or do I need to reference Nurse in my Person table and go with what I've seen online?
I think your problem is that you are using PersonId has the primary key in Nurse. Try giving Nurse its own Id. It is a good idea for every table to have its own Ids. EF has issues with tables or views that do not have a unique primary key.
CREATE TABLE [dbo].[Person]
(
[Id] BIGINT IDENTITY (1, 1) NOT NULL,
CONSTRAINT [PK__Person] PRIMARY KEY CLUSTERED ([Id] ASC)
)
CREATE TABLE [dbo].[Nurse]
(
[Id] BIGINT IDENTITY (1, 1) NOT NULL,
[PersonId] BIGINT unique NOT NULL,
CONSTRAINT [PK_Nurse] PRIMARY KEY ([Id]),
CONSTRAINT [FK_Nurse_Person] FOREIGN KEY ([PersonId]) REFERENCES [dbo].[Person] ([Id])
)
For those that stumble across this. I found the answer here. The fluent api I posted was close, it should have been:
modelBuilder.Entity<Nurse>().HasRequired(n => n.Person).WithOptional();
Consider the following class hierarchy:
public abstract class Entity
{
public virtual int Id { get; private set; }
}
public class ConfiguredBlockEntity : Entity
{
public virtual ScheduledGreetingEntity ScheduledGreeting { get; set; }
}
public abstract class ConfiguredVariableEditableBlockEntity
: ConfiguredBlockEntity
{
public virtual VariableEditableBlockEntity TemplateBlock { get; set; }
}
public class ConfiguredPhoneNumberBlockEntity
: ConfiguredVariableEditableBlockEntity
{
public virtual string PhoneNumber { get; set; }
}
I am using the automapping feature of Fluent NHibernate.
This creates the following table structure:
create table "BlockEntity" (
Id INT IDENTITY NOT NULL,
ExecutionOrder INT null,
Name NVARCHAR(255) null,
BlockType NVARCHAR(255) null,
GreetingId INT null,
primary key (Id)
);
create table "ConfiguredBlockEntity" (
Id INT IDENTITY NOT NULL,
ScheduledGreetingId INT null,
primary key (Id)
);
create table ConfiguredPhoneNumberBlockEntity (
ConfiguredVariableEditableBlockId INT not null,
PhoneNumber NVARCHAR(255) null,
VariableEditableBlockId INT null,
primary key (ConfiguredVariableEditableBlockId)
);
alter table ConfiguredPhoneNumberBlockEntity
add constraint FK87F9EFC9BB9A4B52
foreign key (ConfiguredVariableEditableBlockId)
references "ConfiguredBlockEntity";
There are some problems with this result:
The table ConfiguredPhoneNumberBlockEntity has one column that is primary key and foreign key to the base class in one (column ConfiguredVariableEditableBlockId. This looks strange and doesn't conform to how I normally design my tables. If I would have designed the table by hand, it would have an Id column that is the PK and an ConfiguredBlockId columnt that is the FK. Can I change this somehow with fluent or automapping?
The PK/FK column is named ConfiguredVariableEditableBlockId, but there exists no table ConfiguredVariableEditableBlock, because it was an abstract base class and its property has been incorporated into ConfiguredPhoneNumberBlockEntity table. This column references the table ConfiguredBlockEntity and thus should be named accordingly. How to fix this in fluent or automapping?
an inheritance table doesnt need an extra Id column, its never read alone or by id. Normally its joined with the baseclass-table. Therefor FNH defaults to this design. You can enforce your design for example with FluentMappings with a hidden Property Id as Readonly (additional work for no value).
class MyJoinedSubclassConvention : IJoinedSubclassConvention,
IJoinedSubclassConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IJoinedSubclassInspector> criteria)
{
criteria.Expect(x => x.Name == "ConfiguredPhoneNumberBlockEntity");
}
public void Apply(IJoinedSubclassInstance instance)
{
instance.Key.Column("baseclassId");
}
}
Edit: or more general
class MyJoinedSubclassConvention : IJoinedSubclassConvention
{
public void Apply(IJoinedSubclassInstance instance)
{
Type basetype = instance.Extends;
while (basetype.IsAbstract)
{
basetype = basetype.BaseType;
}
instance.Key.Column(basetype.Name + "Id");
}
}