Insert large array into a SQL Server table variable with Dapper - c#

I have an integer list and I'm trying to insert those values into a table variable, declared with DECLARE statement, using Dapper. I've tried several combinations, but ultimately it leads to Incorrect syntax near ',' error.
Can Dapper even differentiate between a local variable and a Dapper query param, both being with # prefix?
Fiddle
List<int> output = null;
List<int> input = new List<int>
{
1, 2, 3
};
var sql = #"DECLARE #tempTable TABLE (Id INT)
INSERT INTO #tempTable VALUES (#Ids);
SELECT * FROM #tempTable;";
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServer()))
{
output = connection.Query<int>(sql, new { Ids = input }).ToList();
}
Note that the input list can be bigger than 1000.

Dapper will transform the list into seperate parameters, suitable for inclusion in an IN predicate, but not an INSERT ... VALUES query. Instead pass the values as JSON, which is also much, much cheaper for large lists than using separate parameters. EG
List<int> output = null;
List<int> input = new List<int>
{
1, 2, 3
};
var sql = #"
DECLARE #tempTable TABLE (Id INT)
INSERT INTO #tempTable(id) select value from openjson( #Ids );
SELECT * FROM #tempTable;";
var inputJson = System.Text.Json.JsonSerializer.Serialize(input);
using (var con = new SqlConnection("server=localhost;database=tempdb;integrated security=true;trust server certificate=true"))
{
output = con.Query<int>(sql, new { Ids = inputJson }).ToList();
}

Related

How should I pass an array to the FIELD statement using Dapper, c#

I am trying to pass an array as a parameter using Dapper.
My array of values must go into the FIELD section.
I tried to join the array elements into a String and pass it. Still doesn't work.
Guid[] myArr = Ids.ToArray(); // Ids are List<Guid>
var script = #"SELECT * FROM table WHERE Id in #Ids ORDER BY FIELD(Id, #param)";
using (var connection = database.Connection)
{
return connection.Query<MyDataType>(script, new {Ids = Ids, param = myArr}).ToList();
}
This query is just doing an Order By Id. I also passed in param = Ids. Still doesn't work.
Convert the list into array in parameter list of dapper.
var sqlQuery = "Select * from table Where Columnname IN #periodIdStr";
var result = dapperUOW.Connection.Query<Entity>(sqlQuery ,
param: new { periodIdStr = periodIds.ToArray() }, transaction: dapperUOW.Transaction).ToList();
According to this question SELECT * FROM X WHERE id IN (...) with Dapper ORM you should be able to do a WHERE in with dapper but the limit of the number of items in the array is something to watch out for.
Then you could also break out the ORDER BY FIELD SQL and use linq to do an OrderBy on your results.
Edit:
Would this work?
Guid[] myArr = Ids.ToArray(); // Ids are List<Guid>
var script = #"SELECT * FROM table WHERE Id in #Ids)";
using (var connection = database.Connection)
{
var myDataTypeObjects = connection.Query<MyDataType>(script, new {Ids = Ids}).ToList();
myDataTypeObjects = myDataTypeObjects.OrderBy(x => Ids.IndexOf(x.Id)).ToList();
return myDataTypeObjects;
}

Stored procedure with static data not returning values

I have the following stored procedure:
CREATE PROCEDURE [dbo].[usp_GetReportLevel]
AS
DECLARE #TEMP TABLE (BK_REPORT_LEVEL INT, REPORT_LEVEL_NAME VARCHAR(MAX))
INSERT INTO #temp
VALUES (0, 'Client Group'), (1, 'Client'), (2, 'Related Matter'), (3, 'Matter')
SELECT *
FROM #TEMP
GO
The entity database like this:
public IEnumerable<usp_GetReportLevel_Result> usp_GetReportLevel()
{
//const string storedProcedure = "usp_GetReportLevel";
var result = Database.SqlQuery<usp_GetReportLevel_Result>($"EXEC usp_GetReportLevel");
return result;
}
The usp_GetReportLevel_Result as this:
public class usp_GetReportLevel_Result
{
[Column("BK_REPORT_LEVEL")]
public int BK_REPORT_LEVEL;
[Column("REPORT_LEVEL_NAME")]
public string REPORT_LEVEL_NAME;
}
and the c# code in a constructor to select the list items from the stored procedure as this section:
List<SelectListItem> reportLevelList2= new List<SelectListItem>();
var reportLevelusp = context.usp_GetReportLevel();
foreach (var reportLevel in reportLevelusp)
{
SelectListItem lvl = new SelectListItem()
{
Text = reportLevel.REPORT_LEVEL_NAME,
Value = reportLevel.BK_REPORT_LEVEL.ToString()
};
reportLevelList2.Add(lvl);
}
ReportLevelList = reportLevelList2;
Now if I debug that code, the foreach will enter 4 times, telling me that it is reading the database values right, but the values BK_REPORT_LEVEL and REPORT_LEVEL_NAME are always 0 and null respectively.
Why is this happening? The stored procedure is returning what I want to expect if I execute it on the database, Why Linq isn't getting the correct data? I can't see the error
I have had issues with Selecting from Temp tables or table variables from SQL SP's before. Depends on what version of SQL Server you are dealing with. You need to use with result set:
https://www.mssqltips.com/sqlservertip/2356/overview-of-with-result-sets-feature-of-sql-server-2012/
Convert your call to use this:
EXEC usp_GetReportLevel
WITH RESULT SETS
(
(
BK_REPORT_LEVEL INT,
REPORT_LEVEL_NAME varchar(max)
)
)
Try ExecuteSqlCommand:
_context.Database.ExecuteSqlCommand("myproc");
Or with a return parameter:
SqlParameter stmt = new SqlParameter("#stmt", System.Data.SqlDbType.Varchar);
stmt.Direction = System.Data.ParameterDirection.Output;
_context.Database.ExecuteSqlCommand("myproc #stmt out", stmt);
var qry = stmt.value;
Make sure to put the out parameter in the procedure as well.
Didn't have to change anything, all was all right. I just needed to add { get; set; } on the variables....
public class usp_GetReportLevel_Result
{
[Column("BK_REPORT_LEVEL")]
public int BK_REPORT_LEVEL { get; set; }
[Column("REPORT_LEVEL_NAME")]
public string REPORT_LEVEL_NAME { get; set; }
}

SQL return selected result to EF

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)

Parameterize query with IN keyword c#

I am in a application where Parameterized Sql queries are not written. Below is code block
public List<MyClass> GetData(int Id, IEnumerable<string> state)
{
using (var dataContext = new DataContext(_connectionString))
{
var query = new StringBuilder("SELECT * FROM table");
query.Append(" Id = ");
query.Append(Id);
query.Append(" AND state IN ('");
query.Append(string.Join("','", state));
query.Append("')");
return dataContext.ExecuteQuery<MyClass>(query.ToString()).ToList();
}
I am refactoring code using parameterized query like this :
public List<MyClass> GetData(int Id, IEnumerable<string> state)
{
using (var dataContext = new DataContext(_connectionString))
{
var statestring = new StringBuilder("'");
statestring.Append(string.Join("','", state));
statestring.Append("'");
string myStates= statestring.ToString();
string query = "SELECT * FROM table WHERE Id ={0} AND state IN ({1})";
return dataContext.ExecuteQuery<MyClass>(query, new object[] {Id, myStates}).ToList();
}
}
I get no data on running this query. On debugging i found my query is getting formed like this
SELECT * FROM table WHERE Id ={0} AND state IN ({1}) where in ({1})
For state I see data as "'error',' warning'".
In sql server I run query like this
SELECT * FROM table WHERE Id =34 AND state IN ('error','warning').
Do i need to remove " " around mystate? I tried removing " using trim method and assigning it back to string but it didn't work. I can still see double quotes.
myStates = myStates.trim('"');
How can parameterize my query better without using any string builder for the same
Alternative suggestion: dapper...
int x = ...;
int[] y = ...
var items = connection.Query<MyClass>(
"SELECT * FROM table WHERE X = #x AND Y in #y", new {x,y}).AsList();
Dapper will deal with this for you, using an appropriate query for 0, 1, or many items, including (optional configuration options) padding the parameters to avoid query plan saturation (so when you have large lists, you use the same query and query-plan for 47 items, 48 items and 49 items, but possibly a different query for 50), and using string_split if it is supported on your server.
To parameterize the in clause every case has to be an individual parameters. So the in clause has to reflect that.
See this similar question: How to pass sqlparameter to IN()?
public List<MyClass> GetData(int Id, IEnumerable<string> state)
{
using (var dataContext = new DataContext(_connectionString))
{
var stateParameterNumbers = Enumerable.Range(1, state.Count())
.Select(i => string.Format("{{{0}}}", i));
var stateParameterString = string.Join(",", stateParameterNumbers);
string query = "SELECT * FROM table WHERE Id ={0} AND state IN (" + stateParameterString + ")";
return dataContext.ExecuteQuery<MyClass>(query, new object[] { Id }.Concat(state).ToArray()).ToList();
}
}
I think that you should change the way you pass the parameters:
return dataContext.ExecuteQuery<MyClass>(query, Id, stateString).ToList();
For reference please have a look at the signature of this method, which can be found here.

Dapper: Help me run stored procedure with multiple user defined table types

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

Categories