EF CodeFirst bidirectional 1 : 0 or 1 relationship - c#

I have 3 entities; Groups, Scopes, Vlans
What I'm trying to accomplish:
A Group can have many Scopes
A Group can have many Vlans
A Scope must have a Group
A Vlan must have a Group
A Scope can have a Vlan
A Vlan can have a Scope
So kind of a soft relationship between vlans and scopes.
When not setting vlan or scope as a requirement EF complains about not knowing the principal of the relationship, So how do I fix this soft one to one relationship?
My Models:
public class Group
{
public Group()
{
Scopes = new HashSet<Scope>();
Vlans = new HashSet<VLAN>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ICollection<VLAN> Vlans { get; set; }
public ICollection<Scope> Scopes { get; set; }
}
public class Scope
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual VLAN Vlan { get; set; }
public virtual Group Group { get; set; }
}
public class VLAN
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual Scope Scope { get; set; }
[Required]
public virtual Group Group { get; set; }
}

There is a similar problem on SO, related to bidirectional Zero-to-One relationships:
Implementing Zero Or One to Zero Or One relationship in EF Code first by Fluent API
I've tried the same approach on your schema and it works.
See my FluentAPI configuration below:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<VLAN>()
.HasOptional(vlan => vlan.Scope)
.WithOptionalPrincipal();
modelBuilder.Entity<VLAN>()
.HasRequired(vlan => vlan.Group);
modelBuilder.Entity<Scope>()
.HasOptional(scope => scope.Vlan)
.WithOptionalPrincipal();
modelBuilder.Entity<Scope>()
.HasRequired(scope => scope.Group);
modelBuilder.Entity<Group>()
.HasMany(group => group.Scopes);
modelBuilder.Entity<Group>()
.HasMany(group => group.Vlans);
}
To be sure, that it suites your needs, see the generated SQL for VLANs and Scopes tables:
CREATE TABLE [dbo].[Scopes] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[VLAN_Id] INT NULL,
[Group_Id] INT NOT NULL,
CONSTRAINT [PK_dbo.Scopes] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_dbo.Scopes_dbo.VLANs_VLAN_Id] FOREIGN KEY ([VLAN_Id]) REFERENCES [dbo].[VLANs] ([Id]),
CONSTRAINT [FK_dbo.Scopes_dbo.Groups_Group_Id] FOREIGN KEY ([Group_Id]) REFERENCES [dbo].[Groups] ([Id]) ON DELETE CASCADE);
CREATE TABLE [dbo].[VLANs] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Scope_Id] INT NULL,
[Group_Id] INT NOT NULL,
CONSTRAINT [PK_dbo.VLANs] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_dbo.VLANs_dbo.Scopes_Scope_Id] FOREIGN KEY ([Scope_Id]) REFERENCES [dbo].[Scopes] ([Id]),
CONSTRAINT [FK_dbo.VLANs_dbo.Groups_Group_Id] FOREIGN KEY ([Group_Id]) REFERENCES [dbo].[Groups] ([Id]) ON DELETE CASCADE);

Related

One-to-many relationship with composite primary key using EF Core

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)

EF Core 6 Many to Many nested

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)
);

How to apply correctly a relationship of 1 to 1 in EF6

So i wanted to apply a relation of 1 to 1 from one table to another, with navigational properties on each one and a foreign key that is accessable on at least one of the models.
Lets suppose this example
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public int ContactId { get; set; }
public virtual Contact Contact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public virtual User User { get; set; }
}
modelBuilder.Entity<User>().HasOptional<Contact>(u=> u.Contact)
.WithRequired(c => c.User).Map(m => m.MapKey("ContactId")).
Similar to the same example used in this stack overflow question:
EF Code First - 1-to-1 Optional Relationship
The problem is that it gives an error saying that the Property name 'ContactId' is already defined.
But i want to have this foreign property defined both at the database and on the model, so that i can use for example linq:
this.dbContextProvider.CurrentContext.User.SingleOrDefault(src => src.ContactId == contactId);
or is this acceptable or very inneficient:
this.dbContextProvider.CurrentContext.User.SingleOrDefault(src => src.Contact.Id == contactId);
This last options will create a join between the two tables while query the database, right?
The downside of the correct model (i.e. without explicit User.ContactId property) is that in reality it's still a 1:n relationship. The database doesn't enforce 1:1. It's just a FK. The only way to make a true, database-enforced 1:1 association in EF6 is one in which the dependent entity (here: User) has a primary key that's also a foreign key to the principal entity (Contact):
public class User
{
public int Id { get; set; }
public string Username { get; set; }
//public int ContactId { get; set; } <= removed
public virtual Contact Contact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public virtual User User { get; set; }
}
And:
modelBuilder.Entity<User>()
.HasRequired<Contact>(u => u.Contact)
.WithOptional(c => c.User);
This generates the following database schema:
CREATE TABLE [dbo].[Users] (
[Id] [int] NOT NULL,
[Username] [nvarchar](max),
CONSTRAINT [PK_dbo.Users] PRIMARY KEY ([Id])
)
CREATE TABLE [dbo].[Contacts] (
[ID] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
CONSTRAINT [PK_dbo.Contacts] PRIMARY KEY ([ID])
)
CREATE INDEX [IX_Id] ON [dbo].[Users]([Id])
ALTER TABLE [dbo].[Users] ADD CONSTRAINT [FK_dbo.Users_dbo.Contacts_Id]
FOREIGN KEY ([Id]) REFERENCES [dbo].[Contacts] ([ID])
As for querying, in a query like context.Users.Where(u => u.Contact.ID == 4), EF6 will notice that no Contact fields are requested and it will short-circuit the FK to User.Id, i.e. no join. But of course, in this setup, you may as well use context.Users.Where(u => u.Id == 4).
In EF core it would be possible to use your model, with User.ContactId, by this mapping:
modelBuilder.Entity<User>()
.HasOne(u => u.Contact)
.WithOne(c => c.User)
.HasForeignKey<User>(u => u.ContactId);
EF core is smart enough to create a unique index on User.ContactId, so this is a database-enforced 1:1 association with a separate FK.

How do I map a 1 to 0 or 1 relationship in Fluent API in EF 6

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();

Different Key in Table relations

I have a database with this structure
TASKs SubTasks
=============================
Id (pk) Id (pk)
Name Name
TaskCode ParentTaskCode
Now I need to connect SubTasks Table to Tasks using Tasks.TaskCode as the key in the relation between them, and Entity Framework does not allow me to do that, any ideas :) ?
Note: I do not own this database, so any changes to the structure cannot be done.
You can try something like.
public class Certificates
{
[Key]
public int CertID { get; set; }
public Users User { get; set; }
public Quiz Quiz { get; set; }
}
public class Users
{
public int ID { get; set; }
public ICollection<Certificates> Certificates { get; set; }
}
public class Quiz
{
public int QuizID { get; set; }
public ICollection<Certificates> Certificates { get; set; }
}
public class cpdContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entitiy<Users>()
.HasMany(u => u.Certificates)
.WithRequired(c => c.User) // or WithOptional
.Map(m => m.MapKey("UserID")); //<- DB FK column name
modelBuilder.Entitiy<Quiz>()
.HasMany(u => u.Certificates)
.WithRequired(c => c.Quiz) // or WithOptional
.Map(m => m.MapKey("QuizID")); //<- DB FK column name
}
}
Moreover, In independent association, the foreign key (Association) is defined in the conceptual model.
To define the association in a conceptual model, we must add association set, association and navigation properties.
It is represented as separate object in ObjectStateManager. It has its own EntityState!
When building association you always need entitites from both ends of association
This association is mapped in the same way as entity.
Source
There might be something I do not understand...
Why do you want to use Task.TaskCode as a foreign key of SubTasks ?
If ID is the PK of Tasks, then remove TaskCode an use Task.ID as you fk reference
SubTasks.TaskID ----> Task.ID.
Also, some other advices on naming conventions.
do not pluralize table names
use "<%table%>ID" as name for your pk : exemple TaskID for task.
use fk column as name for a fk : exemple "TaskID" as fk in your subtask table
If you are using MVC for this then you can simply make composite viewmodel for above.
Without changing in EF you can do what you want.
Make class as
public class CompositeTaskSubtask
{
public <namespace>.<tasktablename> taskmodel { get; set; }
public <namespace>.<subtasktablename> subtaskmodel { get; set; }
}
You can try adding a linking table called SubTask with its own primary key. That way taskCode will not need to be primary key.
CREATE TABLE Task
(
taskId int IDENTITY (1, 1) NOT NULL,
name nvarchar(50),
taskCode nvarchar(50),
CONSTRAINT Task_PK PRIMARY KEY(taskId)
)
GO
CREATE TABLE SubTask
(
subTaskId int NOT NULL,
taskId int NOT NULL, -- must have a parent
CONSTRAINT SubTask_PK PRIMARY KEY(subTaskId)
)
GO
ALTER TABLE SubTask ADD CONSTRAINT SubTask_FK1 FOREIGN KEY (taskId) REFERENCES [Task] (taskId) ON DELETE NO ACTION ON UPDATE NO ACTION
GO
ALTER TABLE SubTask ADD CONSTRAINT SubTask_FK2 FOREIGN KEY (subTaskId) REFERENCES [Task] (taskId) ON DELETE NO ACTION ON UPDATE NO ACTION
GO

Categories

Resources