Insert multiple records with values based on each other - c#

Sorry for the bad title, I havent come up with a better one yet.
I am currently optimising a tool which basically does thousands of selects and inserts.
Assume the following relation
class A
{
public long ID; // This is an automatic key by the sqlserver
...Some other values
}
class B
{
public long RefID // Reference to A.ID;
... some other values...
}
class C
{
public long RefID // Reference to A.ID;
... some other values
}
What currently is happening is a SELECT to get ObjectA,
if it doesnt exist, create a new one. The Query returns the ID (OUTPUT INSERTED.ID)
Then it selects (inserts if not existant) the objects B and C.
Is there a way to compress this into a single SQL statement?
Im struggling at the part where the automatic generation of object A happens.
So it must do something like this:
IF NOT EXISTS(SELECT * FROM TableA WHERE someConditions)
INSERT... and get the ID
ELSE
REMEMBER THE ID?
IF NOT EXISTS(SELECT * FROM TableB WERE RefID = ourRememberedID)
INSERT...
IF NOT EXISTS(SELECT * FROM TableC WERE RefID = ourRememberedID)
INSERT...
Please note, stored procedures cannot be used.

A little help
You could issue these three in one statement
Use a DataReader NextResult
select ID from FROM TableA WHERE someConditions
select count(*) from TableB where refID = (select ID from FROM TableA WHERE someConditions)
select count(*) from TableC where refID = (select ID from FROM TableA WHERE someConditions)
But even then you take a risk the TableB or TableC had an insert before you got to it
If I could not use a stored procedure I think I would load the data into a #temp using a TVP
I think you could craft 4 statements in a transaction

Related

The data reader is incompatible. A member of the type, 'RoleId', does not have a corresponding column in the data reader with the same name

Stored procedure works and deletes what I want but I still get this error after deleting:
The data reader is incompatible with the specified 'AMSIdentity.Models.RemoveRoleFromUserViewModel'. A member of the type, 'RoleId', does not have a corresponding column in the data reader with the same name.
I need to run the code without this error in the above
This code using ASP.NET MVC 5 and EF6 code first approach; I tried to use this code but always throws this error after delete.
This is the action method that I use
public ActionResult RemoveRoleFromUserConfirmed(string UserName, string RoleId)
{
if (UserName == null && RoleId == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
SqlParameter param1 = new SqlParameter("#RoleId", RoleId);
SqlParameter param2= new SqlParameter("#UserName", UserName);
var remove = Identitydb.Database.SqlQuery<RemoveRoleFromUserViewModel>("admin.sp_RemoveUserFromRole #RoleId, #UserName",
((ICloneable)param1).Clone(),
((ICloneable)param2).Clone()).ToArray().ToList().FirstOrDefault();
if (remove == null)
{
return HttpNotFound();
}
return RedirectToAction("Roles");
}
This is the view model that I use :
public class RemoveRoleFromUserViewModel
{
[Key]
[DisplayName("Role Id")]
public string RoleId { get; set; }
[DisplayName("Username")]
public string UserName { get; set; }
}
This is the stored procedure code:
ALTER PROCEDURE [Admin].[sp_RemoveUserFromRole]
#RoleId NVARCHAR(50),
#UserName NVARCHAR(50)
AS
BEGIN
DELETE FROM AspNetUserRoles
WHERE UserId = (SELECT Id
FROM AspNetUsers
WHERE UserName = #UserName)
AND RoleId = #RoleId
END
I expect that this code will delete role from the specific user.
When you perform a DELETE in the stored procedure, you need to "audit" what got deleted. Then perform a SELECT on that audit-table.
You are taking advantage of the OUTPUT feature of sql server.
see:
https://learn.microsoft.com/en-us/sql/t-sql/statements/delete-transact-sql?view=sql-server-2017
and/or
https://www.sqlservercentral.com/articles/the-output-clause-for-insert-and-delete-statements
Below is a generic example of the TSQL you need.
DROP TABLE IF EXISTS [dbo].[Patient]
GO
CREATE TABLE [dbo].[Patient]
(
[PatientKey] BIGINT NOT NULL IDENTITY(1, 1),
[PatientUniqueIdentifier] VARCHAR(256) NOT NULL,
[CreateDate] DATETIMEOFFSET NOT NULL,
CONSTRAINT [UC_Patient_PatientUniqueIdentifier] UNIQUE (PatientUniqueIdentifier)
)
/* now insert 3 sets of rows, with different create-dates */
INSERT INTO dbo.Patient (PatientUniqueIdentifier, [CreateDate]) SELECT TOP 10 NEWID() , '01/01/2001' from sys.objects
INSERT INTO dbo.Patient (PatientUniqueIdentifier, [CreateDate]) SELECT TOP 10 NEWID() , '02/02/2002' from sys.objects
INSERT INTO dbo.Patient (PatientUniqueIdentifier, [CreateDate]) SELECT TOP 10 NEWID() , '03/03/2003' from sys.objects
SELECT 'SeedDataResult' as Lable1, * FROM dbo.Patient
/* everything above is just setting up the example */
/* below would be the "guts"/implementation of your stored procedure */
DECLARE #PatientAffectedRowsCountUsingAtAtRowCount BIGINT
DECLARE #PatientAffectedRowsCountUsingCountOfOutputTable BIGINT
DECLARE #PatientCrudActivityAuditTable TABLE ( [PatientUniqueIdentifier] VARCHAR(256), DatabaseKey BIGINT , MyCrudLabelForKicks VARCHAR(16));
/* now delete a subset of all the patient rows , your delete will be whatever logic you implement */
DELETE FROM [dbo].[Patient]
OUTPUT deleted.PatientUniqueIdentifier , deleted.PatientKey , 'mydeletelabel'
INTO #PatientCrudActivityAuditTable ([PatientUniqueIdentifier] ,DatabaseKey , MyCrudLabelForKicks )
WHERE
CreateDate = '02/02/2002'
/*you don't need this update statement, but i'm showing the audit table can be used with delete and update and insert (update here) */
/*
UPDATE [dbo].[Patient]
SET CreateDate = '03/03/2003'
OUTPUT inserted.PatientUniqueIdentifier , inserted.PatientKey, 'myupdatelabel'
INTO #PatientCrudActivityAuditTable ([PatientUniqueIdentifier] ,DatabaseKey , MyCrudLabelForKicks)
FROM [dbo].[Patient] realTable
WHERE CreateDate != '03/03/2003'
*/
/* optionally, capture how many rows were deleted using ##ROWCOUNT */
SELECT #PatientAffectedRowsCountUsingAtAtRowCount = ##ROWCOUNT
/* or, capture how many rows were deleted using a simple count on the audit-table */
SELECT #PatientAffectedRowsCountUsingCountOfOutputTable = COUNT(*) FROM #PatientCrudActivityAuditTable
SELECT 'ResultSetOneForKicks' as Label1, 'Rows that I Deleted' as MyLabel_YouCanRemoveThisColumn, DatabaseKey , PatientUniqueIdentifier FROM #PatientCrudActivityAuditTable
/* if so inclined, you can also send back the delete-COUNTS to the caller. You'll have to code your IDataReader (ORM, whatever) to handle the multiple return result-sets */
/* most people will put the "counts" as the first result-set, and the rows themselves as the second result-set ... i have them in the opposite in this example */
SELECT 'ResultSetTwoForKicks' as Label1, #PatientAffectedRowsCountUsingAtAtRowCount as '#PatientAffectedRowsCountUsingAtAtRowCountCoolAliasName' , #PatientAffectedRowsCountUsingAtAtRowCount as '#PatientAffectedRowsCountUsingAtAtRowCountCoolAliasName'
In my example, you would write the dotNet serialize code...(whatever flavor you use, raw IDataReader, ORM tool, whatever) against the PatientKey and PatientUniqueIdentifier columns coming back from the #PatientSurrogateKeyAudit table.
Hi All,
I got the answer from #Jeroen Mostert, The solution is to use the
(ExecuteSqlCommand) rather than (SqlQuery) because I will never return
data, I only execute the stored procedure with two parameters.
This is the answer
SqlParameter param1 = new SqlParameter("#RoleId", RoleId);
SqlParameter param2= new SqlParameter("#UserName", UserName);
//I change this line from SqlQuery to ExecuteSqlCommand
var remove = Identitydb.Database.ExecuteSqlCommand("admin.sp_RemoveUserFromRole #RoleId, #UserName", param1, param2);
Thank you very much #Jeroen Mostert.
Regards,
Ali Mosaad
Software Developer

SQL Query: Display only latest Id from each set

I have the following SQL Table:
Name Description Id UserId CreatedDate
UserSet1 Desc1 1 Abc 06/01/2018
UserSet1 Desc2 2 Def 06/02/2018
UserSet2 Desc for 2 5 NewUser 06/04/2018
UserSet2 Desc for 2 7 NewUser 06/19/2018
What I want to extract from the above table is just the latest Id for each Name so that I could get the following output
Name Description Id UserId CreatedDate
UserSet1 Desc2 2 Def 06/01/2018
UserSet2 Desc for 2 7 NewUser 06/19/2018
Since Id 2 & 7 are the latest entries in the table for UserSet1 & UserSet2, I would like to display that instead of all the entries in the table.
Any inputs how can I get the desired result.
I am open for solutions directly returning the output or any linq (C#) solutions as well. Ie returning the entire dataset and then using linq to filter the above.
EDIT: Since you are looking for the highest number ID, the GROUP BY method would probably be easier to work with.
Using a window function:
SELECT *
FROM (
SELECT Name, Description, Id, UserId, CreatedDate
, ROW_NUMBER() OVER (PARTITION BY Name ORDER BY CreatedDate DESC) AS rn
FROM myTable
) s1
WHERE rn = 1
I don't have an instance of dynamoDB to test on, but I believe it can use the ROW_NUMBER() window function.
Thanks everyone for pointing to right direction. I have got this working with the below code of Linq and C#:
var results = response.GroupBy(row => row.Name)
.SelectMany(g => g.OrderByDescending(row => row.Id).Take(1));
For the initial tests this seems to be working. Let me know if you think this has come issues.
This should be a general SQL answer:
SELECT * FROM yourtable Y1
WHERE Id = (SELECT MAX(Id)
FROM yourtable Y2
WHERE Y2.Name = Y1.Name)
If it was MS SQL you could use Partition By command, otherwise most performant way would be:
select * from Table
where Id in (
select Max(Id) from Table
Group By Name
)
not sure if you can leave Name out of the Select statement, you might need to do:
select * from Table
where Id in (
Select Id from
(select Name, Max(Id) as Id from Table
Group By Name)
)

Get Multiple Values for one variable output in stored procedure

These are my classes
Public Class Student
{
Public int Id{get;set;}
public string Name {get;set;}
}
Public Class Department
{
Public int Id{get;set;}
public string Name {get;set;}
public IList <Student> StudentList{get;set;}
}
These are my tables
Student Table
Id |Name |Department
-----------------
1 |aa | 1
2 |bb | 1
3 |cc | 2
Department Table
ID | Name
----------
1 | xxx
2 | yyy
I need the all data of department with the corresponding list of student by using stored procedure
CREATE PROCEDURE test
(
)
AS
BEGIN
select Id as Id,
Name as Name,
----here I need all the corresponding data from student table as StudentList
from Department
Is it possible.. If so Please help me.
Apparently I what want is I want to write another procedure and call it for StudentList,Is it possible
Use join, i choose Left Join here so in case if department does not have corresponding students it will still be retrieved.
SELECT d.ID
,d.Name
,s.ID
,s. Name
FROM Deparment d
LEFT JOIN Student s ON s.Department = d.ID
So from your example the query will return the following result :
ID, Name, Student ID Student Name
1 xxx 1 aa
1 xxx 2 bb
2 yyy 3 cc
Assuming that your stored procedure will accept department id as parameter and retrieve all students under this department, just add a condition in your query
WHERE d.ID = #DeptID
As #Eugen said, just process the result in your application.
What you describe can be done, but it is not the normal process. You can use for xml to return a column where all of the student information is encoded. Note that while this results in all of the data you want being returned in a single row, you will have to create and populate all of your objects by hand. None of the ORM's like Entity Framework or nHibernate will populate both your department and students. In fact, you will probably end up parsing a string by hand in order to create your students.
The most common practice today would be to use Entity Framework and let it do all of the work for you.
Here's some code to show how you can do as you request. Again, note that this is not recommended, and that it's just one way of doing this:
create table #s (id int, name varchar(max), departmentId int);
insert into #s (id, name, departmentId)
select * from (values (1 , 'aa', 1) ,(2 , 'bb', 1) ,(3 , 'cc', 2)
) g(id, name, departmentId);
create table #d (departmentId int, name varchar(max));
insert into #d (departmentId, name) select * from (values
(1 , 'xxx') ,(2 , 'yyy')
)g(departmentId, name)
select *, (select id, name, departmentId
from #s s where s.departmentID = d.departmentId for xml path
)students
from #d d
drop table #d
drop table #s;
Students for deparment 1 =
<row><id>1</id><name>aa</name><departmentId>1</departmentId></row>
<row><id>2</id><name>bb</name><departmentId>1</departmentId></row>
In particular, you may find this a bit heavy, 2016 should be able to do something similar with JSON, but if you'd like that in the meantime, you'll have to customize the output yourself. It can be done, and wouldn't be too difficult.

EF6 fails to import stored procedure

This is a simplified version of a stored procedure
ALTER PROCEDURE [dbo].[StoredProc1]
(
#PageIndex INT = 1,
#RecordCount INT = 20,
#Gender NVARCHAR(10) = NULL
)
AS
BEGIN
SET NOCOUNT ON ;
WITH tmp1 AS
(
SELECT u.UserId, MIN(cl.ResultField) AS BestResult
FROM [Users] u
INNER JOIN Table1 tbl1 ON tbl1.UserId = u.UserId
WHERE (#Gender IS NULL OR u.Gender = #Gender)
GROUP BY u.UserID
ORDER BY BestResult
OFFSET #PageIndex * #RecordCount ROWS
FETCH NEXT #RecordCount ROWS ONLY
)
SELECT t.UserId, t.BestResult, AVG(cl.ResultField) AS Average
INTO #TmpAverage
FROM tmp1 t
INNER JOIN Table1 tbl1 ON tbl1.UserId = t.UserId
GROUP BY t.UserID, t.BestResult
ORDER BY Average
SELECT u.UserId, u.Name, u.Gender, t.BestResult, t.Average
FROM #tmpAverage t
INNER JOIN Users u on u.UserId = t.UserId
DROP TABLE #TmpAverage
END
When I use EF6 to load the stored procedure, and then go to the "Edit Function Import" dialog, no columns are displayed there. Even after I ask to Retrieve the Columns, I get the message that the SP does not return columns. When I execute the SP from SMMS, I get the expected [UserId, Name, Gender, BestResult, Average] list of records.
Any idea how can I tweak the stored procedure or EF6 to make it work?
Thanks in advance
Thanks to the comments above, the answer is that unfortunately EF6 does not cope well with TMP tables on stored procedures.
One way around is the following:
1) Comment out all temp table calls inside the Stored Procedure.
2) Change the Stored Procedure to return a Fake a result with the same exact column's names that match the expected result
3) Import the Stored Procedure into EF6
4) Double Click on the Function Imports/Stored procedure name
5) On the Edit Function Import dialog, retrieve the Columns and the create a New Complex Type that will match the fake columns
6) CTRL+Save in order to generate all the C# code
7) Re-Update the Stored Procedure by removing the fake result set and un-comment the code with the Temp tables.
That should do the job.
P.S. Special thanks for the helpers that pointed me to the right place !!!
Sometimes it works to add the following statement to the stored proc:
set fmtonly off
But still, dont leave this statement in - only use it while generating the result set

Partial mapping in Entity Framework 4

I want to be able to do the following:
I have a model and inside there I do have an entity.
This entity has the following structure:
public class Client
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
What I want now, is to just get the client name based on the id.
Therefore I wrote a stored procedure which is doing this.
CREATE PROCEDURE [Client].[GetBasics]
#Id INT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT
Name
FROM Client.Client
INNER JOIN Client.Validity ON ClientId = Client.Id
WHERE
Client.Id = #Id;
END
Now, going back to VS, I do update the model from the database with the stored procedure included.
Next step is to map this stored procedure to the client entity as a function import.
This also works fine.
Trying now to load one client's name results into an error during runtime...
"The data reader is incompatible with
the specified 'CSTestModel.Client'. A
member of the type, 'Id', does not
have a corresponding column in the
data reader with the same name."
I am OK with the message. I know how to fix this (returning as result set Id, Name, Description).
My idea behind this question is the following:
I just want to load parts of the entity, not the complete entity itself.
I have a restriction here to just use stored procedures for the entire communication towards/from the database.
Is there a solution to my problem (except creating complex types, LINQ on the result set itself)?
And if yes, can someone point me to the right direction?
Many thanks,
Dimi
Just project onto a POCO:
var q = from c in Context.Clients
select new NameOnlyPresentation
{
Id = c.Id,
Name = c.Name
};
... or just the name:
public string ClientName(int id)
{
return (from c in Context.Clients
where c.Id == id
select c.Name).FirstOrDefault();
}

Categories

Resources