How to clear parameters after use of `db.Database.SqlQuery<model>` - c#

In an MVC 5 web app using Entity Framework, I learned how to populate an Index view by using db.Database.SqlQuery<model> to execute a stored procedure and show the results in the Index View.
This is the relevant code in my Index View (and it works).
// supply parameter values required by the stored procedure
object[] parameters = {
new SqlParameter("#campus",SqlDbType.NVarChar,3) {Value=vm.SelectedCampus},
new SqlParameter("#date1",SqlDbType.DateTime) {Value=Convert.ToDateTime(vm.SelectedStartDate)},
new SqlParameter("#date2",SqlDbType.DateTime) {Value=Convert.ToDateTime(vm.SelectedEndDate)}
};
// populate the list by calling the stored procedure and supplying parameters
IEnumerable<PerfOdomoeterDate> query =
db.Database.SqlQuery<PerfOdomoeterDate>("PerfOdomoeterDate #campus, #date1, #date2",
parameters).OrderBy(m => m.StudentName).ToList();
And to put that code into better context, here is the entire Index ActionResult.
private PerformanceContext db = new PerformanceContext();
private static readonly string d1 = DateTime.Now.ToShortDateString();
private static readonly string d2 = DateTime.Now.ToShortDateString();
[HttpGet]
public ActionResult Index(int? page, string SelectedCampus = "CRA", string SelectedStartDate=null, string SelectedEndDate=null)
{
int PageNumber = (page ?? 1);
PerfOdomoeterDateViewModel vm = new PerfOdomoeterDateViewModel();
vm.SelectedCampus = SelectedCampus;
vm.SelectedStartDate = string.IsNullOrEmpty(SelectedStartDate) ? d1 : SelectedStartDate;
vm.SelectedEndDate = string.IsNullOrEmpty(SelectedEndDate) ? d2 :SelectedEndDate;
vm.CampusList = StaticClasses.ListBank.CampusList();
// supply parameter values required by the stored procedure
object[] parameters = {
new SqlParameter("#campus",SqlDbType.NVarChar,3) {Value=vm.SelectedCampus},
new SqlParameter("#date1",SqlDbType.DateTime) {Value=Convert.ToDateTime(vm.SelectedStartDate)},
new SqlParameter("#date2",SqlDbType.DateTime) {Value=Convert.ToDateTime(vm.SelectedEndDate)}
};
// populate the list by calling the stored procedure and supplying parameters
IEnumerable<PerfOdomoeterDate> query =
db.Database.SqlQuery<PerfOdomoeterDate>("PerfOdomoeterDate #campus, #date1, #date2",
parameters).OrderBy(m => m.StudentName).ToList();
vm.CreditTable = query.ToPagedList(PageNumber, 25);
return View(vm);
}
As I stated, this code works perfectly in the Index View. However, in a separate ActionResult, where the user has an option to export the data set to an Excel file, I use the same code, and I get this runtime error:
The SqlParameter is already contained by another SqlParameterCollection.
I was under the impression that each ActionResult is in its own scope, so how is it that I am getting this error when I am calling up a new query from a separate ActionResult?
Intellisense did not give me any clues as to how I could explicitly empty the parameters after executing the stored procedure.
This is the code in the ActionResult that is producing the error.
public ActionResult ExportToExcel(string SelectedCampus, string SelectedStartDate, string SelectedEndDate)
{
object[] parameters2 = {
new SqlParameter("#campus",SqlDbType.NVarChar,3) {Value=SelectedCampus},
new SqlParameter("#date1",SqlDbType.DateTime) {Value=Convert.ToDateTime(SelectedStartDate)},
new SqlParameter("#date2",SqlDbType.DateTime) {Value=Convert.ToDateTime(SelectedEndDate)}
};
IEnumerable<PerfOdomoeterDate> query =
db.Database.SqlQuery<PerfOdomoeterDate>("PerfOdomoeterDate #campus, #date1, #date2",
parameters2).OrderBy(m => m.StudentName).AsEnumerable();
...

The ADO.Net objects (like SqlParameter, SqlCommand etc.) presented to us by the .Net framework are a mere layer on top of the real stuff under the hood that is managed by the .Net connection pool. If we create a new SqlConnection —which is implicitly done by db.Database.SqlQuery— we don't really establish a new connection to the database. That would be far too expensive. In reality, our connection object "plugs" in to an available connection in the connection pool.
Normally, this mechanism is pretty transparent, but it is unveiled in issues like the one you see here. I remember having experienced similar issues (exceptions that persisted longer than met the eye).
The message is: you can't beat it, so join it. The quick solution seems to be renaming the parameters in one of the methods. A better solution, of course, is to factor out the repetitive part of your code into a method that contains the identical parts.

I would say, This is how as per the design.
You need to extract the data right from there .ToArray() or .ToList() etc...
Do not try to re execute the query for further data operations.

Related

Teradata Query Using Dapper C#

I am trying to dynamically query a Teradata database using Dapper but am having some issues. Here is the code:
// model variable is the parameter passed in with search information
using (IDbConnection con = new TdConnection(connection.GetConnectionString()))
{
var builder = new SqlBuilder();
var selector = builder.AddTemplate($"SELECT * FROM Temp_Table /**where**/");
if (model.Id != 0)
{
builder.Where("Id = ?", new { model.Id });
}
if (!string.IsNullOrEmpty(model.Employee_Id))
{
builder.Where("Employee_Id = ?", new { model.Employee_Id });
}
var data= con.Query<TableModel>(selector.RawSql, model).ToList();
return data;
}
The error I am getting is:
[Teradata Database] [3939] There is a mismatch between the number of
parameters specified and the number of parameters required.
I have used very similar code to query DB2 which worked just fine; what do I need to do differently with Teradata?
Managed to figure it out. Changed the line for getting the data to:
var data= con.Query<TableModel>(selector.RawSql, selector.Parameters).ToList();
Not sure why passing in the model worked just fine in my DB2 version but not this Teradata version.
At first glance it appears to be falling through and not adding any "where" condition. Try to structure it in such a way that if it falls through then add 1=1 or a Teradata equivalent if that doesn't work.
I'm unfamiliar with the SqlBuilder() class; but if you have a way of seeing if there aren't any Where constraints added, then to add a generic one. Or, a dirtier way would be to keep a bool reference and check at the end.
Update
Try passing in the parameters:
var data= con.Query<TableModel>(selector.RawSql, selector.Parameters).ToList();

Dapper QueryMultiple Read result as IDictionary

I am using Dapper with Oracle Managed and trying to use QueryMultiple method. I have a stored procedure in an Oracle package that returns multiple recordsets. I want to reuse my code for multiple package methods and the data coming back is coming from different source tables, so columns and actual data will vary.
To handle this I have a class that uses an IEnumerable<string> for storing FieldNames and an IEnumerable<object[]> for storing the data for each row.
With just a single recordset I am able to use the ExecuteReader method to iterate the results and add them myself. However, I would like to use QueryMultiple to get everything in a single call. Right now I have two recordsets coming back but others may be added.
After a few different attempts I was able to get the following code to work. However, it seems like there should be a better way to get this. Further below is a piece of code found in another SO question that seemed to be what I wanted but I just couldn't get it to work. Does anyone have any suggestions about how I'm getting the FieldNames and Data in the code below and if there is a better way using Dapper API. Loving Dapper by the way. Thanks for any suggestions.
// class to load results into
public class Results {
public IEnumerable<string> FieldNames { get; set; }
public DetailInfo Detail { get; set; }
public IEnumerable<object[]> Data { get; set; }
}
// code to get the results from the database field names and data varies
// method is private so that external callers cannot pass any just any string as methodName
private Results GetTableResults(string methodName) {
var queryparams = new OracleDynamicParameters();
queryparams.Add(name: "l_detail_cursor", dbType: OracleDbType.RefCursor, direction: ParameterDirection.Output);
queryparams.Add(name: "l_data_cursor", dbType: OracleDbType.RefCursor, direction: ParameterDirection.Output);
// ... other parameters go here, removed for example
Results results = new Results();
string sql = string.Format("{0}.GET_{1}_DETAILS", PackageName, methodName);
using (IDbConnection db = new OracleConnection(this.ConnectionString)) {
db.Open();
using (var multi = db.QueryMultiple(sql: sql, param: queryparams, commandType: CommandType.StoredProcedure)) {
// detail in first cursor, no problems here
results.Detail = multi.Read<DetailInfo>().Single();
// --------------------------------------------------
// this is the code I'm trying to see if there is a better way to handle
// --------------------------------------------------
// data in second cursor
var data = multi.Read().Select(dictionary =>
// cast to IDictionary
dictionary as IDictionary<string, object>
);
// pull from Keys
results.FieldNames = data.First().Select(d => d.Key);
// pull from values
results.Data = data.Select(d => d.Values.ToArray());
// --------------------------------------------------
}
}
return results;
}
I was attempting to try to use something like the following but only get an exception at run time about splitOn needing to be specified. I tried using something like ROWNUM to even give the recordset an "id" but that didn't seem to help.
var data = multi.Read<dynamic, dynamic, Tuple<dynamic, dynamic>>(
(a, b) => Tuple.Create((object)a, (object)b)).ToList();

Add stored proc and call it from a service

I create a simple stored procedure with some joins with the customer table and other related tables, which takes in two parameters. I can execute this SP in SQL and works.
I drag and drop this SP to my DBML file and recompile.
I add the below code in order to call the SP and return it in a List
public IQueryable<Entities.Customer> AllCustomerRanges(int CId, int ItemID)
{
List<Entities.Customer> c = myDataContext.spCustomerRanges(CId, ItemID).ToList();
}
This gives me the error:
Cannot implicitly convert type 'System.Collections.Generic.List< spCustomerRangesResult>' to 'System.Collections.Generic.List< Entities.Customer>'
Now i dont have a class spCustomerRangesResult but after some research I'm puzzled if i have done something wrong or if i need to implement a class with all the properties that the Customer class has (which sounds a little long winded) or if i've just made an error.
Any idea of how i can call a SP which shows the data in a List?
new class spCustomerRangesResult automatically generated based on sp result, you should convert it to Entities.Customer like this:
public IQueryable<Entities.Customer> AllCustomerRanges(int CId, int ItemID)
{
var c = myDataContext.spCustomerRanges(CId, ItemID).ToList();
if (c == null)
return null;
var customers = c.Select(a => new Entities.Customer
{
FirstName=a.spResultFirstName,
LastName = a.spResultLastName
//this just example conversion, change it as needed.
});
return customers;
}
please note, that I return IQueryable even though the approach that you take when using ToList() but yet returning IQuerybale may not be needed. I dont know all details so this only to show how to convert but the whole method may need re-factoring.

how to test out parameter from Oracle in C#

I have written a function that retrieve some data from database (using a stored procedure, Oracle database).
The stored procedure has 4 parameter and one of them is am out parameter. Here is my code for this:
string commandName = "someStoredProc";
List<IDataParameter> parameters = new List<IDataParameter>() { new SqlParameter("#param1", param1), new SqlParameter("#param2", param2), new SqlParameter("#param3", param3) };
SqlParameter calcId = new SqlParameter("#param4", SqlDbType.BigInt);
calcId.Direction = ParameterDirection.Output;
parameters.Add(calcId);
Dictionary<long, ITrade> trades = _genericDataReader.ExecuteSqlQuery(parameters, CommandTypeEnum.Command, commandName);
long CalculationID = (long)calcId.Value;
This code is working fine.
Now I am writing unit test cases for this. I am able to mock the database result whatever I want but not this fourth out Parameter:
test failed when it try to convert calcId.Value to long as it is null.
How can I mock this out parameter value?
What is actually necessary here is writting an implementation of ExecuteSqlQuery for testing purpose, which has an access to the arguments and can do the job.
Rhino Mock provides a few ways to add a custom behavior to mocks/stubs. One of possible ways is to write The Do() handler.
Here is a short example:
_genericDataReader
.Stub(gdr => gdr.ExecuteSqlQuery(Arg<List<IDataParameter>>.Is.Anything, Arg<CommandTypeEnum>.Is.Anything, Arg<string>.Is.Anything))
.Do((Func<List<IDataParameter>, CommandTypeEnum, string, IDictionary<long, ITrade>>)((param, cmd, cmdName) =>
{
param[3].Value = 12L;
return null; // or return whatever is required dataset here
}));

How to optimise ASP.NET MVC controller to minimise the number of database query

My controller has different methods that use the same result returned by a stored procedure called by a LINQ query,
Is there a way to create a global variable that contains the result after making only one call to the procedure ??
I tried creating a constructor but every time the variable (ListePays) is used a new query is executed
public class BUController : Controller {
private NAV_MAUIEntities db = new NAV_MAUIEntities();
public DbSet<liste_pays> ListePays;
public BUController() {
ListePays = db.liste_pays();
}
public JsonResult BillPh(string Pays) {
var x = from pays in ListePays
where pays.Pays.ToUpper() == Pays.ToUpper()
select pays.code_pays;
string CodePays = x.FirstOrDefault().ToString();
}
public JsonResult BillPh2(string Pays) {
var x = from pays in ListePays
where pays.Pays.ToUpper() == Pays.ToUpper()
select pays.code_pays;
string CodePays = x.FirstOrDefault().ToString();
}
}
If the methods are all called as part the processing of the same HTTP request, just make sure some central actor (say, the action method) calls the procedure and passes the result to each method.
public ActionResult MyAction()
{
var data = db.liste_pays();
Method1(data);
Method2(data);
return View();
}
On the other hand, if you want to share the result across HTTP requests, you can cache the result of the procedure call in a number of locations (this is not an exhaustive list):
In the ASP.NET session
In a static variable
In System.Web.Caching.Cache
Not knowing more about your specific scenario, I can't recommend one over the other. However, be aware that the two latter options will potentially share the data between users, which may or may not be what you want.
NOTE: As your code stands at the moment, the call to db.liste_pays() is inside the constructor of BUController, so it is called every time a controller is created, that is, every time a new incoming HTTP request arrives. In other words, your assumption that it is being called every time the variable is used is not entirely correct.
First of all, there is no need for a constructor into an ASP.NET MVC controller.
What you did, means that every time a user makes a call to this controller, a call will be made to the DB to retrieve all your "pays".
I assume you are using Entity Framework. And the way you use Linq is "LinqToEntities". Do not worry about your DB calls, those are managed by the entity framework.
So, now, you just have to use linq that way :
public JsonResult BillPh(string Pays)
{
string codePays = db.liste_pays.FirstOrDefault(f =>
f.Pays.ToUpper() == Pays.ToUpper())
.CodePays.ToString();
}
And the syntax for the query is called "Lambda expressions".
Good luck ;-)

Categories

Resources