In Entity Framework, I tried executing these two queries:
Query #1:
var deptInput = new SqlParameter("#DepartmentName", deptString);
var departmentTable = Database.Users.FromSql("EXECUTE dbo.
[ViewEmployeeListByDepartment] #DepartmentName", deptInput).ToList();
Query #2:
var deptInput = new SqlParameter("#DepartmentName", deptString);
var departmentTable = Database.Database.ExecuteSqlCommand("EXECUTE dbo.
[ViewEmployeeListByDepartment] #DepartmentName", deptInput);
And this is my stored procedure in SQL Server:
Declare #CounterForEmployees int
Declare #NumberOfEmployees int
Declare #TableCount int = 1
Declare #DepartmentTable TABLE(
Id nvarchar(450),
UserName nvarchar(256),
Display_Name nvarchar(256),
Direct_Supervisor nvarchar(256),
Ranking int
);
SET #NumberOfEmployees = (SELECT Count(*)
FROM dbo.[User]
WHERE Department = #DepartmentName)
SET #CounterForEmployees = 0
--Get the HOD of the Department First
INSERT INTO #DepartmentTable
SELECT
Id, UserName, Display_Name, Direct_Supervisor, ''
FROM
[ASEAppraisalDB].[dbo].[User]
WHERE
Department = #DepartmentName AND
UserName IN (SELECT Distinct(Head_Of_Department)
FROM [ASEAppraisalDB].[dbo].[User]
WHERE Department = #DepartmentName)
UPDATE #DepartmentTable
SET Ranking = #TableCount
WHERE Ranking = 0
.
.
.
.
.
.
.
.
SELECT * FROM #DepartmentTable
When I execute the first one, it returns a error message saying there is missing column from the dbo.[User] table inside #DepartmentTable. The dbo.[User] table has some columns I did not select into #DepartmentTable.
When I execute the second one, it returns only a number (which is the number of employees). It seems that the second query has only selected the part that counts the employee. The number of employee is only needed for my other parts of my code for calculations for a while loop. I do not need to return it to the EF.
I only want the result from select * from #DepartmentTable to be returned to Entity Framework. In the parameter of the stored procedure in SQL Server Object Explorer, it also says that it returns integer shown below:
How do I fix this? Also Which query is more efficient and more 'correct' to use?
when you map you stored procedure to your .edmx fle in enfity framework create a complex type.
first click on Generate column Information then click on Create New Complex Type
I will be yielding the result from complex type.
Since i am using a repository pattern i have created a generic function for executing stored procedure in my code which takes name of stored procedure as string and list of sql parameter.
public List<T> ExecuteStoredProc(string StoredProcName, List<SqlParameter> parameters)
{
List<T> list = null;
try
{
if (!string.IsNullOrEmpty(StoredProcName))
{
StringBuilder sb = new StringBuilder();
sb.Append("Exec ");
sb.Append(StoredProcName);
foreach (SqlParameter item in parameters)
{
if (list == null)
{
sb.Append(" #");
list = new List<T>();
}
else
{
sb.Append(", #");
}
sb.Append(item.ParameterName);
if (item.Direction == System.Data.ParameterDirection.Output)
{
sb.Append(" OUT");
}
}
list = Context.Database.SqlQuery<T>(sb.ToString(), parameters.Cast<object>().ToArray()).ToList();
}
}
catch (SqlException ex)
{
//RAISERROR() has limitation of 255 characters, so complete error message is being passed through output parameter : #ErrorMessage
if (parameters != null && parameters.Count > 0)
{
SqlParameter param = parameters.FirstOrDefault(a => a.Direction == ParameterDirection.Output && a.ParameterName == Constants.Constant.ParamErrorMessage);
if (param != null)
{
string errorMessage = Convert.ToString(param.Value);
if (!string.IsNullOrEmpty(errorMessage))
{
throw new JcomSqlException(errorMessage, ex);
}
}
}
throw new JcomSqlException(ex.Message, ex);
}
return list;
}
Then use it with your repository
List<GetDepartmentDetails_Result> getDepts(string deptName)
{
List<SqlParameter> spm = new List<SqlParameter>()
{
new SqlParameter() { ParameterName="#DepartmentName", Value=deptName}
};
var result = this.UnitOfWork.GetRepository<GetDepartmentDetails_Result>().ExecuteStoredProc("GetDepartmentDetails", spm);
return result;
}
All stored procedures return an integer only.It also return the data for multiple data tables.
You check in SQL Server when you execute stored procedure it's return two set of data table in SQL Server optionally returns int.
You can verify this Link RETURN (Transact-SQL)
Related
Here I have blazor server app, where I have stored procedure [dbo].[spGetAllChapter] which return list of chapter name, class name and subject name in SSMS.
To call this stored procedure [dbo].[spGetAllChapter] I have used _dbContext.Database.ExecuteSqlRaw().
Now, the problem is that on calling stored procedure it does not return list instead it shows -1 value, but on executing same stored procedure in SSMS returns list.
Below is my stored procedure
ALTER PROCEDURE [dbo].[spGetAllChapter]
AS
BEGIN
SELECT CH.ChapterId
, CH.ChapterName
, SC.ClassName
, S.SubjectName
FROM [dbo].[Chapter] AS CH
JOIN [dbo].[SchoolClass] AS SC
ON CH.SchoolClassId= SC.SchoolClassId
JOIN [dbo].[Subject] AS S
ON CH.SubjectId = S.SubjectId
END
Below is how I have called stored procedure
public eLearningDBContext _dbContext = new eLearningDBContext();
//getChapterList is -1 instead of returning list from procedure
var getChapterList = _dbContext.Database.ExecuteSqlRaw($"EXEC dbo.spGetAllChapter");
The docs for ExecuteSqlRaw say that the function:
Executes the given SQL against the database and returns the number of rows affected.
Note that it doesn't return the data, but the number of rows affected. Perhaps what you're looking for is FromSqlRaw
Alternatively, you don't need a stored procedure to accomplish what you want; you can simply project the columns you want in a regular EF query, like:
_dbContext.Chapters.Select(ch => new
{
ChapterId = ch.ChapterId,
ChapterName = ch.ChapterName,
ClassName = ch.SchoolClass.ClassName,
SubjectName = ch.Subject.SubjectName
}).ToList()
This will give you a list of anonymous objects with the fields you want, but you could also create a named type (Like ChapterAbstract or something) and project to that instead.
If the context was created DB first, you can update the DataModel via the Wizard to include Stored Procedures and Functions. Then you can call the SP directly from the context
_dbcontext.spGetAllChapter();
I some how solved by making this helper class
public static class SQLHelper
{
public static List<T> RawSqlQuery<T>(string query, Func<DbDataReader, T> function)
{
using (var context = new eLearningDBContext())
{
using (var command = context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = query;
command.CommandType = CommandType.Text;
context.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
var entities = new List<T>();
while (result.Read())
{
entities.Add(function(result));
}
return entities;
}
}
}
}
}
var list = SQLHelper.RawSqlQuery($"EXEC dbo.spGetAllChapter",
x => new ChapterVM {
ChapterId = (int)x[0],
ChapterName = (string)x[1],
ClassName = (string)x[2],
SubjectName = (string)x[3]
}).ToList();
I am trying to understand how to use Dapper to make a call to a PostgreSQL function that returns multiple result sets. My understanding is that in PostgreSQL, the best (only?) way to currently achieve this is to declare that the function RETURNS SETOF REFCURSOR.
Example PostgreSQL Function that Returns Multiple REFCURSORs
CREATE OR REPLACE FUNCTION public.testmultiplerefcursorfunc()
RETURNS SETOF REFCURSOR
LANGUAGE 'plpgsql'
STABLE
AS $BODY$
DECLARE
ref1 REFCURSOR;
ref2 REFCURSOR;
BEGIN
OPEN ref1 FOR
SELECT *
FROM characters;
RETURN NEXT ref1;
OPEN ref2 FOR
SELECT *
FROM planets;
RETURN NEXT ref2;
END;
$BODY$;
Broken Dapper+PostgreSQL with Multiple REFCURSORs Example
[Test]
public void UsingDapper_QueryMultiple_CallFunctionThatReturnsMultipleRefCursors_ReadsMultipleResultSetsViaMultipleRefCursors()
{
// Arrange
using (var conn = new NpgsqlConnection(_getConnectionStringToDatabase()))
{
var funcName = "testmultiplerefcursorfunc";
var expect1 = CharacterTestData;
var expect2 = PlanetTestData;
conn.Open();
using (var transaction = conn.BeginTransaction())
{
// Act
using (var results = conn.QueryMultiple(
funcName,
commandType: CommandType.StoredProcedure,
transaction: transaction))
{
var result1 = results.Read<Character>().AsList();
var result2 = results.Read<Planet>().AsList();
// Assert
CollectionAssert.AreEquivalent(expect1, result1);
CollectionAssert.AreEquivalent(expect2, result2);
}
}
}
}
The problem that I'm having with the code above is that when I make the first results.Read<T>() call, it attempts to return both REFCURSORs cast as T. This cast then results in a T with null values for all of the properties. Then the next call to results.Read<T>() throws the following exception:
System.ObjectDisposedException: 'The reader has been disposed; this can happen after all data has been consumed
Object name: 'Dapper.SqlMapper+GridReader'.'
So, how does Dapper work with multiple PostgreSQL REFCURSORs? Is there a way to read the results without manually dereferencing the cursors?
I've got a vanilla example that returns multiple REFCURSORs without using Dapper that works where I manually dereference the cursors and read the results and I've also got examples that work against a SQL Server stored procedure that return multiple results.
I haven't (yet) found any particular documentation that points to a specific difference of how QueryMultiple should be called for PostgreSQL vs SQL Server, but such documentation would be greatly appreciated.
Even when calling a PostgreSQL function that returns single REFCURSOR using Dapper, I've found it necessary to manually handle the cursor dereferencing like the example below.
But from what I've read so far, this doesn't seem like it's supposed to be necessary, although I've had trouble finding specific documentation/examples for Dapper+PostgreSQL that show how this should otherwise work.
Working Dapper+PostgreSQL with Single REFCURSOR Example
[Test]
public void UsingDapper_Query_CallFunctionThatReturnsRefCursor_ReadsRowsViaRefCursor()
{
// Arrange
using (var conn = new NpgsqlConnection(_getConnectionStringToDatabase()))
{
var procName = "testrefcursorfunc";
var expect = CharacterTestData;
conn.Open();
using (var transaction = conn.BeginTransaction())
{
// Act
var cursorResult = (IDictionary<string, object>)conn
.Query<dynamic>(procName, commandType: CommandType.StoredProcedure, transaction: transaction)
.Single();
var cursorSql = $#"FETCH ALL FROM ""{(string)cursorResult[procName]}""";
var result = conn.Query<Character>(
cursorSql,
commandType: CommandType.Text,
transaction: transaction);
// Assert
CollectionAssert.AreEquivalent(expect, result);
}
}
}
So, with Dapper + PostgreSQL + REFCURSOR, is it always necessary to manually deference the cursor to read the results? Or can Dapper handle that for you?
Coming from an sql server background where its just a question of a list of select statements in your stored proc and your "good to go"; using postgresql and the requirement to use refcursors and "fetch " those refcusors on the other side can be quite painful.
What i can suggest is:
1.) Use a postgresql Procedure with refcursors as INOUT parameters.
CREATE OR REPLACE PROCEDURE public.proc_testmultiplerefcursor(INOUT ref1 refcursor, INOUT ref2 refcursor)
2.) Call the procedure and then fetch the refcursors for the returned data using "FETCH ALL".
Fill the INOUT parameters with names for refcursors so they are recoverable in this case i have used 'ref1' & 'ref2'.
var sql = "BEGIN;CALL public.proc_testmultiplerefcursor(#pentity_id,'ref1','ref2');" +
"FETCH ALL FROM ref1; " +
"FETCH ALL FROM ref2;" +
"COMMIT;";
3.) Then your usual Dapper QueryMutliple and Reads.
var multi = await conn.QueryMultipleAsync(sql);
var result1 = (await multi.ReadAsync<Character>()).AsList();
var result2 =(await multi.ReadAsync<Planet>()).AsList();
This is untested but i hope it can be of help. Postgresql is painfull but brilliant.
I've ran into this problem lately, and to imitate the multiple result set behavior of SQL Server, I used a function that returns SETOF REFCURSOR. I made my own simple library to resolve this problem, so I can reuse it to my other projects. See my GitHub Repository
Assuming you have 5 tables in the database
User
Id
Username
Role
Id
Name
Permission
Id
Name
UserRole
UserId | FK: User.Id
RoleId | FK: Role.Id
RolePermission
RoleId | FK: Role.Id
PermissionId | FK: Permission.Id
PostgreSQL (PL/PGSQL)
CREATE OR REPLACE FUNCTION "get_user_with_roles_and_permissions"("user__id" INTEGER)
RETURNS SETOF REFCURSOR AS
$BODY$
DECLARE
-- Refcursor declarations
"ref__user" REFCURSOR;
"ref__roles" REFCURSOR;
"ref__permissions" REFCURSOR;
BEGIN
-- Select User
-- NOTE: this only query for exactly 1 row
OPEN "ref__user" FOR
SELECT "User"."Id", "User"."Username"
FROM "User"
WHERE "User"."Id" = "user__id"
LIMIT 1;
RETURN NEXT "ref__user";
-- Select Roles
OPEN "ref__roles" FOR
SELECT "Role"."Id", "Role"."Name"
FROM "Role"
INNER JOIN "UserRole" ON "Role"."Id" = "UserRole"."RoleId"
WHERE "UserRole"."UserId" = "user__id";
RETURN NEXT "ref__roles";
-- Select Permissions
-- NOTE: There's a chance that user has many roles which have same permission, we use DISTINCT to eliminate duplicates
OPEN "ref__permissions" FOR
SELECT DISTINCT "Permission"."Id", "Permission"."Name"
FROM "Permission"
INNER JOIN "RolePermission" ON "Permission"."Id" = "RolePermission"."PermissionId"
INNER JOIN "UserRole" ON "RolePermission"."RoleId" = "UserRole"."RoleId"
WHERE "UserRole"."UserId" = "user__id";
RETURN NEXT "ref__permissions";
END;
$BODY$
C#
/* Entity models */
record User
{
public int Id { get; set; }
public string Username { get; set; }
public IEnumerable<Role> Roles { get; set; }
public IEnumerable<Permission> Permissions { get; set; }
}
record Role
{
public int Id { get; set; }
public string Name { get; set; }
}
record Permission
{
public int Id { get; set; }
public string Name { get; set; }
}
// Calling the database function in C# code
// Create an instance of NpgsqlConnection
using var connection = new NpgsqlConnection(connectionString);
// Try to open a database connection
connection.Open();
// Begin a database transaction
using var transaction = connection.BeginTransaction();
// Query refcursors
// Call a function with name 'get_user_with_roles_and_permissions' with parameter 'user_id' = 1
var refcursors = connection.QueryRefcursor("get_user_with_roles_and_permissions", transaction, new { user__id = 1 });
// we use ReadSingleOrDefault because we're sure that there is only one user that has an id of 1 (or none if the user with id = 1 doesn't exists)
var user = refcursors.ReadSingleOrDefault<User>();
// Check if user with id = 1 exists
if (user is not null)
{
// Query for roles
user.Roles = refcursors.Read<Role>();
// Query for permissions
user.Permissions = refcursors.Read<Permission>();
}
Try with conn.QueryMultipleAsync In Current source you are using conn.QueryMultiple. Here is the complete guide for it . Dapper Multiple
You Can Use Like this. Sure It Will Work..!
public DataSet Manage_user_profiledata(string _prof_code)
{
string query = string.Format(#"select * from Function_Name(#prof_code, #first_tbl, #second_tbl)");
NpgsqlParameter[] sqlParameters = new NpgsqlParameter[3];
sqlParameters[0] = new NpgsqlParameter("#prof_code", NpgsqlDbType.Varchar);
sqlParameters[0].Value = Convert.ToString(_prof_code);
//
sqlParameters[1] = new NpgsqlParameter("#first_tbl", NpgsqlTypes.NpgsqlDbType.Refcursor);
sqlParameters[1].Value = Convert.ToString("Avilable");
sqlParameters[1].Direction = ParameterDirection.InputOutput;
sqlParameters[1].NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Refcursor;
//
sqlParameters[2] = new NpgsqlParameter("#second_tbl", NpgsqlTypes.NpgsqlDbType.Refcursor);
sqlParameters[2].Value = Convert.ToString("Assigned");
sqlParameters[2].Direction = ParameterDirection.InputOutput;
sqlParameters[2].NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Refcursor;
return conn.executeMultipleSelectQuery(query, sqlParameters);
}
public DataSet executeMultipleSelectQuery(string _query, NpgsqlParameter[] sqlParameter)
{
// NgpSql Init //
npg_connection = new NpgsqlConnection(connstr);
npg_command = new NpgsqlCommand(_query, npg_connection);
// NgpSql Init //
i = 0;
try
{
ds = new DataSet();
npg_connection.Open();
NpgsqlTransaction tran = npg_connection.BeginTransaction();
npg_command.CommandType = CommandType.Text;
npg_command.Parameters.AddRange(sqlParameter);
npg_command.ExecuteNonQuery();
foreach (NpgsqlParameter parm in sqlParameter)
{
if (parm.NpgsqlDbType == NpgsqlTypes.NpgsqlDbType.Refcursor)
{
if (parm.Value.ToString() != "null" || parm.Value.ToString() != "NULL" || parm.Value.ToString() != "")
{
string parm_val = string.Format("FETCH ALL IN \"{0}\"", parm.Value.ToString());
npg_adapter = new NpgsqlDataAdapter(parm_val.Trim().ToString(), npg_connection);
ds.Tables.Add(parm.Value.ToString());
npg_adapter.Fill(ds.Tables[i]);
i++;
}
}
}
tran.Commit();
return ds;
}
catch (Exception ex)
{
ds_ERROR.Tables[0].Rows.Add(ex.ToString(), ex.Message.ToString());
return ds_ERROR;
}
finally
{
npg_connection.Close();
}
}
In my asp.net web-api, am getting error as "ORA-00936: missing expression" from below code snippet. I had tried many solutions but i did not overcome this error. And also i want to know how do i bind more than one parameter dynamically. I am using oracle as my back-end and dapper as my ORM.
string empId = json.EMPID; //'15RD005'
var sql = #"Select id_no,SNO,REASON,APPLIEDDATE,Case
when LEAVE_TYPE = 0 then 'CL'
when LEAVE_TYPE = 1 then 'EL'
when LEAVE_TYPE = 2 then 'SL'
when LEAVE_TYPE = 3 then 'OFF'
when LEAVE_TYPE = 4 then 'OD-OFF'
when LEAVE_TYPE = 5 then 'LOP'
when LEAVE_TYPE = 6 then 'OPTIONAL' end LEAVE_TYPE,
to_char(fromdate,'DD-MON-YYYY') f_date, to_char(todate,'DD-MON-YYYY') t_date,
Case when fromslot=0 then 'First-Half' when fromslot=1 then 'Second-Half' when fromslot=2 then 'Full-Day' end From_Slot,
Case when toslot=0 then 'First-Half' when toslot=1 then 'Second-Half' when toslot=2 then 'Full-Day' end To_Slot,
applieddays APP_DAYS,
case when actinact=0 and cancel_idno is not null then 'Cancelled'
when actinact=1 and AUTH_IDNO is null then 'Pending'
when actinact=0 and cancel_idno is not null then 'Rejected'
else 'Authorised' end leave_Status
from Tleaves where to_char(Todate,'mm-yyyy') >= to_char(sysdate-30,'mm-yyyy') and to_char(todate,'mm-yyyy') <=to_char(sysdate,'mm-yyyy')
and to_char(Todate,'yyyy')=to_char(sysdate,'yyyy') and id_no like #EmpId Order by sno";
try
{
using (OracleConnection db = new OracleConnection(conString))
{
db.Open();
var pastLeavesReport = new PastLeavesReportDTO();
//3.Present and last month lev status report
List<PastLeavesReportInfoDTO> pastLeavesReportInfo = db.Query<PastLeavesReportInfoDTO>(sql, new { EmpId = empId }).ToList();
pastLeavesReport.EMPID = "";
pastLeavesReport.LEAVES = pastLeavesReportInfo;
return Ok(
new EmpLeavesActionResponse(ActionStatusCodes.PastLeavesReportDataFound,
"",
pastLeavesReport));
}
}
catch (Exception exp)
{
return Ok(
new EmpLeavesActionResponse(ActionStatusCodes.ServerException,
exp.Message,
null));
}
Finally i solved my issue with little change in my code. That is #Empid is changed into :Empid, since oracle database supports dynamic parameters in this way. And for my second question that is how to handle with multiple dynamic parameters i am using dappers DynamicParameters class as shown below,
var parameters = new Dictionary<string, object>();
parameters.Add("ID", empId);
DynamicParameters dbParams = new DynamicParameters();
dbParams.AddDynamicParams(parameters);
And we can use this in dapper as shown in below code snippe,
Parameter query is a sample sql query.
dynamic result = db.Query(query, dbParams);
So you have a logic problem here which might be the error
case
when actinact=0 and cancel_idno is not null then 'Cancelled'
when actinact=1 and AUTH_IDNO is null then 'Pending'
when actinact=0 and cancel_idno is not null then 'Rejected'
else 'Authorised'
end leave_Status
because actinact=0 and cancel_idno is not null is listed twice
Note: This is easy to spot when the code is nicely formatted.
You say you want two parameters just add then in the new object
new { EmpId = empId }
becomes
new { EmpId = empId,
newparam = newvalue })
and then use #newparam in your query.
doc -> https://msdn.microsoft.com/en-us/library/bb397696.aspx
I have a stored procedure with 3 input paramaters.
... PROCEDURE [dbo].[gama_SearchLibraryDocuments]
#Keyword nvarchar(160),
#CategoryIds [dbo].[IntList] READONLY,
#MarketIds [dbo].[IntList] READONLY ...
Where IntList is user defined table type.
CREATE TYPE [dbo].[IntList]
AS TABLE ([Item] int NULL);
My goal is to call this stored procedure with dapper.
I have found some examples regarding passing user defined type with dapper.
One of them is TableValuedParameter class implemented in Dapper.Microsoft.Sql nuget package.
var list = conn.Query<int>("someSP", new
{
Keyword = (string)null,
CategoryIds = new TableValuedParameter<int>("#CategoryIds", "IntList", new List<int> { }),
MarketIds = new TableValuedParameter<int>("#MarketIds", "IntList", new List<int> { 541 })
}, commandType: CommandType.StoredProcedure).ToList();
Written above code throws
An exception of type 'System.NotSupportedException' occurred in Dapper.dll but was not handled in user code
Additional information: The member CategoryIds of type Dapper.Microsoft.Sql.TableValuedParameter`1[System.Int32] cannot be used as a parameter value
I have tested my stored procedure with one user defined table type and it worked fine.
conn.Query<int>("someSP", new TableValuedParameter<int>("#MarketIds", "IntList", new List<int> { 541 }), commandType: CommandType.StoredProcedure).ToList();
I need help with running original stored procedure.
Thank you.
Dapper has extension method to work with table valued parameters.
public static SqlMapper.ICustomQueryParameter AsTableValuedParameter(this DataTable table, string typeName = null)
You can use dapper in the following way:
var providersTable = new DataTable();
providersTable.Columns.Add("value", typeof(Int32));
foreach (var value in filterModel.Providers)
{
providersTable.Rows.Add(value);
}
var providers = providersTable.AsTableValuedParameter("[dbo].[tblv_int_value]");
var filters =
new
{
campaignId = filterModel.CampaignId,
search = filterModel.Search,
providers = providers,
pageSize = requestContext.PageSize,
skip = requestContext.Skip
};
using (var query = currentConnection.QueryMultiple(StoredProcedureTest, filters, nhTransaction, commandType: CommandType.StoredProcedure))
{
var countRows = query.Read<int>().FirstOrDefault();
var temp = query.Read<CategoryModel>().ToList();
return new Result<IEnumerable<CategoryModel>>(temp, countRows);
}
It will be translated into SQL:
declare #p3 dbo.tblv_int_value
insert into #p3 values(5)
insert into #p3 values(34)
insert into #p3 values(73)
insert into #p3 values(14)
exec [dbo].[StoredProcedureTest] #campaignId=123969,#search=NULL,#providers=#p3,#pageSize=20,#skip=0
I answered a similar post at Call stored procedure from dapper which accept list of user defined table type with the following and I think this may help you as well!
I know this is a little old, but I thought I would post on this anyway since I sought out to make this a little easier. I hope I have done so with a NuGet package I create that will allow for code like:
public class IntList
{
public int Item { get; set; }
}
var list1 = new List<IntList>{new IntList{ Item= 1 }, new IntList{ Item= 2}};
var list2 = new List<IntList>{new IntList{ Item= 3 }, new IntList{ Item= 4}};
var parameters = new DynamicParameters();
parameters.Add("#Keyword", "stringValue");
parameters.AddTable("#CategoryIds", "IntList", list1);
parameters.AddTable("#MarketIds", "IntList", list2);
var result = con.Query<int>("someSP", parameters, commandType: CommandType.StoredProcedure);
NuGet package: https://www.nuget.org/packages/Dapper.ParameterExtensions/0.2.0
Still in its early stages so may not work with everything!
Please read the README and feel free to contribute on GitHub: https://github.com/RasicN/Dapper-Parameters
This is a bit of a necro-post, but I believe there is a better way to do this. Specifically, a way that allows simple and complex types to be inserted and perform a single update, which may be important if you have a lot of indexes.
In this example I'm adding a Product that has a Name, ImageUrl and Year using a Stored Procedure called ProductAdd and a User-Defined Table Type called ProductList. You can simplify this to be a list of numbers or strings.
DataTable productList = new DataTable();
productList.Columns.Add(new DataColumn("Name", typeof(string)));
productList.Columns.Add(new DataColumn("ImageUrl", typeof(string)));
productList.Columns.Add(new DataColumn("Year", typeof(Int32)));
foreach (var product in products)
{
// The order of these must match the order of columns for the Type in SQL!
productList.Rows.Add(product.Name, product.ImageUrl, product.Year);
}
using (var connection = new SqlConnection(_appSettings.ConnectionString))
{
await connection.OpenAsync();
var result = new List<Product>();
try
{
result = await connection.QueryAsync<Product>("[dbo].[ProductAdd]",
new { product = productList },
commandType: CommandType.StoredProcedure
);
}
catch (SqlException ex)
{
// Permissions and other SQL issues are easy to miss, so this helps
throw ex;
}
}
return result;
The User-Defined Type is defined with this:
CREATE TYPE [dbo].[ProductList] AS TABLE(
[Name] [nvarchar](100) NOT NULL,
[ImageUrl] [nvarchar](125) NULL,
[Year] [int] NOT NULL,
)
And the Stored Procedure uses the List passed in as a table. If you are using the same logic for a GET/SELECT and trying to use an IN you would use this same logic. A SQL IN is a JOIN under the covers, so we are just being more explicit about that by using a JOIN on a table in place of the IN
CREATE PROCEDURE ProductAdd
#product ProductList READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [Product] ([Name], [ImageUrl], [Year])
SELECT paramProduct.[Name]
, paramProduct.[ImageUrl]
, paramProduct.[Year]
FROM #product AS paramProduct
LEFT JOIN [Product] AS existingProduct
ON existingProduct.[Name] = paramProduct.[Name]
AND existingProduct.[Year] = paramProduct.[Year]
WHERE existingProduct.[Id] IS NULL
END
GO
This question already has answers here:
Pass Array Parameter in SqlCommand
(11 answers)
Closed 6 years ago.
For some reason the Sqlparameter for my IN() clause is not working. The code compiles fine, and the query works if I substitute the parameter with the actual values
StringBuilder sb = new StringBuilder();
foreach (User user in UserList)
{
sb.Append(user.UserId + ",");
}
string userIds = sb.ToString();
userIds = userIds.TrimEnd(new char[] { ',' });
SELECT userId, username
FROM Users
WHERE userId IN (#UserIds)
You have to create one parameter for each value that you want in the IN clause.
The SQL needs to look like this:
SELECT userId, username
FROM Users
WHERE userId IN (#UserId1, #UserId2, #UserId3, ...)
So you need to create the parameters and the IN clause in the foreach loop.
Something like this (out of my head, untested):
StringBuilder sb = new StringBuilder();
int i = 1;
foreach (User user in UserList)
{
// IN clause
sb.Append("#UserId" + i.ToString() + ",");
// parameter
YourCommand.Parameters.AddWithValue("#UserId" + i.ToString(), user.UserId);
i++;
}
Possible "cleaner" version:
StringBuilder B = new StringBuilder();
for (int i = 0; i < UserList.Count; i++)
YourCommand.Parameters.AddWithValue($"#UserId{i}", UserList[i].UserId);
B.Append(String.Join(",", YourCommand.Parameters.Select(x => x.Name)));
If you are using SQL 2008, you can create a stored procedure which accepts a Table Valued Parameter (TVP) and use ADO.net to execute the stored procedure and pass a datatable to it:
First, you need to create the Type in SQL server:
CREATE TYPE [dbo].[udt_UserId] AS TABLE(
[UserId] [int] NULL
)
Then, you need to write a stored procedure which accepts this type as a parameter:
CREATE PROCEDURE [dbo].[usp_DoSomethingWithTableTypedParameter]
(
#UserIdList udt_UserId READONLY
)
AS
BEGIN
SELECT userId, username
FROM Users
WHERE userId IN (SELECT UserId FROM #UserIDList)
END
Now from .net, you cannot use LINQ since it does not support Table Valued Parameters yet; so you have to write a function which does plain old ADO.net, takes a DataTable, and passes it to the stored procedure: I've written a generic function I use which can do this for any stored procedure as long as it takes just the one table-typed parameter, regardless of what it is;
public static int ExecStoredProcWithTVP(DbConnection connection, string storedProcedureName, string tableName, string tableTypeName, DataTable dt)
{
using (SqlConnection conn = new SqlConnection(connection.ConnectionString))
{
SqlCommand cmd = new SqlCommand(storedProcedureName, conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter p = cmd.Parameters.AddWithValue(tableName, dt);
p.SqlDbType = SqlDbType.Structured;
p.TypeName = tableTypeName;
conn.Open();
int rowsAffected = cmd.ExecuteNonQuery(); // or could execute reader and pass a Func<T> to perform action on the datareader;
conn.Close();
return rowsAffected;
}
}
Then you can write DAL functions which use this utility function with actual names of stored procedures; to build on the example in your question, here is what the code would look like:
public int usp_DoSomethingWithTableTypedParameter(List<UserID> userIdList)
{
DataTable dt = new DataTable();
dt.Columns.Add("UserId", typeof(int));
foreach (var userId in updateList)
{
dt.Rows.Add(new object[] { userId });
}
int rowsAffected = ExecStoredProcWithTVP(Connection, "usp_DoSomethingWithTableTypedParameter", "#UserIdList", "udt_UserId", dt);
return rowsAffected;
}
Note the "connection" parameter above - I actually use this type of function in a partial DataContext class to extend LINQ DataContext with my TVP functionality, and still use the (using var context = new MyDataContext()) syntax with these methods.
This will only work if you are using SQL Server 2008 - hopefully you are and if not, this could be a great reason to upgrade! Of course in most cases and large production environments this is not that easy, but FWIW I think this is the best way of doing this if you have the technology available.
SQL Server sees your IN clause as:
IN ('a,b,c')
What it needs to look like is:
IN ('a','b','c')
There is a better way to do what you're trying to do.
If the user id's are in the DB, then the IN clause should be changed to a subquery, like so:
IN (SELECT UserID FROM someTable WHERE someConditions)
This is a hack -- it doesn't work well with indexes, and you have to be careful it works right with your data, but I've used it successfully in the past:
#UserIDs LIKE '%,' + UserID + ',%' -- also requires #UserID to begin and end with a comma