I have a Gig Model as follows:
public class Gig
{
public int Id { get; set; }
[Required]
public ApplicationUser Artist { get; set; }
[Required]
public string ArtistId { get; set; }
public DateTime DateTime { get; set; }
[Required]
[StringLength(255)]
public string Venue { get; set; }
[Required]
public Genre Genre { get; set; }
[Required]
public byte GenreId { get; set; }
}
In EF6, I was able to Eager Load Artist and Genre using the following code
var gigs = _context.Attendances
.Where(a => a.AttendeeId == userId)
.Select(a => a.Gig)
.Include(a => a.Artist)
.Include(a => a.Genre)
.ToList();
But with EF Core, the Artist info or the Genre info is not getting loaded. SQL Profiler shows that there is no INNER JOIN being called on the projection tables.
SELECT [a.Gig].[Id], [a.Gig].[ArtistId], [a.Gig].[DateTime], [a.Gig].[GenreId], [a.Gig].[Venue]
FROM [Attendances] AS [a]
INNER JOIN [Gigs] AS [a.Gig] ON [a].[GigId] = [a.Gig].[Id]
WHERE [a].[AttendeeId] = #__userId_0',N'#__userId_0 nvarchar(450)',#__userId_0=N'469d8515-9a04-46af-9276-09c6fead9e10'
Can someone help me re-write the query for EF Core please to include the projection tables?
UPDATE:
added link to db schema scripts here. posting just the gigs table here:
CREATE TABLE [dbo].[Gigs](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ArtistId] [nvarchar](450) NOT NULL,
[DateTime] [datetime2](7) NOT NULL,
[GenreId] [tinyint] NOT NULL,
[Venue] [nvarchar](255) NOT NULL,
CONSTRAINT [PK_Gigs] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Attendances] WITH CHECK ADD CONSTRAINT [FK_Attendances_AspNetUsers_AttendeeId] FOREIGN KEY([AttendeeId])
REFERENCES [dbo].[AspNetUsers] ([Id])
GO
ALTER TABLE [dbo].[Attendances] CHECK CONSTRAINT [FK_Attendances_AspNetUsers_AttendeeId]
GO
ALTER TABLE [dbo].[Attendances] WITH CHECK ADD CONSTRAINT [FK_Attendances_Gigs_GigId] FOREIGN KEY([GigId])
REFERENCES [dbo].[Gigs] ([Id])
GO
ALTER TABLE [dbo].[Attendances] CHECK CONSTRAINT [FK_Attendances_Gigs_GigId]
GO
ALTER TABLE [dbo].[Gigs] WITH CHECK ADD CONSTRAINT [FK_Gigs_AspNetUsers_ArtistId] FOREIGN KEY([ArtistId])
REFERENCES [dbo].[AspNetUsers] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Gigs] CHECK CONSTRAINT [FK_Gigs_AspNetUsers_ArtistId]
GO
ALTER TABLE [dbo].[Gigs] WITH CHECK ADD CONSTRAINT [FK_Gigs_Genres_GenreId] FOREIGN KEY([GenreId])
REFERENCES [dbo].[Genres] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Gigs] CHECK CONSTRAINT [FK_Gigs_Genres_GenreId]
GO
If you turn on EF Core Logging, you'll see inside the log something like this:
The Include operation for navigation: 'a.Gig.Artist' was ignored because the target navigation is not reachable in the final query results.
and similar for a.Gig.Genre.
Looks like EF Core at this time cannot handle includes for such queries (that don't start from the resulting entity). The only workaround I can propose is to rewrite the query like this:
var gigs = _context.Gigs
.Where(g => g.Attendances.Any(a => a.AttendeeId == userId))
.Include(g => g.Artist)
.Include(g => g.Genre)
.ToList();
or this (translates to better SQL, although the SQL execution plan could be the same):
var gigs = (from g in _context.Gigs
from a in g.Attendances
where a.AttendeeId == userId
select g)
.Include(g => g.Artist)
.Include(g => g.Genre)
.ToList();
Related
I using Entity Framework Core, and I have a table:
public class BlogComment
{
public int Id { get; set; }
public BlogPost Post { get; set; }
[StringLength(100)]
public string AuthorName { get; set; }
[StringLength(254)]
public string AuthorEmail { get; set; }
public bool SendMailOnReply { get; set; }
[StringLength(2000)]
public string Content { get; set; }
public DateTime CreatedTime { get; set; }
public int? ReplyToId { get; set; }
public BlogComment ReplyTo { get; set; }
}
From this, EFC generates the following table:
CREATE TABLE [dbo].[BlogComment] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[AuthorEmail] NVARCHAR (254) NULL,
[AuthorName] NVARCHAR (100) NULL,
[Content] NVARCHAR (2000) NULL,
[CreatedTime] DATETIME2 (7) NOT NULL,
[PostId] INT NULL,
[ReplyToId] INT NULL,
[SendMailOnReply] BIT NOT NULL,
CONSTRAINT [PK_BlogComment] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_BlogComment_BlogPost_PostId] FOREIGN KEY ([PostId]) REFERENCES [dbo].[BlogPost] ([Id]),
CONSTRAINT [FK_BlogComment_BlogComment_ReplyToId] FOREIGN KEY ([ReplyToId]) REFERENCES [dbo].[BlogComment] ([Id])
);
GO
CREATE NONCLUSTERED INDEX [IX_BlogComment_PostId]
ON [dbo].[BlogComment]([PostId] ASC);
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_BlogComment_ReplyToId]
ON [dbo].[BlogComment]([ReplyToId] ASC) WHERE ([ReplyToId] IS NOT NULL);
Some comments are send as a reply to another, but not all. When the original comment is deleted, the reply becomes a normal comment. So, following this tutorial, the configuration looks is this:
modelBuilder.Entity<BlogComment>()
.HasOne(p => p.ReplyTo)
.WithOne()
.HasForeignKey<BlogComment>(c => c.ReplyToId)
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
The delete method is pretty simple:
var comment = await context.BlogComment.Include(c => c.ReplyTo).SingleAsync(m => m.Id == id);
context.BlogComment.Remove(comment);
await context.SaveChangesAsync();
But I can't run it, I get an error:
System.Data.SqlClient.SqlException: The DELETE statement conflicted with the SAME TABLE REFERENCE constraint "FK_BlogComment_BlogComment_ReplyToId".
How can I fix this?
To wrap up the conversation in the comments:
First, the self reference is a 1:n association:
modelBuilder.Entity<BlogComment>()
.HasOne(p => p.ReplyTo)
.WithMany(c => c.Replies)
.HasForeignKey(c => c.ReplyToId)
.IsRequired(false)
.OnDelete(<we'll get to that>);
So, just for convenience, BlogComment now also has a property
public ICollection<BlogComment> Replies { get; set; }
However, I can't create the table using
.OnDelete(DeleteBehavior.SetNull);
It gives me
Introducing FOREIGN KEY constraint 'FK_BlogComments_BlogComments_ReplyToId' on table 'BlogComments' may cause cycles or multiple cascade paths.
This is a Sql Server restriction we just have to accept, no way to evade it. The only way to get the desired cascade behavior is
.OnDelete(DeleteBehavior.ClientSetNull);
Which is:
For entities being tracked by the DbContext, the values of foreign key properties in dependent entities are set to null. This helps keep the graph of entities in a consistent state while they are being tracked, such that a fully consistent graph can then be written to the database. (...) This is the default for optional relationships.
I.e.: the client executes SQL to nullify the foreign key values. The child records should be tracked though. To remove a BlogComment parent the delete action should look like:
using (var db = new MyContext(connectionString))
{
var c1 = db.BlogComments
.Include(c => c.Replies) // Children should be included
.SingleOrDefault(c => c.Id == 1);
db.BlogComments.Remove(c1);
db.SaveChanges();
}
As you see, you don't have to set ReplyToId = null, that's something EF takes care of.
For me, I had to Include() the entities I needed to be "dealt with" when I deleted an entity. EF cant manage things it is not currently tracking.
var breedToDelete = context.Breed
.Include(x => x.Cats)
.Single(x => x.Id == testBreedId);
context.Breed.Remove(breedToDelete);
context.SaveChanges();
I could get it working by manually setting ReplyTo to null. I'm still looking for a better solution, or an explanation why is it needed. Isn't it what OnDelete(DeleteBehavior.SetNull) supposed to do?
var comment = await context.BlogComment.Include(c => c.ReplyTo).SingleAsync(m => m.Id == id);
var reply = await context.BlogComment.SingleOrDefaultAsync(m => m.ReplyToId == id);
if (reply != null)
{
reply.ReplyTo = null;
reply.ReplyToId = null;
context.Entry(reply).State = EntityState.Modified;
}
context.BlogComment.Remove(comment);
I have models that have references to each other:
public class Dept
{
[Key]
public int DeptId { get; set; }
[ForeignKey("ManagerId")]
public Emp Manager { get; set; }
public int? ManagerId { get; set; }
public string DeptName { get; set; }
}
public class Emp
{
[Key]
public int EmpId { get; set; }
[Required]
[ForeignKey("DeptId")]
public Dept Dept { get; set; }
public int DeptId { get; set; }
public string Name { get; set; }
}
When I call Add-Migration, I get error:
The ForeignKeyAttribute on property 'Manager' on type 'App.Dept' is not valid.
The foreign key name 'ManagerId' was not found on the dependent type 'App.Emp'.
The Name value should be a comma separated list of foreign key property names.
What should I do to create migration with these tables?
UPD: Implicit optional Manager does not solve the problem:
modelBuilder.Entity<Emp>().HasRequired(_ => _.Dept).WithOptional(_ => _.Manager);
UPD2: Dept:Emp relation is 1:0..1
UPD3: Maybe another relation will be added to Dept model but it will be also 1:0..1:
[ForeignKey("ManagerId")]
public Emp CTO { get; set; }
public int? CTOId { get; set; }
It is not one to many relation: one department have zero or one manager, and zero or one CTO. At the moment I have only one relation, but I want to name the field ManagerId, not a EmpId.
UPD4:
Schema from the beginning of my question with two primary/foreign keys relation (Dept.DeptId/Emp.DeptId, Emp.EmpId/Dept.ManagerId) works in plain SQL. I know workarounds with additional table or without foreign keys, but I need an answer how to make work schema above or why it is not working in EF.
You have mainly three way to configure 1-1 relationships (the case of your error is the 3rd explained case).
Complex types
The first way is with only one table and using complex types. Choosing this configuration has performance impact (often, overall performances are better than other configurations but it depends on the record size and on how many times you have both records).
In your case you need only to mark one of the entities with ComplexType attribute
public class Dept
{
[Key]
public int DeptId { get; set; }
public Emp Manager { get; set; }
public string DeptName { get; set; }
}
[ComplexType]
public class Emp
{
public int EmpId { get; set; } // You can still have this property but it will not be a primary key
public string Name { get; set; }
}
With this model this is the created table
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [Manager_EmpId] int not null
, [Manager_Name] text null
, [DeptName] text null
);
Standard foreign key
The second way is to use a standard foreign key. The model can have navigation properties on both classes, has 2 tables with independent primary keys but only 1 table has the foreign key to the other table (you wrote about this configuration on your question). You obtain this configuration overriding OnModelCreating.
Using this way there are several configurations you can do using fluent API. The main option is where should EF insert the foreign key.
In every configuration you must have Map method (I explain what happens without the Map method in the third way)
The model is always this
public class Dept
{
[Key]
public int DeptId { get; set; }
public Emp Manager { get; set; }
public string DeptName { get; set; }
}
public class Emp
{
[Key]
public int EmpId { get; set; }
public Dept Department { get; set; }
public string Name { get; set; }
}
WithRequiredPrincipal (1-1)
From MSDN:
Configures the relationship to be required:required without a navigation property on the other side of the relationship. The entity type being > configured will be the principal in the relationship. The entity type that the relationship targets will be the dependent and contain a foreign > key to the principal.
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredPrincipal(_ => _.Department)
.Map(_ => _.MapKey("DepartmentId"));
This is the DDL generated
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_c0491d33] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
, [DepartmentId] int not null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_c0491d33] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_DepartmentId] ON [Emps] ([DepartmentId])
ExecuteNonQuery==========
ALTER TABLE [Emps] ADD CONSTRAINT [FK_Emps_Depts_DepartmentId] FOREIGN KEY ([DepartmentId]) REFERENCES [Depts] ([DeptId])
WithRequiredDependent (1-1)
From MSDN:
Configures the relationship to be required:required without a navigation property on the other side of the relationship.
[For me is not clear this explanation, anyway, for the real behaviour see below]
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredDependent(_ => _.Department)
.Map(_ => _.MapKey("EmpId"));
This is the DDL generated
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
, [EmpId] int not null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_bebceea2] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_bebceea2] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_EmpId] ON [Depts] ([EmpId])
ExecuteNonQuery==========
ALTER TABLE [Depts] ADD CONSTRAINT [FK_Depts_Emps_EmpId] FOREIGN KEY ([EmpId]) REFERENCES [Emps] ([EmpId])
WithOptional (1-0..1)
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithOptional(_ => _.Department)
.Map(_ => _.MapKey("ManagerId"));
This is the DDL generated
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
, [ManagerId] int not null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_ee5245bb] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_ee5245bb] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_ManagerId] ON [Depts] ([ManagerId])
ExecuteNonQuery==========
ALTER TABLE [Depts] ADD CONSTRAINT [FK_Depts_Emps_ManagerId] FOREIGN KEY ([ManagerId]) REFERENCES [Emps] ([EmpId])
You can use other methods to obtain similar configurations. I dont show here every example but we can mix these configurations
HasOptional/WithRequired
HasOptional/WithOptionalDependent
HasOptional/WithOptionalPrincipal
EF Default 0..1-1 1-0..1 1-1 configuration
This is how EF is interpreting your configuration. In this case EF generates 2 tables with dependent primary keys. On one table there is an independent primary key (in your case identity(1,1)) and on the other table you have a primary key that is also the foreign key. This is the default configuration. This is the only way to have foreign keys on both tables (not 2 constraints, no way to have 2 circular contraints, see below)
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredPrincipal(_ => _.Department);
This is the DDL generated
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_b91ed7c4] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_b91ed7c4] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_EmpId] ON [Emps] ([EmpId])
ExecuteNonQuery==========
ALTER TABLE [Emps] ADD CONSTRAINT [FK_Emps_Depts_EmpId] FOREIGN KEY ([EmpId]) REFERENCES [Depts] ([DeptId])
This should be 1-1 relationship but if we look better there is one missing constraint. The primary key of the Dept table should be a foreign key for the second table. Why EF did not insert that constraint? Because we will violate always a constraint so we could not insert records on tables (also inside a transaction a reference key constraint can be violated).
Changing the configuration to HasRequired/WithRequiredDependent we obtain the table with the independent primary key will be the Emps table
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredDependent(_ => _.Department);
This is the DDL generated
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null
, [DeptName] text null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_58ab8622] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_58ab8622] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_DeptId] ON [Depts] ([DeptId])
ExecuteNonQuery==========
ALTER TABLE [Depts] ADD CONSTRAINT [FK_Depts_Emps_DeptId] FOREIGN KEY ([DeptId]) REFERENCES [Emps] ([EmpId])
You can use other methods to obtain similar configurations. I dont show here every example but we can mix these configurations
HasOptional/WithRequired
HasOptional/WithOptionalDependent
HasOptional/WithOptionalPrincipal
From your class code you have the following
For 1-1 and 1-0..1, same primary key should appear in both tables and in your design its not the case since both tables will have their own primary key
Now, based on the code you put, the configuration should be as follows
modelBuilder.Entity<Dept>()
.HasKey(t => t.DeptId)
.HasOptional(t => t.Manager)
.WithRequired(t => t.Dept);
but this will not mean a 1-1 or 1-0..1 relation.
if you want to convert your codes to become 1-0..1, then you code should be like this
Remove the EmpId from the class Emp
The configuration should look like
modelBuilder.Entity<Emp>()
.HasKey(t => t.DeptId)
.HasRequired(t => t.Dept);
modelBuilder.Entity<Dept>()
.HasKey(t => t.DeptId)
.HasOptional(t => t.Manager)
.WithRequired(t => t.Dept);
modelBuilder.Entity<Dept>()
.HasOptional(t => t.Manager)
.WithMany()
.HasForeignKey(t => t.ManagerId)
.WillCascadeOnDelete(false);
for more information about the relations, you might read this article Configure One-to-Zero-or-One Relationship:
Hope this will help you
I have a (strange) situation.
I am using Entity Framework Code First but I have to attach to an existing Database.
I do not have to map every single table of the database in my object model. So I would like to migrate single Tables, whenever I need it.
I try to explain better. My database have about 100 tables, but I need to map in my model just 3 or 4. I have created my classes in c# and now I would like to map this classes with the tables I need.
Is it possible to do it? Do I have to do a migration?
UPDATE
Here my class:
public class PricePlan
{
public Guid Id { get; set; }
public String Name { get; set; }
public Double ActivationPrice { get; set; }
}
Here the context:
public class PublicAreaContext : DbContext
{
public DbSet<PricePlan> PricePlans { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<PricePlan>()
.HasKey(pp => new { pp.Id });
base.OnModelCreating(modelBuilder);
}
}
Here the table:
ALTER TABLE [dbo].[PricePlan](
[Id] [uniqueidentifier] NOT NULL,
[Name] [varchar](50) NULL,
[ActivationPrice] [decimal](5, 2) NULL,
... //Other columns
CONSTRAINT [PK_Price_Plans] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
possible: yes
migration: no. If you need migration you may have problem as in this case you haven't the __migrationHistory table (as the db is "existing" by opposite to "created by EF").
But the answer is definitively yes.
Create your classes, create a DbContext comprising DbSet, "et voilĂ ".
I've just noticed a rather strange behaviour with how Entity Framework creates a Code-First DB when you have a model with a parent that has a list of children, but also an optional navigation property to one particular child: I end up with an additional nullable foreign key column on the child that I'm not expecting. Can anyone please explain whether this column is actually necessary? And for that matter, can anyone suggest a better way of indicating that a particular child is Selected/Active.
To elaborate:
Given this model:
public class Parent
{
public int Id { get; set; }
public virtual List<Child> Children { get; set; }
// Optional navigation property to one of the child objects.
public int? ActiveChildId { get; set; }
public virtual Child ActiveChild { get; set; }
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
I end up with the following DB:
CREATE TABLE [dbo].[Parents](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ActiveChildId] [int] NULL,
CONSTRAINT [PK_dbo.Parents] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Parents] WITH CHECK ADD CONSTRAINT [FK_dbo.Parents_dbo.Children_ActiveChildId] FOREIGN KEY([ActiveChildId])
REFERENCES [dbo].[Children] ([Id])
GO
ALTER TABLE [dbo].[Parents] CHECK CONSTRAINT [FK_dbo.Parents_dbo.Children_ActiveChildId]
GO
CREATE TABLE [dbo].[Children](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ParentId] [int] NOT NULL,
[Parent_Id] [int] NULL,
CONSTRAINT [PK_dbo.Children] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Children] WITH CHECK ADD CONSTRAINT [FK_dbo.Children_dbo.Parents_Parent_Id] FOREIGN KEY([Parent_Id])
REFERENCES [dbo].[Parents] ([Id])
GO
ALTER TABLE [dbo].[Children] CHECK CONSTRAINT [FK_dbo.Children_dbo.Parents_Parent_Id]
GO
ALTER TABLE [dbo].[Children] WITH CHECK ADD CONSTRAINT [FK_dbo.Children_dbo.Parents_ParentId] FOREIGN KEY([ParentId])
REFERENCES [dbo].[Parents] ([Id])
GO
ALTER TABLE [dbo].[Children] CHECK CONSTRAINT [FK_dbo.Children_dbo.Parents_ParentId]
GO
i.e. there is both a ParentId (NOT NULL) column on the child AND a Parent_Id (NULL) column on the child.
It seems to me that since we've already got a 1:N foreign key relationship Parent to Child, then by adding a one-way 1:[0 or 1] Parent to Child relationship it shouldn't create another foreign key column on the child.
Add the InverseProperty attribute:
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
[InverseProperty( "Children" )]
public virtual Parent Parent { get; set; }
}
Or map the relationship via Fluent API and specify the FK as ParentId:
modelBuilder.Entity<Parent>()
.HasMany( p => p.Children )
.WithRequired( c => c.Parent )
.HasForeignKey( c => c.ParentId );
EF 5.0
I am working on a prototype to test hierarchyid and entity framework together. I have the following schema:
Create Table dbo.Employee
(
EmployeeId int identity not null,
Name nvarchar(100) not null,
Node hierarchyid not null,
NodePath as Node.ToString() persisted,
Level AS Node.GetLevel() persisted,
ManagerNode as Node.GetAncestor(1) persisted,
ManagerNodePath as Node.GetAncestor(1).ToString() persisted
);
Alter Table dbo.Employee
Add Constraint EmployeePK Primary Key NonClustered (EmployeeId);
Go
--Enforce Hierarchy
Alter Table dbo.Employee
Add Constraint EmployeeManagerNodeNodeFK Foreign Key (ManagerNode) References Employee(Node);
Go
Create Unique Clustered Index EmployeeDepthFirstIndex on dbo.Employee(Node);
Go
Create NonClustered Index EmployeeBreathFirstIndex on dbo.Employee(Level, Node);
Go
From my reading, the hierarchyid datatype isn't currently supported in EF, but some have suggested workarounds such as creating calculated columns (Node.ToString()) which I have done above.
Is there a way to setup EF so that it recognizes the Parent/Child relationship so I can effectively have a subordinates collection? e.g.
Employee.Subordinates
The only thing I can think of is to create a ManagerId column w/ a FK, but then I am effectively storing the hierarchy in two places.
Thanks for any help!
EF6 is now open source, so it is easy to add HierarcyID support. I have added it, too.
You can download the modifed source and the complied/signed dlls from codeplex:
http://entityframework.codeplex.com/SourceControl/network/forks/zgabi/efhierarchyidrc1 (sometimes the fork name changes)
Or from NuGet: https://www.nuget.org/packages/EntityFrameworkWithHierarchyId/
Currenty EF6 is in RC1 state, but I'll merge the modifications to every later releases of EF6.
I have the following model:
public class Employee
{
public int EmployeeId { get; set; }
[Required, MaxLength(100)]
public string Name { get; set; }
[Required]
public HierarchyId Node { get; set; }
public IQueryable<Employee> GetSubordinates(MyContext context)
{
return context.Employees.Where(o => Node == o.Node.GetAncestor(1));
}
}
public class MyContextInitializer : CreateDatabaseIfNotExists<MyContext>
{
protected override void Seed(MyContext context)
{
context.Database.ExecuteSqlCommand(
"ALTER TABLE [dbo].[Employees] ADD [ManagerNode] AS ([Node].[GetAncestor]((1))) PERSISTED");
context.Database.ExecuteSqlCommand(
"ALTER TABLE [dbo].[Employees] ADD CONSTRAINT [UK_EmployeeNode] UNIQUE NONCLUSTERED (Node)");
context.Database.ExecuteSqlCommand(
"ALTER TABLE [dbo].[Employees] WITH CHECK ADD CONSTRAINT [EmployeeManagerNodeNodeFK] " +
"FOREIGN KEY([ManagerNode]) REFERENCES [dbo].[Employees] ([Node])");
context.Employees.Add(new Employee { Name = "Root", Node = new HierarchyId("/") });
context.Employees.Add(new Employee { Name = "Emp1", Node = new HierarchyId("/1/") });
context.Employees.Add(new Employee { Name = "Emp2", Node = new HierarchyId("/2/") });
context.Employees.Add(new Employee { Name = "Emp3", Node = new HierarchyId("/1/1/") });
context.Employees.Add(new Employee { Name = "Emp4", Node = new HierarchyId("/1/1/1/") });
context.Employees.Add(new Employee { Name = "Emp5", Node = new HierarchyId("/2/1/") });
context.Employees.Add(new Employee { Name = "Emp6", Node = new HierarchyId("/1/2/") });
}
}
public class MyContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
}
Generated database:
CREATE TABLE [dbo].[Employees](
[EmployeeId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](100) NOT NULL,
[Node] [hierarchyid] NOT NULL,
[ManagerNode] AS ([Node].[GetAncestor]((1))) PERSISTED,
CONSTRAINT [PK_dbo.Employees] PRIMARY KEY CLUSTERED
(
[EmployeeId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [UK_EmployeeNode] UNIQUE NONCLUSTERED
(
[Node] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[Employees] WITH CHECK ADD CONSTRAINT [EmployeeManagerNodeNodeFK] FOREIGN KEY([ManagerNode])
REFERENCES [dbo].[Employees] ([Node])
Example to get the child nodes of Emp1 employee:
using (var c = new MyContext())
{
var firstItem = c.Employees.Single(o => o.Node == new HierarchyId("/1/"));
foreach (var table1 in firstItem.GetSubordinates(c))
{
Console.WriteLine(table1.EmployeeId + " " + table1.Name);
}
}
result:
4 Emp3
7 Emp6
Using varbinary(892) instead of hierarchyid.
EF recognizes varbinary returning byte array.
You can convert byte array to SqlHierarchyid type and use hyrarchy pod functions.
With this workaround you can use hierarchyid functions even in other databases.
See http://www.casavillar.com.br/blog with more details and links to nugget and github where you will find samples including MySql