Tables
CREATE TABLE [dbo].[Users](
[UserId] [int] IDENTITY(1,1) NOT NULL,
[UserName] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Email] [varchar](255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[BirthDate] [smalldatetime] NULL,
[CountryId] [int] NULL,
CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED
([UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[TeamMember](
[UserId] [int] NOT NULL,
[TeamMemberUserId] [int] NOT NULL,
[CreateDate] [smalldatetime] NOT NULL CONSTRAINT [DF_TeamMember_CreateDate]
DEFAULT (getdate()),
CONSTRAINT [PK_TeamMember] PRIMARY KEY CLUSTERED
([UserId] ASC,
[TeamMemberUserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
dbo.TeamMember has both UserId and TeamMemberUserId as the index key.
My goal is to show a list of Users on my View. In the list I want to flag, or highlight the Users that are Team Members of the LoggedIn user.
My ViewModel
public class UserViewModel
{
public int UserId { get; private set; }
public string UserName { get; private set; }
public bool HighLight { get; private set; }
public UserViewModel(Users users, bool highlight)
{
this.UserId = users.UserId;
this.UserName = users.UserName;
this.HighLight = highlight;
}
}
View
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<MvcPaging.IPagedList<MyProject.Mvc.Models.UserViewModel>>" %>
<% foreach (var item in Model) { %>
<%= item.UserId %>
<%= item.UserName %>
<%if (item.HighLight) { %>
Team Member
<% } else { %>
Not Team Member
<% } %>
How do I toggle the TeamMember or Not
If I add dbo.TeamMember to the EDM, there are no relationships on this table, how will I wire it to Users object?
So I am comparing the LoggedIn UserId with this list(SELECT TeamMemberUserId FROM TeamMember WHERE UserId = #LoggedInUserId)
EDIT
TeamMemberUserId is the UserId of the User that is a team member.
I named the table Users to avoid confusion with the System.We.Security User class.
I presume TeamMemberUserId is really the TeamId?
Why is there no relationship between the tables?
Why is pluralization messed up? User vs Users?
Sounds like you need something like:-
int userId = ... user Id for current user
var teams = context.TeamMember.Where(tm => tm.UserId == userId);
foreach (User other in ...)
{
int otherId = other.UserId;
bool highlight = teams.Any(team => context.TeamMember.Any(tm => tm.TeamId == team.TeamId && tm.UserId == otherId ));
...
}
Which would be much cleaner if you had the FK relationship in there.
Related
I make a very simple test, and right the method to click run test, but I have no idea why the table will insert two record
public class BookDbContext : DbContext
{
public BookDbContext(DbContextOptions<BookDbContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Books>().HasKey(x => x.ID)
.HasName("PK_Books");
modelBuilder.Entity<Books>().Property(x => x.ID)
.ValueGeneratedOnAdd();
base.OnModelCreating(modelBuilder);
}
}
Unit Test method :
[Fact]
public void SimpleTest()
{
var contextOptions = new DbContextOptionsBuilder<BookDbContext>()
.UseSqlServer("server=192.168.0.100;database=BookDB;User Id=sa;Password=password;Connection Timeout=120;TrustServerCertificate=True")
.Options;
BookDbContext db = new(contextOptions);
var book = new Books()
{
BookName = "My Book"
};
db.Add(book);
db.SaveChanges();
}
Table structure:
CREATE TABLE [dbo].[Books](
[Id] [int] IDENTITY(1,1) NOT NULL,
[BookName] [nvarchar](100) NULL,
CONSTRAINT [PK_Books] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON,
OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] ) ON [PRIMARY] GO
I have make a breakpoint, it just just run one time, but after check with table, it appear as two record
can I know reason?
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 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'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