advance search text box in angular - sql-server

I work on an online search project
I want to make a search textbox that works like: if I want to search about a book that Joe wrote it with title my book and publisher is tia.
I type in the search: joe my book tia or tia jo book >
so I will get a result for it.
tia is from a table in SQL database
joe is from a table in SQL database
my book is from a table in SQL database
can somebody help me?

You can easily accomplish that if you add another column, that will contain concatenated data from other three columns, to your data table (in your database) and then make a search in that column.
writer title publisher search_column
joe my book tia joe my book tia
Then, you can make SQL query to search by that column with LIKE
example with ExecuteReader.
var query = "select * from my_table where 1 = 1 " + filterQuery;
Create parameters:
public static SqlParameter AddSqlParameter(string parameterName, object value)
{
var p = new SqlParameter(parameterName, value);
return p;
}
List<SqlParameter> sqlParameters = new List<SqlParameter>();
var filterQuery = "";
this will split your search input by spaces
string[] words = searchInput.Split(' ');
loop through your search phrases and add one parameter for each phrase found:
for (int i = 0; i < words.Count; i++) {
sqlParameters.Add(AddSqlParameter("#p" + i.ToString(), words[i]));
filterQuery = filterQuery + " AND search_column LIKE " + "#p" + i.ToString();
}
add your search query and parameters to ExecuteReader:
public static List<T> ExecuteReader<T>(string commandText, List<SqlParameter> parameters) where T : new()
{
List<T> output = new List<T>();
using (SqlConnection con = new SqlConnection(mySetting.ConnectionString))
using (SqlCommand cmd = new SqlCommand(commandText, con))
{
cmd.Parameters.AddRange(parameters.ToArray());
con.Open();
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
T t = new T();
for (int i = 0; i < rdr.FieldCount; i++)
{
Type type = t.GetType();
PropertyInfo prop = type.GetProperty(rdr.GetName(i));
if (prop != null)
{
prop.SetValue(t, rdr.GetValue(i) is DBNull ? null : rdr.GetValue(i), null);
}
}
output.Add(t);
}
return output;
}
}
}
call your ExecuteReader like this:
var result = ExecuteReader<myClass>(query, sqlParameters );
If you have further questions, just ask.

Related

Preparing statements and batching in npgsql

The Simple Preparation example in the docs (https://www.npgsql.org/doc/prepare.html#simple-preparation) shows an example where parameters are set after the command is prepared.
var cmd = new NpgsqlCommand(...);
cmd.Parameters.Add("param", NpgsqlDbType.Integer);
cmd.Prepare();
// Set parameters
cmd.ExecuteNonQuery();
// And so on
Questions
How are the parameters set?
Is it possible to use AddWithValue instead of Add if the AddWithValue(String, NpgsqlDbType, Object) method which specifies NpgsqlDbType is used -- docs say "setting the value isn't support"?
How does this work if multiple statements exist in the same command?
This answer (https://stackoverflow.com/a/53268090/10984827) shows that multiple commands in a single string can be prepared together but it's not clear how this CommandText string is created.
Edit: I think I'm almost there but I'm not sure how to create and execute the batched the query string. Here's my naive attempt at building a batched query using a StringBuilder. This doesn't work. How do I do this correctly?
using System;
using System.Collections.Generic;
using System.Text;
using Npgsql;
using NpgsqlTypes;
class Model
{
public int value1 { get; }
public int value2 { get; }
public Model(int value1, int value2)
{
this.value1 = value1;
this.value2 = value2;
}
}
class Program
{
static void Main(string[] args)
{
var dataRows = new List<Model>();
dataRows.Add(new Model(3,2));
dataRows.Add(new Model(27,-10));
dataRows.Add(new Model(11,-11));
var connString = "Host=127.0.0.1;Port=5432;Username=postgres;Database=dbtest1";
// tabletest1
// ----------
// id SERIAL PRIMARY KEY
// , value1 INT NOT NULL
// , value2 INT NOT NULL
using (var conn = new NpgsqlConnection(connString))
{
conn.Open();
var cmd = new NpgsqlCommand();
cmd.Connection = conn;
cmd.CommandText = $"INSERT INTO tabletest1 (value1,value2) VALUES (#value1,#value2)";
var parameterValue1 = cmd.Parameters.Add("value1", NpgsqlDbType.Integer);
var parameterValue2 = cmd.Parameters.Add("value2", NpgsqlDbType.Integer);
cmd.Prepare();
var batchCommand = new StringBuilder();
foreach (var d in dataRows)
{
parameterValue1.Value = d.value1;
parameterValue2.Value = d.value2;
batchCommand.Append(cmd.CommandText);
batchCommand.Append(";");
}
Console.WriteLine(batchCommand.ToString());
// conn.ExecuteNonQuery(batchCommand.ToString());
}
}
}
1) Simply capture the NpgsqlParameter returned from Add(), and then set its Value property:
var p = cmd.Parameters.Add("p", NpgsqlDbType.Integer);
cmd.Prepare();
p.Value = 8;
cmd.ExecuteNonQuery();
2) You can use AddWithValue() in the same way, but if you're preparing the command in order to reuse it several times, that makes less sense. The idea is that you first add the parameter without a value, then prepare, then execute it several times, setting the value each time.
3) You can prepare a multi-statement command. As things work now, all statements in the command will share the same parameter list (which lives on NpgsqlCommand). So the same pattern holds: create your command with your SQL and parameters, prepare it, and then set parameter values and execute. Each individual statement within your command will run prepared, benefiting from the perf increase.
Here's an example with two-statement batching:
cmd.CommandText = "INSERT INTO tabletest1 (value1,value2) VALUES (#v1,#v2); INSERT INTO tabletest1 (value1, value2) VALUES (#v3,#v4)";
var v1 = cmd.Parameters.Add("v1", NpgsqlDbType.Integer);
var v2 = cmd.Parameters.Add("v2", NpgsqlDbType.Integer);
var v3 = cmd.Parameters.Add("v3", NpgsqlDbType.Integer);
var v4 = cmd.Parameters.Add("v4", NpgsqlDbType.Integer);
cmd.Prepare();
while (...) {
v1.Value = ...;
v2.Value = ...;
v3.Value = ...;
v4.Value = ...;
cmd.ExecuteNonQuery();
}
However, if the objective is to efficiently insert lots of data, consider using COPY instead - it will be faster than even batched inserts.
Finally, to complete the picture, for INSERT statements specifically you can include more than one row in a single statement:
INSERT INTO tabletest1 (value1, value2) VALUES (1,2), (3,4)
You can also again parameterize the actual values, and prepare this command. This is similar to batching two INSERT statements, and should be faster (although still slower than COPY).
In NpgSQL 6.0 there has been the addition of batching/pipelining.
Here is an updated example:
await using var connection = new NpgsqlConnection(connString);
await connection.OpenAsync();
var batch = new NpgsqlBatch(connection);
const int count = 10;
const string parameterName = "parameter";
for (int i = 0; i < count; i++)
{
var batchCommand = new NpgsqlBatchCommand($"SELECT #{parameterName} as value");
batchCommand.Parameters.Add(new NpgsqlParameter(parameterName, i));
batch.BatchCommands.Add(batchCommand);
}
await batch.PrepareAsync();
var results = new List<int>(count);
await using (var reader = await batch.ExecuteReaderAsync())
{
do
{
while (await reader.ReadAsync())
{
results.Add(await reader.GetFieldValueAsync<int>("value"));
}
} while (await reader.NextResultAsync());
}
Console.WriteLine(string.Join(", ", results));

SSIS - Sending email different recipients different data

I'm new in SSIS.
I have a table with 1500 rows and I need to send emails from that table but each recipients has 15 rows from the table.
So I need to send different data to different emails from the same query.
Can you guys could help me please?
Thanks in advance.
Leo
-------------------update------------------------
Guys I could create a SSIS package to send email to different recipients the problem is: sample: 2 different users is receiving emails for the number of rows that they have in database...that's terrible each customer has 15 lines it will be 15 emails can I send just one email for customer contains the whole data?
Thanks in advance...
This is going to vary somewhat based on the query and other specifications, but at a high level you're probably going to want to follow these steps for sending the emails using SSIS. This example assumes that the emails are stored in a column within this table. As others have pointed out, using sp_send_dbmail will likely be your best option.
Create two string variables. One will hold the email addresses and the other will be for the SQL for sp_send_dbmail (more on this below). Create an additional variable of the object type that will hold the list of emails during execution.
Modify the string variable that will hold the SQL for sp_send_dbmail to be an expression using the variable with the email names. Depending on the query, you may need to add additional variables for other parameters in this query. An example of this variable is at the end of this post.
Have an initial Execute SQL Task that queries the table and retrieves the email addresses. Make sure to get all rows for each email. Set the ResultSet property to full and on the Result Set pane, add the object variable with 0 as the Result Name.
Next add a Foreach Loop, use the Foreach ADO Enumerator type, and select the object variable from the last Execute SQL Task for the source variable. The Enumeration Mode can be left as the "Rows in the first table" option.
On the Variable Mappings pane, add the string variable (for the email addresses) and set the index to 0. This will hold the email addresses for each execution of sp_send_dbmail.
Within the Foreach Loop, add an Execute SQL Task. For this, you will need to set the SQLSourceType to variable and use a variable holding the SQL with sp_send_dbmail.
Make sure that you have Database Mail properly configured for the account and profile used, including membership in the DatabaseMailUserRole role in msdb. You may also need to use the three-part name (database.schema.table) for your table.
Example SQL Variable Expression:
Note the double-quotes in the #query parameter around the email variable in addition to the quotes from concatenating the expression. You can either use two single quotes or precede a double-quote with a \ in the query to use a double-quote as part of the expression.
"DECLARE #Title varchar(100)
SET #Title = 'Email Title'
EXEC MSDB.DBO.SP_SEND_DBMAIL #profile_name = 'Your Profile',
#recipients = 'YourEmail#test.org',
#query = 'SELECT * FROM YourDatabase.YourSchema.YourTable WHERE EmailColumn = ""
+ #[User::VariableWithEmailAddress] + ""',
#query_result_no_padding = 1, #subject = #Title ; "
I have a package that sole role is to send emails from my packages and record the results in to a table. I use this package over and over from any package that sends mail.
It is simply a script task, that takes parameters and does the work:
The script to process:
public void Main()
{
//Read variables
#region ReadVariables
string cstr = Dts.Variables["connString"].Value.ToString();
//string sender = (string)Dts.Variables["User::Sender"].Value;
string title = (string)Dts.Variables["$Package::Title"].Value;
string priority = (string)Dts.Variables["$Package::Priority"].Value;
string body = (string)Dts.Variables["$Package::Body"].Value;
string source = Dts.Variables["$Package::Source"].Value.ToString();
string directTo = Dts.Variables["$Package::DirectMail"].Value.ToString();
string groups = Dts.Variables["$Package::MailGroups"].Value.ToString();
#endregion
//Send Email
#region SendMail
MailMessage mail = new MailMessage();
//mail.From = new MailAddress(sender);
mail.Subject = title;
mail.Body = body;
mail.IsBodyHtml = true;
switch(priority.ToUpper())
{
case "HIGH":
mail.Priority= MailPriority.High;
priority = "High";
break;
default:
mail.Priority=MailPriority.Normal;
priority = "Normal";
break;
}
DataTable dt = new DataTable(); //This is going to be a full distribution list
//Fill table with group email
if (groups.Split(',').Length > 0)
{
foreach (string group in groups.Split(','))
{
string strCmd = "mail.spGetEmailAddressesByGroup";
using (OleDbConnection conn = new OleDbConnection(cstr))
{
using (OleDbCommand cmd = new OleDbCommand(strCmd, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("A", group);
OleDbDataAdapter da = new OleDbDataAdapter(cmd);
da.Fill(dt);
}
}
}
}
//add the directs to email
if (directTo.Split(',').Length > 0)
{
foreach (string m in directTo.Split(','))
{
if (m != "")
{
DataRow dr = dt.NewRow();
dr[0] = "TO";
dr[1] = m;
dt.Rows.Add(dr);
}
}
}
//Add from and reply to defaults
DataRow dr2 = dt.NewRow();
dr2[0] = "REPLYTO";
dr2[1] = ""; //WHERE DO YOU WANT REPLIES
dt.Rows.Add(dr2);
DataRow dr3 = dt.NewRow();
dr3[0] = "FROM";
dr3[1] = ""; //ENTER WHO YOU WANT THE EMAIL TO COME FROM
dt.Rows.Add(dr3);
//Bind dt to mail
foreach (DataRow dr in dt.Rows)
{
switch (dr[0].ToString().ToUpper())
{
case "TO":
mail.To.Add(new MailAddress(dr[1].ToString()));
dr[0] = "To";
break;
case "CC":
mail.CC.Add(new MailAddress(dr[1].ToString()));
dr[0] = "Cc";
break;
case "BCC":
mail.Bcc.Add(new MailAddress(dr[1].ToString()));
dr[0] = "Bcc";
break;
case "REPLYTO":
mail.ReplyToList.Add(new MailAddress(dr[1].ToString()));
dr[0] = "ReplyTo";
break;
case "FROM":
mail.From = new MailAddress(dr[1].ToString());
dr[0] = "From";
break;
case "SENDER":
mail.Sender = new MailAddress(dr[1].ToString());
dr[0] = "Sender";
break;
default:
dr[0] = "NotSent";
break;
}
}
try
{
SmtpClient smtp = new SmtpClient();
smtp.Port = 25;
smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
smtp.UseDefaultCredentials = false;
smtp.Host = ""; //ENTER YOUR IP / SERVER
smtp.Send(mail);
}
catch (Exception e)
{
}
#endregion
//Record email as sent //I WILL NOT BE PROVIDING THIS PART
//#region RecordEmailInDB
That's just to send mails, I have many packages that build the emails to send. Most are variables to parameters on the call. The most complicated is the building of the Email Body and this is where your specific question comes into play.
This is a sample control flow:
There's a data flow that queries the details that need to be sent and are recorded into an object. As well as a record counter.
Back to control flow. There is a precendence constraint set to rowcount >0.
The I have a script task to build the body basically. And I have a class that converts the ADO Object into an HTML table.
public string BuildHTMLTablefromDataTable(DataTable t)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append("<table border='1'><tr style='background-color: #1A5276; color:#FFFFFF;'>");
foreach (DataColumn c in t.Columns)
{
sb.Append("<th align='left'>");
sb.Append(c.ColumnName);
sb.Append("</th>");
}
sb.Append("</tr>");
int rc = 0;
foreach (DataRow r in t.Rows)
{
rc++;
//every other row switches from white to gray
string OpeningTR = "<tr style='background-color: " + ((rc % 2 == 1) ? "#E5E7E9;'>" : "#FCF3CF;'>");
sb.Append(OpeningTR);
foreach (DataColumn c in t.Columns)
{
sb.Append("<td align='left'>");
sb.Append(System.Web.HttpUtility.HtmlEncode(
r[c.ColumnName] == null ? String.Empty : r[c.ColumnName].ToString()
)); //This will handle any invalid characcters and convert null to empty string
sb.Append("</td>");
}
sb.Append("</tr>");
}
sb.Append("</table>");
return sb.ToString();
}
public string BuildBody(DataTable dt)
{
string body = "<P>The following are vouchers that are not in the voucher table but in the GL:</p>";
DataView v = new DataView(dt);
body += BuildHTMLTablefromDataTable(dt); //v.ToTable(true, "Name", "LastVisit", "DaysUntilTimeout", "ExpDate", "RoleName"));
return body;
}
public void Main()
{
#region Read Variables
System.Data.OleDb.OleDbDataAdapter da = new System.Data.OleDb.OleDbDataAdapter();
DataTable dt = new DataTable();
da.Fill(dt, Dts.Variables["User::Changes"].Value);
#endregion
string body = BuildBody(dt);
Dts.Variables["User::Body"].Value = body;
Dts.TaskResult = (int)ScriptResults.Success;
}
Finally I will call the SendMail package and pass the parameters.
For your purpose you will need to have a foreach around this package and adjust your where clause for the person on each pass.
This is an example of an email sent (Body only):

Export data from excel to a database table using ClosedXML example

i have to read rows of data from an excel file which has only a column and then i have to save the rows in a table in database .
In my project i have to use ClosedXML .dll .
I have search but i couldn't find an example .
Can you please help me?
Thanks
For the ClosedXML part, you can refer to the documentation at https://github.com/ClosedXML/ClosedXML/wiki/Finding-and-extracting-the-data
private static void Main()
{
List<String> categories;
List<String> companies;
ExtractCategoriesCompanies("NorthwindData.xlsx", out categories, out companies);
// Do something with the categories and companies
}
private static void ExtractCategoriesCompanies(string northwinddataXlsx, out List<string> categories, out List<string> companies)
{
categories = new List<string>();
const int coCategoryId = 1;
const int coCategoryName = 2;
var wb = new XLWorkbook(northwinddataXlsx);
var ws = wb.Worksheet("Data");
// Look for the first row used
var firstRowUsed = ws.FirstRowUsed();
// Narrow down the row so that it only includes the used part
var categoryRow = firstRowUsed.RowUsed();
// Move to the next row (it now has the titles)
categoryRow = categoryRow.RowBelow();
// Get all categories
while (!categoryRow.Cell(coCategoryId).IsEmpty())
{
String categoryName = categoryRow.Cell(coCategoryName).GetString();
categories.Add(categoryName);
categoryRow = categoryRow.RowBelow();
}
// There are many ways to get the company table.
// Here we're using a straightforward method.
// Another way would be to find the first row in the company table
// by looping while row.IsEmpty()
// First possible address of the company table:
var firstPossibleAddress = ws.Row(categoryRow.RowNumber()).FirstCell().Address;
// Last possible address of the company table:
var lastPossibleAddress = ws.LastCellUsed().Address;
// Get a range with the remainder of the worksheet data (the range used)
var companyRange = ws.Range(firstPossibleAddress, lastPossibleAddress).RangeUsed();
// Treat the range as a table (to be able to use the column names)
var companyTable = companyRange.AsTable();
// Get the list of company names
companies = companyTable.DataRange.Rows()
.Select(companyRow => companyRow.Field("Company Name").GetString())
.ToList();
}
EDIT: The below only gets you a populated datatable. You'll then need to load that datatable into your database. You don't say which database this is, but for SQL Server you would use the SqlBulkCopy class (see definition, which also has an example).
Late to the party, but try this:
public static DataTable GetDataTableFromExcel(string path, string sheetname = "", bool hasHeader = true)
{
using (var workbook = new XLWorkbook(path))
{
IXLWorksheet worksheet;
if (string.IsNullOrEmpty(sheetname))
worksheet = workbook.Worksheets.First();
else
worksheet = workbook.Worksheets.FirstOrDefault(x => x.Name == sheetname);
var rangeRowFirst = worksheet.FirstRowUsed().RowNumber();
var rangeRowLast = worksheet.LastRowUsed().RowNumber();
var rangeColFirst = worksheet.FirstColumnUsed().ColumnNumber();
var rangeColLast = worksheet.LastColumnUsed().ColumnNumber();
DataTable tbl = new DataTable();
for (int col = rangeColFirst; col <= rangeColLast; col++)
tbl.Columns.Add(hasHeader ? worksheet.FirstRowUsed().Cell(col).Value.ToString() : $"Column {col}");
Logger("started creating datatable");
rangeRowFirst = rangeRowFirst + (hasHeader ? 1 : 0);
var colCount = rangeColLast - rangeColFirst;
for (int rowNum = rangeRowFirst; rowNum <= rangeRowLast; rowNum++)
{
List<string> colValues = new List<string>();
for (int col = 1; col <= colCount; col++)
{
colValues.Add(worksheet.Row(rowNum).Cell(col).Value.ToString());
}
tbl.Rows.Add(colValues.ToArray());
}
Logger("finished creating datatable");
return tbl;
}
}
and call like this:
var datatable = GetDataTableFromExcel(fileName, sheetName);
If you're using (the excellent and free in its basic form) LinqPad, you can inspect the datatable using datatable.Dump();

Prevent SQL injection when building query

I know usually how to prevent it using preparedStatements, but now I have such a method for bulding queries. For example in Java:
private String buildQuery(String where) {
String query = "SELECT id, name FROM someTable";
if(where.length() > 0) {
query = query + " WHERE " + where;
}
return query;
}
'where' string is like this 'variable = value'. How can i prevent it here? I thought of passing variable and value separately, creating prepared statement using them and then returning that prepared statement as string somehow, but I'm not sure.
This is not specific to any one DB API.
TL;DR: Don't pass "SQL fragments" around.
Rather than passing complete clauses fro a select statement, or (sub-)expressions to add into a select clause, pass the components keeping the user data separate from the identifiers.
In this case do not pass name = value, pass them separately. Then validate name is a valid column for the table, and generate a parameter for the value part.
Thus, pseudo-code (my Java is rusty):
function BuildCommand(string column, object value) {
if !IsValidColumn("theTable", column)) throw InvalidOperation(...)
string sql = "Select column from theTable where " + column + " = #p0";
SqlCommand cmd = new SqlCommand(sql);
cmd.Parameters.Add("#p0", value);
return cmd;
}
You can use a map to pass your values and build a preparedStatement. Check the code below it should be something similar to that logic
public static PreparedStatement buildQuery(String where,Map<Integer, String> cond)
throws SQLException {
PreparedStatement stat = null;
String query = "SELECT id, name FROM someTable " + where;
try {
stat = con.prepareStatement(query);
for (Map.Entry<Integer, String> e : cond.entrySet()) {
stat.setString(e.getKey(), e.getValue());
}
} catch (SQLException e ) {
// Handle ex
} finally {
}
return stat;
}
public static void main(String[] a) throws SQLException {
Map<Integer,String> cond =new HashMap<Integer, String>();
cond.put(1,"val22");
cond.put(2,"val2");
buildQuery("col1 = ? and col2= ?", cond);
}
My suggestion is that if you have an array of where clauses in the parameter and rewrite the function as :
private String buildQuery(String[] where) {
String query = "SELECT id, name FROM someTable";
query = query + " WHERE "
for(int i = 0; i < where.length; i++) {
if(i > 0){
query = query + " AND "
}
query = query + w + " = ?";
}
return query;
}

Is it possible to use `SqlDbType.Structured` to pass Table-Valued Parameters in NHibernate?

I want to pass a collection of ids to a stored procedure that will be mapped using NHibernate. This technique was introduced in Sql Server 2008 ( more info here => Table-Valued Parameters ). I just don't want to pass multiple ids within an nvarchar parameter and then chop its value on the SQL Server side.
My first, ad hoc, idea was to implement my own IType.
public class Sql2008Structured : IType {
private static readonly SqlType[] x = new[] { new SqlType(DbType.Object) };
public SqlType[] SqlTypes(NHibernate.Engine.IMapping mapping) {
return x;
}
public bool IsCollectionType {
get { return true; }
}
public int GetColumnSpan(NHibernate.Engine.IMapping mapping) {
return 1;
}
public void NullSafeSet(DbCommand st, object value, int index, NHibernate.Engine.ISessionImplementor session) {
var s = st as SqlCommand;
if (s != null) {
s.Parameters[index].SqlDbType = SqlDbType.Structured;
s.Parameters[index].TypeName = "IntTable";
s.Parameters[index].Value = value;
}
else {
throw new NotImplementedException();
}
}
#region IType Members...
#region ICacheAssembler Members...
}
No more methods are implemented; a throw new NotImplementedException(); is in all the rest. Next, I created a simple extension for IQuery.
public static class StructuredExtensions {
private static readonly Sql2008Structured structured = new Sql2008Structured();
public static IQuery SetStructured(this IQuery query, string name, DataTable dt) {
return query.SetParameter(name, dt, structured);
}
}
Typical usage for me is
DataTable dt = ...;
ISession s = ...;
var l = s.CreateSQLQuery("EXEC some_sp #id = :id, #par1 = :par1")
.SetStructured("id", dt)
.SetParameter("par1", ...)
.SetResultTransformer(Transformers.AliasToBean<SomeEntity>())
.List<SomeEntity>();
Ok, but what is an "IntTable"? It's the name of SQL type created to pass table value arguments.
CREATE TYPE IntTable AS TABLE
(
ID INT
);
And some_sp could be like
CREATE PROCEDURE some_sp
#id IntTable READONLY,
#par1 ...
AS
BEGIN
...
END
It only works with Sql Server 2008 of course and in this particular implementation with a single-column DataTable.
var dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
It's POC only, not a complete solution, but it works and might be useful when customized. If someone knows a better/shorter solution let us know.
A simpler solution than the accepted answer would be to use ADO.NET. NHibernate allows users to enlist IDbCommands into NHibernate transactions.
DataTable myIntsDataTable = new DataTable();
myIntsDataTable.Columns.Add("ID", typeof(int));
// ... Add rows to DataTable
ISession session = sessionFactory.GetSession();
using(ITransaction transaction = session.BeginTransaction())
{
IDbCommand command = new SqlCommand("StoredProcedureName");
command.Connection = session.Connection;
command.CommandType = CommandType.StoredProcedure;
var parameter = new SqlParameter();
parameter.ParameterName = "IntTable";
parameter.SqlDbType = SqlDbType.Structured;
parameter.Value = myIntsDataTable;
command.Parameters.Add(parameter);
session.Transaction.Enlist(command);
command.ExecuteNonQuery();
}
For my case, my stored procedure needs to be called in the middle of an open transaction.
If there is an open transaction, this code works because it is automatically reusing the existing transaction of the NHibernate session:
NHibernateSession.GetNamedQuery("SaveStoredProc")
.SetInt64("spData", 500)
.ExecuteUpdate();
However, for my new Stored Procedure, the parameter is not as simple as an Int64. It's a table-valued-parameter (User Defined Table Type)
My problem is that I cannot find the proper Set function.
I tried SetParameter("spData", tvpObj), but it's returning this error:
Could not determine a type for class: …
Anyways, after some trial and error, this approach below seems to work.
The Enlist() function is the key in this approach. It basically tells the SQLCommand to use the existing transaction. Without it, there will be an error saying
ExecuteNonQuery requires the command to have a transaction when the
connection assigned to the command is in a pending local transaction…
using (SqlCommand cmd = NHibernateSession.Connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = "MyStoredProc";
NHibernateSession.Transaction.Enlist(cmd); // Because there is a pending transaction
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("#wiData", SqlDbType.Structured) { Value = wiSnSqlList });
int affected = cmd.ExecuteNonQuery();
}
Since I am using the SqlParameter class with this approach, SqlDbType.Structured is available.
This is the function where wiSnList gets assigned:
private IEnumerable<SqlDataRecord> TransformWiSnListToSql(IList<SHWorkInstructionSnapshot> wiSnList)
{
if (wiSnList == null)
{
yield break;
}
var schema = new[]
{
new SqlMetaData("OriginalId", SqlDbType.BigInt), //0
new SqlMetaData("ReportId", SqlDbType.BigInt), //1
new SqlMetaData("Description", SqlDbType.DateTime), //2
};
SqlDataRecord row = new SqlDataRecord(schema);
foreach (var wi in wiSnList)
{
row.SetSqlInt64(0, wi.OriginalId);
row.SetSqlInt64(1, wi.ShiftHandoverReportId);
if (wi.Description == null)
{
row.SetDBNull(2);
}
else
{
row.SetSqlString(2, wi.Description);
}
yield return row;
}
}
You can pass collections of values without the hassle.
Example:
var ids = new[] {1, 2, 3};
var query = session.CreateQuery("from Foo where id in (:ids)");
query.SetParameterList("ids", ids);
NHibernate will create a parameter for each element.

Resources