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 );
Related
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();
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 have create a table with this schema :
CREATE TABLE [dbo].[A](
[KeyID] [uniqueidentifier] NOT NULL,
[OtherID] [uniqueidentifier] NULL,
[Info] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_TX_A] PRIMARY KEY CLUSTERED
(
[KeyID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
I have created this POCO Class :
public partial class A
{
public A()
{
}
public System.Guid KeyID { get; set; }
public Nullable<System.Guid> OtherID{ get; set; }
public string Info{ get; set; }
}
When I try to insert a new line in my table with Entity Framework :
[TestMethod]
public void TestAdd( )
{
A a = new A( );
Guid myKey = Guid.Parse("9B3CA1AC-279F-48CE-B693-D5329FF3AD14");
a.OtherID = myKey;
a.KeyID = myKey;
a.Info = "Test";
using( var database = new myConnection( ) )
{
database.A.Add( a );
database.SaveChangesAsync( ).Wait( );
}
}
It produces this SQL command :
DECLARE #generated_keys table([A] uniqueidentifier)
INSERT [dbo].[A]([OtherID], [Info])
OUTPUT inserted.[KeyID] INTO #generated_keys
VALUES (#0, #1)
SELECT t.[KeyID]
FROM #generated_keys AS g JOIN [dbo].[A] AS t ON g.[KeyID] = t.[KeyID]
WHERE ##ROWCOUNT > 0
Then it generates this error :
Can not insert the value NULL into column 'KeyID', table 'A. This column does not accept NULL values. INSERT failed.
It seems it doesn't take into account the primary key ID specified and it seems to believe that the primary key is auto incremented.
But this SQL statement works fine in SQL Server :
INSERT INTO A (KeyID, OtheriD, Info)
VALUES('9B3CA1AC-279F-48CE-B693-D5329FF3AD14', '9B3CA1AC-279F-48CE-B693-D5329FF3AD14', 'test')
Do you know what's wrong ?
Thanks for your help.
Your Entity Framework configuration is set up for database-generated primary key columns, that's why you also see in the SQL how EF attempts to retrieve the new KeyID value even though it hasn't set it. Your primary key column isn't database-generated, so this can never work.
If you let EF generate your database for you, you would have got a matching one. If you create your database manually, it has to match what EF thinks it should be, or you get errors like this.
You can chance your model to not treat the key column as database-generated with the DatabaseGenerated attribute, specifying DatabaseGeneratedOption.None.
use this code :
public void TestAdd( )
{
A a = new A( );
Guid myKey = Guid.NewGuid();//use this
a.OtherID = myKey;
a.KeyID = myKey;
a.Info = "Test";
using( var database = new myConnection( ) )
{
database.A.Add( a );
database.SaveChangesAsync( ).Wait( );
}
}
I have a tiny database with one tiny Table.
CREATE TABLE [dbo].[Connections](
[connectionID] [bigint] IDENTITY(1,1) NOT NULL,
[ipAddress] [varchar](50) NULL,
[ConnectionGUID] [varchar](100) NULL,
[created] [datetime] NULL,
[registrationID] [varchar](100) NULL,
CONSTRAINT [PK_Connections] PRIMARY KEY CLUSTERED
(
[connectionID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
When I generate the model from the database, it generates:
public virtual DbSet<Connections> Connections { get; set; }
of which:
public partial class Connections
{
public long connectionID { get; set; }
public string ipAddress { get; set; }
public string ConnectionGUID { get; set; }
public Nullable<System.DateTime> created { get; set; }
public string registrationID { get; set; }
}
I don't know why it generated the DbSet instead of the single class
When I am trying to fill the class and SaveChanges, how do I access the DbSet?
using (savitassaEntities3 entities3 = new savitassaEntities3())
{
entities3.Connections conn = new Connections();
}
It's also colliding with the SignalR class Connections as this is aSignalR Hub.
From a database perspective, think of the DbSet as the Table and the class (Connections) as a row in that table. The DbSet is included in a DbContext class which you can think of as your Database.
As for the name collision with Connections, make sure to fully namespace qualify it. entities3.Connections conn = new MyProject.MyNamespace.Connections();
Thus to select records, you would do something along the lines of
var cn = new savitassaEntities3();
var query = cn.Connections.Where(c => c.foo == bar);
To insert a connection, use
cn.Connections.Add(value);
cn.SaveChanges();
If I'm missing what you're trying to do and what you have, feel free to add some more code and I can take a look.
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