Many-To-Many Query with Linq-To-NHibernate - c#

Ok guys (and gals), this one has been driving me nuts all night and I'm turning to your collective wisdom for help.
I'm using Fluent Nhibernate and Linq-To-NHibernate as my data access story and I have the following simplified DB structure:
CREATE TABLE [dbo].[Classes](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](100) NOT NULL,
[StartDate] [datetime2](7) NOT NULL,
[EndDate] [datetime2](7) NOT NULL,
CONSTRAINT [PK_Classes] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
CREATE TABLE [dbo].[Sections](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[ClassId] [bigint] NOT NULL,
[InternalCode] [varchar](10) NOT NULL,
CONSTRAINT [PK_Sections] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
CREATE TABLE [dbo].[SectionStudents](
[SectionId] [bigint] NOT NULL,
[UserId] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_SectionStudents] PRIMARY KEY CLUSTERED
(
[SectionId] ASC,
[UserId] ASC
)
CREATE TABLE [dbo].[aspnet_Users](
[ApplicationId] [uniqueidentifier] NOT NULL,
[UserId] [uniqueidentifier] NOT NULL,
[UserName] [nvarchar](256) NOT NULL,
[LoweredUserName] [nvarchar](256) NOT NULL,
[MobileAlias] [nvarchar](16) NULL,
[IsAnonymous] [bit] NOT NULL,
[LastActivityDate] [datetime] NOT NULL,
PRIMARY KEY NONCLUSTERED
(
[UserId] ASC
)
I omitted the foreign keys for brevity, but essentially this boils down to:
A Class can have many Sections.
A Section can belong to only 1 Class but can have many Students.
A Student (aspnet_Users) can belong to many Sections.
I've setup the corresponding Model classes and Fluent NHibernate Mapping classes, all that is working fine.
Here's where I'm getting stuck. I need to write a query which will return the sections a student is enrolled in based on the student's UserId and the dates of the class.
Here's what I've tried so far:
1.
var sections = (from s in this.Session.Linq<Sections>()
where s.Class.StartDate <= DateTime.UtcNow
&& s.Class.EndDate > DateTime.UtcNow
&& s.Students.First(f => f.UserId == userId) != null
select s);
2.
var sections = (from s in this.Session.Linq<Sections>()
where s.Class.StartDate <= DateTime.UtcNow
&& s.Class.EndDate > DateTime.UtcNow
&& s.Students.Where(w => w.UserId == userId).FirstOrDefault().Id == userId
select s);
Obviously, 2 above will fail miserably if there are no students matching userId for classes the current date between it's start and end dates...but I just wanted to try.
The filters for the Class StartDate and EndDate work fine, but the many-to-many relation with Students is proving to be difficult. Everytime I try running the query I get an ArgumentNullException with the message:
Value cannot be null.
Parameter name: session
I've considered going down the path of making the SectionStudents relation a Model class with a reference to Section and a reference to Student instead of a many-to-many. I'd like to avoid that if I can, and I'm not even sure it would work that way.
Thanks in advance to anyone who can help.
Ryan

For anyone who cares, it looks like the following might work in the future if Linq-To-NHibernate can support subqueries (or I could be totally off-base and this could be a limitation of the Criteria API which is used by Linq-To-NHibernate):
var sections = (from s in session.Linq<Section>()
where s.Class.StartDate <= DateTime.UtcNow
&& s.Class.EndDate > DateTime.UtcNow
&& s.Students.First(f => f.UserId == userId) != null
select s);
However I currently receive the following exception in LINQPad when running this query:
Cannot use subqueries on a criteria
without a projection.
So for the time being I've separated this into 2 operations. First get the Student and corresponding Sections and then filter that by Class date. Unfortunately, this results in 2 queries to the database, but it should be fine for my purposes.

Related

C# Linq To SQL insert a single big row performance issue

I have a program that use old Linq To SQL to connect an ASP.NET application to a SQL Server DB.
ASP.NET application and SQL Server instance are on the same machine, and both "environment" are upadated (IIS 10, NET Framework 4.8 and SQL Server 2019).
In the software i have to handle a virtual Cart with the customer order. Cart has many field, one of them is a nvarchar and contains the "cart document" a stirng that tipically is few KB, but sometime may reach few MB (never more than 10MB)
When i udpate a document string in the reange of 2-3MB, and then update the single row that contains it, the udpate operation is really, really slow (2-2,5s).
Here update code :
protected void Upsert(CartDto cart, bool isValidationUpsert = false )
{
lock (_sync)
{
if ((cart?.Id ?? 0) <= 0)
throw new ExtendedArgumentException("cartId");
using (var dbContext = ServiceLocator.ConnectionProvider.Instace<CartDataContext>())
{
var repository = new CartRepository(dbContext);
var existingCart = repository.Read(crt => crt.ID == cart.Id).FirstOrDefault();
if (existingCart == null)
{
existingCart = new tbl_set_Cart();
existingCart.Feed(cart);
repository.Create(existingCart);
}
else
{
existingCart.Feed(cart);
repository.Update(existingCart);
}
dbContext.SubmitChanges(); //<<--- This speecifi operation will take 2-2,5s previous instructions take a neglectable time
}
}
}
I have no idea about the why, nor how to improve performance in this scenario
--EDITED :
as suggested, i have profiled the oepration on the DB and experienced the same delay (~2,5) event if i run the SQL code directly onto SQL Server (using SSMS to connect and execute code).
Here SQL code and perforamance statistics :
DECLARE #p0 AS INT = [cart_id];
DECLARE #p1 AS INT = [entry_count];
DECLARE #p2 AS NVARCHAR(MAX) = '..document..';
UPDATE [dbo].[tbl_set_Cart]
SET [ITEMS_COUNT] = #p1, [ITEMS] = #p2
WHERE [ID] = #p0
Here my table schema, as you can see nothing it's very simple :
/****** Object: Table [dbo].[tbl_set_Cart] Script Date: 02/12/2021 15:44:07 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[tbl_set_Cart](
[ID] [int] NOT NULL,
[AS400_CUSTOMER_COD] [nvarchar](50) NOT NULL,
[AS400_LISTIN] [int] NOT NULL,
[VALUE] [nvarchar](max) NOT NULL,
[DELIVERY_COSTS] [nvarchar](max) NOT NULL,
[ITEMS_COUNT] [int] NOT NULL,
[ITEMS] [nvarchar](max) NOT NULL,
[KIND] [int] NOT NULL,
[CHECKOUT_INFO] [nvarchar](max) NOT NULL,
[ISSUES] [nvarchar](max) NOT NULL,
[LAST_CHECK] [datetime] NOT NULL,
[USER_ID] [int] NOT NULL,
[IMPERSONATED_USER_ID] [int] NOT NULL,
[OVERRIDE_PRICES] [bit] NOT NULL,
[HAS_ISSUE] [bit] NOT NULL,
[IS_CONFIRMED] [bit] NOT NULL,
[IS_COLLECTED] [bit] NOT NULL,
[_METADATA] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_tbl_set_Cart] 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] TEXTIMAGE_ON [PRIMARY]
GO
After investigating deeper the DB profiling with the help of DBA Stack Overflow users (here the discussion https://dba.stackexchange.com/questions/303400/sql-server-how-to-upload-big-json-into-column-performance-issue/303409#303409) turn out to be an issue probably related to disk.
Because some production system hit the same problem as my development machine i ask how to improve performance and recevied the beautiful tips of store the compressed version of my data.
Data are not to big (in my scanrio) to be too slow for an in-memory at runtime compression/decompression, and that drammatically reduce the time (LZMA used).
From 2,5s to ~0,3 a really good improvement.
Thanks to all for precious help and tips.

One to One relation in Entity frame work Insert

I have 2 tables, Users and Employees
CREATE TABLE [dbo].[Users](
[UserID] [int] IDENTITY NOT NULL,
[Username] [nvarchar](8) NOT NULL,
[Activo] [bit] NOT NULL,
[UltimoAcesso] [datetime] NULL,
PRIMARY KEY (UserID)
)
CREATE TABLE [dbo].[Employees](
[ColaboradorID] [int] IDENTITY(1,1) NOT NULL,
[Nome] [nvarchar](100) NOT NULL,
[Email] [nvarchar](100) NULL,
[UserID] [int] NULL
PRIMARY KEY(ColaboradorID),
UNIQUE (UserID)
)
ALTER TABLE [dbo].[Employees] WITH CHECK ADD CONSTRAINT [FK_Employees_UtilizadorID] FOREIGN KEY([UserID])
REFERENCES [dbo].[Users] ([UserID])
ON UPDATE CASCADE
ON DELETE CASCADE
I'm using Entity FrameWork Database first.
I'm trying to insert a new user
public void fvAddUser_InsertItem()
{
var item = new InventarioCiclico.Users();
using (InventarioCiclicoContext db = new InventarioCiclicoContext())
{
Employee c = new Employee ();
c.Nome = (fvAddUser.FindControl("txtNome") as TextBox).Text;
c.Email = (fvAddUser.FindControl("txtEmail") as TextBox).Text;
item.Employee.Add(c);
var employee = db.Set<Employee>();
TryUpdateModel(item);
if (ModelState.IsValid)
{
if (db.Users.Any(u => u.Username == item.Username))
{
// Handle exception
}
else
{
db.Users.Add(item);
db.SaveChanges();
var userID = item.UserID;
c.UserID = userID;
employee.Add(c);
db.SaveChanges();
}
}
}
}
However it keeps giving me exception of violation of unique value? Before starting with entity framework I would insert on Users table first, get scope_identity and insert on Employee table after and I'm trying to do this using EF6 but i don't know what can i do about this.
You are adding two employees with the same UserId in the database and since UserId is a unique field in employee table you are getting the exception of violation of unique value.
In the line item.Employee.Add(c); you are add the employee to the user, therefore, when adding the user to the database, the employee will be added two. So you don't need the last three lines:
c.UserID = userID;
employee.Add(c);
db.SaveChanges();

Retrieve and iterate data using JOIN in C# MVC4

I am not getting how to iterate data that is retrieved using join.
Table project images
[img_id] INT IDENTITY (1, 1) NOT NULL,
[proj_id] INT NOT NULL,
[path] NVARCHAR (50) NOT NULL,
PRIMARY KEY CLUSTERED ([img_id] ASC),
CONSTRAINT [FK_projimg_projects] FOREIGN KEY ([proj_id]) REFERENCES [dbo].[Projects] ([proj_id])
Table Projects
[proj_id] INT IDENTITY (1, 1) NOT NULL,
[proj_name] NVARCHAR (50) NOT NULL,
[step1] NVARCHAR (MAX) NOT NULL,
[step2] NVARCHAR (MAX) NOT NULL,
[step3] NVARCHAR (MAX) NOT NULL,
[step4] NVARCHAR (MAX) NOT NULL,
[user_id] INT NOT NULL,
[materials] NVARCHAR (MAX) NOT NULL,
[tag] NVARCHAR (50) NOT NULL,
PRIMARY KEY CLUSTERED ([proj_id] ASC),
CONSTRAINT [FK_Projects_user] FOREIGN KEY ([user_id]) REFERENCES [dbo].[Users] ([user_id])
i retrieved the data using following query
var tutorial = from proj in de.Projects
join image in de.projimgs
on proj.proj_id equals image.proj_id
select new {
proj.proj_name,
proj.materials,
proj.step1,
proj.step2,
proj.step3,
proj.step4,
image.path,
};
and now i want to iterate the data, each project containing multiple images, how do i show those images in single iteration of foreach loop. Can anyone help clearly.
Thankx in advance.
Well you can iterate then in two foreach loop like
foreach(project p in tutorial)
{
foreach(image in p.Images)
{
//Do your processing
}
}
First, if you have your foreign keys set up as mapped properties, Rahul's answer is the easiest. If you actually need to do a join, your query isn't quite right.
Keep in mind what your SQL statement is doing. When you do an INNER JOIN, you're asking for one result per combination of values. Your query is equivalent (roughly) to:
SELECT proj.proj_name, proj.proj_name, proj.materials, proj.step1, proj.step2, proj.step3, proj.step4, image.path
FROM Project proj
INNER JOIN Project_Images image ON image.ProjectId = proj.Id
Given your select statement, you will get back multiple copies of the project - one for each image. You just loop over those results.
It sounds like what you wrote is incorrect and what you actually want is a group by:
var tutorial = from proj in de.Projects
join image in de.projimgs on proj.proj_id equals image.proj_id
group image by proj into groupedImages
select new { Project = groupedImages.Key, Images = groupedImages };
Then you loop over it:
foreach (var project in tutorial)
{
// Do what you want with project here
foreach (var image in project.Images)
{
// Do what you want with image here
}
}

C# & SQL Server stored procedure - updating entity with a many-to-many relationship

At one point, there was a Node entity that had a field Alias. However, things have changed, and now we have modified that data model so that a Node can have multiple Aliases, and therefore, a Node now support an array of Aliases (which in turn creates an Aliases table, and an AliasesNodes table that maps the many-to-many relationship).
My question is, how do I update my stored procedure so that it supports this, and how do I modify the c# code that calls this stored procedure?
The requirements from the procedure is to only insert a Node if it doesn't exists, for each Alias, only insert if it doesn't exist, and finally, create the relationships between the Node and its Aliases.
OLD working stored procedure when Alias was a field (before an Alias table existed):
CREATE TYPE [dbo].[NodeTableType] AS TABLE
(
[Id] [int] NOT NULL,
[NodeTypeId] [smallint] NOT NULL,
[Location] [nvarchar](50) NULL,
[DisplayName] [nvarchar](100) NULL,
[AccessLevel] [smallint] NOT NULL,
[IsEnabled] [bit] NOT NULL,
[CreatedOn] [datetime2](7) NULL,
[CreatedBy] [nvarchar](150) NULL,
[ModifiedOn] [datetime2](7) NULL,
[ModifiedBy] [nvarchar](150) NULL,
[NativeId] [bigint] NOT NULL,
[SourceId] [int] NOT NULL,
[Name] [nvarchar](100) NOT NULL,
[Alias] [nvarchar](100) NULL
);
CREATE PROCEDURE [dbo].[InsertNonExistingNode]
(#TableVariable dbo.NodeTableType READONLY)
AS
BEGIN
INSERT INTO NWatchNodes WITH (ROWLOCK) (Id, NodeTypeId, Location, DisplayName,
AccessLevel, IsEnabled, CreatedOn, CreatedBy,
ModifiedOn, ModifiedBy, NativeId, SourceId, Name, Alias)
SELECT
t.Id, t.NodeTypeId, t.Location, t.DisplayName,
t.AccessLevel, t.IsEnabled, t.CreatedOn, t.CreatedBy,
t.ModifiedOn, t.ModifiedBy, t.NativeId, t.SourceId, t.Name, t.Alias
FROM
#TableVariable t
LEFT JOIN
NWatchNodes PR WITH (NOLOCK) ON PR.Id = t.Id
WHERE
PR.ID IS NULL
END;

Comment Count on Entry Table (SQL Query)

It may be a simple/specific question but I really need help on that. I have two tables: Entry and Comment in a SQL Server database. I want to show comment count in entry table. And of course comment count will increase when a comment is added. Two tables are connected like this:
Comment.EntryId = Entry.Id
Entry table:
CREATE TABLE [dbo].[Entry] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Subject] NVARCHAR (MAX) NOT NULL,
[Content] NVARCHAR (MAX) NOT NULL,
[Type] NVARCHAR (50) NOT NULL,
[SenderId] NVARCHAR (50) NOT NULL,
[Date] DATE NOT NULL,
[Department] NVARCHAR (50) NULL,
[Faculty] NVARCHAR (50) NULL,
[ViewCount] INT DEFAULT ((0)) NOT NULL,
[SupportCount] INT DEFAULT ((0)) NOT NULL,
[CommentCount] INT DEFAULT ((0)) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
Comment table:
CREATE TABLE [dbo].[Comment] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[EntryId] INT NOT NULL,
[SenderId] NVARCHAR (50) NOT NULL,
[Date] DATETIME NOT NULL,
[Content] NVARCHAR (MAX) NOT NULL,
[SupportCount] INT NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
I am showing the entries in a gridview in codebehind (c#). The question is this, what should I write as a query to do this most efficiently? Thanks for help.
Try this:
select e.Id,e.date,count(*) as NumComments
from Entry e
join comment c on c.entryId=e.id
group by e.id,e.date
If there might be no comments, try the following
select e.Id,e.date,count(c.entryId) as NumComments
from Entry e
left join comment c on c.entryId=e.id
group by e.id,e.date
You can use left join for that purpose. Kindly me more specific with what fields you want in gridview
And why do you want commentcount in table (most tables have that 1-many relation and we didn't use that). If you keep that in table you have to update entry table every time when comment is made.

Categories

Resources