Using Dapper.TVP TableValueParameter with other parameters - c#

I have a procedure that takes in a table-valued parameter, along with others:
CREATE PROCEDURE [dbo].[Update_Records]
#currentYear INT,
#country INT,
#records Record_Table_Type READONLY
AS
and am trying to call this with Dapper.TVP.
Here is the code I have so far:
var recordsParameter = new List<SqlDataRecord>();
// This metadata matches 'Record_Table_Type' in the Database
var recordsMetaData = new[]
{
new SqlMetaData("OriginalValue", SqlDbType.Decimal, 19, 4),
new SqlMetaData("NewValue", SqlDbType.Decimal, 19, 4),
new SqlMetaData("NewPercent", SqlDbType.Decimal, 7, 2),
};
foreach (var r in records)
{
var record = new SqlDataRecord(recordsMetaData);
record.SetDecimal(0, r.OriginalValue);
record.SetDecimal(1, r.NewValue);
record.SetDecimal(2, r.NewPercent);
recordsParameter.Add(record);
}
var spParams = new DynamicParameters(new
{
currentYear = filter.currentYear,
country = filter.country,
});
var recordsParam = new TableValueParameter("#records", "Record_Table_Type", recordsParameter);
using (var connection = ConnectionFactory.GetConnection())
{
connection.Execute("Update_Records", ???, commandType: CommandType.StoredProcedure);
}
My issue is how do I pass both sets of parameters to the procedure in the call to Dapper Execute()?
I have tried:
var spParams = new DynamicParameters(new
{
currentYear = filter.currentYear,
country = filter.country,
records = new TableValueParameter("#records", "Record_Table_Type", recordsParameter);
});
connection.Execute("Update_Records", spParams, commandType: CommandType.StoredProcedure);
and
connection.Execute("Update_Records", new Object[] { spParams, recordsParam }, commandType: CommandType.StoredProcedure);
Both call the procedure, but pass an empty table parameter ( SELECT COUNT(*) FROM #records returns 0 )
I can't seem to find any actual documentation or source for Dapper.TVP, so the whole thing is very confusing, and the 2nd parameter to .Execute() is just a dynamic so that again doesn't tell me what I can and can't pass to it.
Any ideas?

I am on mobile and may be misunderstanding the question, but this should be just:
DataTable records = ...
connection.Execute("Update_Records",
new {
currentYear = filter.currentYear,
country = filter.country,
records
},
commandType: CommandType.StoredProcedure
);

Based on an answer from Mark Gravell here: Does Dapper support SQL 2008 Table-Valued Parameters?
I changed my code to no longer use Dapper.TVP and instead just use a DataTable so the code is now:
var recordsTable = new DataTable();
recordsTable.Columns.Add("NewValue", typeof(Decimal));
foreach (var netRevenue in records)
{
var row = recordsTable.NewRow();
row[0] = netRevenue.NewValue;
recordsTable.Rows.Add(row);
}
recordsTable.EndLoadData();
var spParams = new DynamicParameters(new
{
currentYear = filter.currentYear,
country = filter.country,
records = recordsTable.AsTableValuedParameter("Record_Table_Type")
});
using (var connection = ConnectionFactory.GetConnection())
{
connection.Execute("Update_Records", spParams, commandType: CommandType.StoredProcedure);
}
And this works.

Related

Oracle stored procedure with multiple arrays and scalars from managed data access

I am trying to call the following Oracle stored procedure from the Oracle managed data access client
PROCEDURE CLONE_PRODUCT(p_f_cloned_prod_id IN product.product_id%TYPE,
p_f_name IN product.name%TYPE,
p_f_desc IN product.presentation_value%TYPE,
p_f_sys_issue IN product.product_reference%TYPE,
p_f_feature_names IN T_CHAR_TAB,
p_f_feature_values IN T_CHAR_TAB,
p_f_audit_user IN product.last_updated_by%TYPE,
p_f_product_id OUT product.product_id%TYPE)
where
TYPE t_char_tab IS TABLE OF VARCHAR2(1000) INDEX BY BINARY_INTEGER;
with this C# code:
using (var cloneProductCmd = new OracleCommand("SPF_SQL.CLONE_PRODUCT", con))
{
cloneProductCmd.BindByName = true;
cloneProductCmd.CommandType = System.Data.CommandType.StoredProcedure;
cloneProductCmd.Parameters.Add("P_F_CLONED_PROD_ID", 1);
cloneProductCmd.Parameters.Add("P_F_NAME", "bob");
cloneProductCmd.Parameters.Add("P_F_DESC", "bob smith");
cloneProductCmd.Parameters.Add("P_F_SYS_ISSUE", 123);
var featureNames = new OracleParameter()
{
ParameterName = "P_F_FEATURE_NAMES",
Direction = System.Data.ParameterDirection.Input,
OracleDbType = OracleDbType.Varchar2,
Value = new string[] { "feature 1" }
};
cloneProductCmd.Parameters.Add(featureNames);
var featureValues = new OracleParameter()
{
ParameterName = "P_F_FEATURE_VALUES",
Direction = System.Data.ParameterDirection.Input,
OracleDbType = OracleDbType.Varchar2,
Value = new string[] { "value 1" }
};
cloneProductCmd.Parameters.Add(featureValues);
cloneProductCmd.Parameters.Add("P_F_AUDIT_USER", "me");
cloneProductCmd.Parameters.Add("P_F_PRODUCT_ID", OracleDbType.Decimal, System.Data.ParameterDirection.Output);
cloneProductCmd.ArrayBindCount = 1;
var reader = await cloneProductCmd.ExecuteNonQueryAsync();
newProductId = Convert.ToInt32(cloneProductCmd.Parameters["P_F_PRODUCT_ID"].Value.ToString());
}
and I have tried changing the ArraybindCount to 2 (2 arrays of length 1) and also tried specifying that the array parameters have a collectionType of PLSQLAssociativeArray.
I always get an exception with the message:
Unable to cast object of type 'System.Int32' to type 'System.Array'
this answer and this article suggest that the ArrayBindCount property means that the client is expecting an array for all parameters.
My question is how can I call a stored procedure passing in multiple scalar values and multiple arrays (all arrays are of the same number of elements) and also an out parameter (scalar)?
I eventually solved this, with the help of this answer I tweaked my code and got it to work.
in short the ArrayBindCount appear to be unecessary, but for each array parameter the CollectionType, Size, ArrayBindSize and ArrayBindStatus are necessary, I also created the parameters by directly adding to the Parameter collection of the command rather than creating them standalone and then adding them to the collection, not sure if that is relevant.
here is my working code:
using (var cloneProductCmd = new OracleCommand("SPF_SQL.CLONE_PRODUCT", con))
{
cloneProductCmd.BindByName = true;
cloneProductCmd.CommandType = System.Data.CommandType.StoredProcedure;
cloneProductCmd.Parameters.Add("P_F_CLONED_PROD_ID", product.OriginalProductId);
cloneProductCmd.Parameters.Add("P_F_NAME", productName);
cloneProductCmd.Parameters.Add("P_F_DESC", fullProduct.ProductName);
cloneProductCmd.Parameters.Add("P_F_SYS_ISSUE", fullProduct.SystemIssueNumber);
var featureNames = cloneProductCmd.Parameters.Add("P_F_FEATURE_NAMES", OracleDbType.Varchar2);
featureNames.Direction = System.Data.ParameterDirection.Input;
featureNames.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
featureNames.Value = features.Select(_ => _.Key).ToArray();
featureNames.Size = features.Count();
featureNames.ArrayBindSize = features.Select(_ => _.Key.Length).ToArray();
featureNames.ArrayBindStatus = Enumerable.Repeat(OracleParameterStatus.Success, features.Count()).ToArray();
var featureValues = cloneProductCmd.Parameters.Add("P_F_FEATURE_VALUES", OracleDbType.Varchar2);
featureValues.Direction = System.Data.ParameterDirection.Input;
featureValues.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
featureValues.Value = features.Select(_ => _.Value).ToArray();
featureValues.Size = features.Count();
featureValues.ArrayBindSize = features.Select(_ => _.Value.Length).ToArray();
featureValues.ArrayBindStatus = Enumerable.Repeat(OracleParameterStatus.Success, features.Count()).ToArray();
cloneProductCmd.Parameters.Add("P_F_AUDIT_USER", HttpContext.Current.User.Identity.Name);
cloneProductCmd.Parameters.Add("P_F_PRODUCT_ID", OracleDbType.Decimal, System.Data.ParameterDirection.Output);
var reader = await cloneProductCmd.ExecuteNonQueryAsync();
newProductId = Convert.ToInt32(cloneProductCmd.Parameters["P_F_PRODUCT_ID"].Value.ToString());
}

How to pass parameters by position to stored procedure using dapper?

using (SqlConnection connection = new SqlConnection(ConnectionString))
{
Train result = connection.Query<Train>("Trains.AwesomeSauce", new { TrainId = "TRN001", CurrentTrack = "TR001"}, commandType: CommandType.StoredProcedure).FirstOrDefault<Train>();
}
The above method works, but I want to pass the parameters without actually saying this = that. The method below does not work I get an error that says
Additional information: Procedure or function 'AwesomeSauce' expects parameter '#TrainId', which was not supplied
Code:
public void TestMethod5()
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
List<string> parameters = new List<string> { "TRN001", "TR001" };
var list = connection.Query<Train>("Trains.Awesomesauce", new { parameters}, commandType: CommandType.StoredProcedure).FirstOrDefault<Train>();
}
}
I also tried the code below, but got the same
Procedure or function 'AwesomeSauce' expects parameter '#TrainId', which was not supplied
error.
public void TestMethod5()
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
var dictionary = new object[] { "TRN001","TR001"}
.Select((item, ind) => new { ind = ind.ToString(), item })
.ToDictionary(item => item.ind, item => item.item);
DynamicParameters p = new DynamicParameters(dictionary);
Train train = connection.Query<Train>("Trains.Awesomesauce", param: p, commandType: CommandType.StoredProcedure).FirstOrDefault<Train>();
}
}

Create seed data from sql script

I'm using ORM in my project. Currently seed data is taken from sql scripts but I would like to create seed data basing on my c# code. For example, I have sql:
SET IDENTITY_INSERT [dbo].[State] ON
INSERT INTO [dbo].[State] ([Id], [Code], [Name]) VALUES (1, N'AL', N'Alabama')
INSERT INTO [dbo].[State] ([Id], [Code], [Name]) VALUES (2, N'AK', N'Alaska')
SET IDENTITY_INSERT [dbo].[State] OFF
And instead of it I want to have a string:
new List<State>
{
new State { Id = 1, Code = "AL", Name = "Alabama" },
new State { Id = 2, Code = "AK", Name = "Alaska" }
};
How can I achieve it?
For INSERT statements (as you said you need seed) you can create helper method like this:
public static List<State> ParseSqlScript(string sqlScriptPath)
{
using (var reader = new StreamReader(sqlScriptPath))
{
var sqlScript = reader.ReadToEnd();
var pattern = #"INSERT INTO \[dbo\].\[State\] \(\[Id\], \[Code\], \[Name\]\) VALUES (\(.*?\))";
var regex = new Regex(pattern);
var matches = regex.Matches(sqlScript);
var states = new List<State>();
foreach (Match match in matches)
{
var values = match.Groups[1].Value.Split(new [] { '(', ',',' ',')' }, StringSplitOptions.RemoveEmptyEntries);
var id = int.Parse(values[0]);
var code = values[1].Substring(2, values[1].Length - 3);
var name = values[2].Substring(2, values[2].Length - 3);
foreach (var value in values)
{
var state = new State() { Id = id, Code = code, Name = name };
states.Add(state);
}
}
return states;
}
}
If you also need other CRUD statements you will probably need to get acquainted with some SQL Parser, maybe the Microsoft.SqlServer.SMO.
Try to add the following code, I assume you are working with entity framework:
List<State> states = new List<State>()
{
new State { Id = 1, Code = "AL", Name = "Alabama" },
new State { Id = 2, Code = "AK", Name = "Alaska" }
};
StateDBEntities context = new StateDBEntities();
foreach (State state in states)
{
context.State.Add(state);
}
context.SaveChanges();
List<State> states = new List<State>();
using (SqlConnection connection = new SqlConnection("conn_string"))
{
string query = "SELECT Id, Code, Name FROM State";
using (SqlCommand command = new SqlCommand(query, connection))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
State state = new State { Id = (int)reader["Id"], Code = reader["Code"].ToString(), Name = reader["Name"].ToString() };
states.Add(state);
}
}
}
}
Call the select query(here I'm writing the query, but it should be avoided, you can use Stored Procedure). Get all columns using ExecuteReader and add all the rows to list one by one.

C# retrieve stored procedure results

Procedure (modified):
alter procedure searchProgramUnitResult(
#id char(10)
)
as
begin
select id from table1 where id = #id
end
Sam procedure in the DBML Designer (after importing the procedure to the MVC project):
[global::System.Data.Linq.Mapping.FunctionAttribute(Name="dbo.searchProgramUnit")]
public ISingleResult<searchProgramUnitResult> searchProgramUnit([global::System.Data.Linq.Mapping.ParameterAttribute(DbType="VarChar(10)")] ref string id){
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())),id);
id = ((string)(result.GetParameterValue(0)));
return ((ISingleResult<searchProgramUnitResult>)(result.ReturnValue));
}
Question is, how do I retrieve the result set in another C# class?
public ??Data-type search (string id){
DataContextClass db = new DataContextClass();
??Datatype results = db.searchProgramUnit(id);
return results;
}
If you have mapped the stored procedure in your DbContext you can call it like that:
using (var context = new DataContextClass())
{
var courses = context.searchProgramUnit("1");
foreach (table1 cs in table1s)
Console.WriteLine(cs.Name);
}
another approach which works also with Code First:
using (var ctx = new DataContextClass())
{
var idParam = new SqlParameter
{
ParameterName = "id",
Value = "1"
};
var table1List = ctx.Database.SqlQuery<table1>("exec searchProgramUnitResult #id ", idParam).ToList<table1>();
foreach (table cs in table1List)
Console.WriteLine("Name: {0}", cs.Name);
}
table1 is your entity/class name!
Are you asking about the data type?
public List<string> search (string id){
DataContextClass db = new DataContextClass();
List<string> results = db.searchProgramUnit(id).ToList();
return results;
}

Sum of fields using LINQ

Some background before asking my question.
Im using sql compact, and i have two tables,
The first table (IssueEmp)
The second table (RecEmp)
SqlCeDataAdapter adap = new SqlCeDataAdapter("SELECT * FROM RecEmp", cn);
DataTable dat = new DataTable();
DataSet receice = new DataSet();
adap.Fill(receice);
adap.Fill(dat);
SqlCeDataAdapter adap1 = new SqlCeDataAdapter("SELECT * FROM IssueEmp", cn);
DataTable dat1 = new DataTable();
DataSet issue = new DataSet();
adap1.Fill(issue);
adap1.Fill(dat1);
Im performing a join between RecEmp and IssueEmp using linq
var res = from t1 in receice.Tables[0].AsEnumerable()
join t2 in issue.Tables[0].AsEnumerable()
on new
{
CNo = t1.Field<int>("CNo"),
Empid = t1.Field<int>("EmpID")
}
equals new
{
CNo = t2.Field<int>("CNo"),
Empid = t2.Field<int>("EmpID")
}
select new
{
SNo = t1.Field<int>("SNo"),
ChNo = t1.Field<int>("CNo"),
EmpID = t1.Field<int>("EmpID"),
DateIssued = t2.Field<DateTime>("Date"),
RMIssued = t2.Field<string>("RMCode"),
QuantityIssued = t2.Field<double>("Quantity"),
DateReceived = t1.Field<DateTime>("Date"),
RMCodeReceived = t1.Field<string>("RMCode"),
QuantityReceived = t1.Field<double>("Quantity")
};
The output Im getting from the above linq query is
But I don't know how to get the sum of issued quantity likewise the sum of received quantity, lastly the difference between the two sum as the diff. The required is below.
Note:
I´m a bit lazy so I didn´t use all the records you provided, only the first four records.
Expected result:
This is what I got:
The Linq query:
var query = from d in data
group d by new { d.DateIssued, d.EmpId, d.ChNo, d.DateReceived }
into x
select new {
Date = x.Key.DateIssued,
CNo = x.Key.ChNo,
EmpId=x.Key.EmpId,
CRi = x.Where(c=>c.RMIssued == "CR").Sum(c=>c.QuantityIssued),
SJi = x.Where(c=>c.RMIssued == "SJ").Sum(c=>c.QuantityIssued),
TTi = x.Where(c=>c.RMIssued == "TT").Sum(c=>c.QuantityIssued),
WRi = x.Where(c=>c.RMIssued == "WR").Sum(c=>c.QuantityIssued),
TotalIssued = x.Sum(c => c.QuantityIssued),
DateReceived = x.Key.DateReceived,
CRr = x.Where(c=>c.RMCodeReceived == "CR").Sum(c=>c.QuantityReceived),
SJr = x.Where(c=>c.RMCodeReceived == "SJ").Sum(c=>c.QuantityReceived),
TTr = x.Where(c=>c.RMCodeReceived == "TT").Sum(c=>c.QuantityReceived),
WRr = x.Where(c=>c.RMCodeReceived == "WR").Sum(c=>c.QuantityReceived),
TotalReceived = x.Sum(c => c.QuantityReceived),
Diff = x.Sum(c => c.QuantityIssued) - x.Sum(c => c.QuantityReceived)
};
Data used:
And this is the set of data I used to test it:
var data= new []{
new { SNo= 9, ChNo=5, EmpId=81, DateIssued=dateIssued, RMIssued="SJ", QuantityIssued=30, DateReceived=dateReceived, RMCodeReceived="SJ", QuantityReceived=20.3},
new { SNo= 10, ChNo=5, EmpId=81, DateIssued=dateIssued, RMIssued="SJ", QuantityIssued=30, DateReceived=dateReceived, RMCodeReceived="CR", QuantityReceived=9.6},
new { SNo= 11, ChNo=28, EmpId=82, DateIssued=dateIssued, RMIssued="TT", QuantityIssued=30.5, DateReceived=dateReceived, RMCodeReceived="TT", QuantityReceived=29},
new { SNo= 12, ChNo=28, EmpId=82, DateIssued=dateIssued, RMIssued="WR", QuantityIssued=10, DateReceived=dateReceived, RMCodeReceived="TT", QuantityReceived=29}
};
I recommed you use LinqPad to test it.
Good luck!

Categories

Resources