I am executing a stored procedure using QueryMultiple to return multiple sets of data.
var gridReader = db.QueryMultiple("sp",
parameters,
commandType: CommandType.StoredProcedure);
I can very easily get each set given I know the order they will come back in.
SELECT * FROM dbo.Set1;
SELECT * FROM dbo.Set2;
SELECT * FROM dbo.Set3;
var set1 = gridReader.Read<Set1>();
var set2 = gridReader.Read<Set2>();
var set3 = gridReader.Read<Set3>();
However, I am in a situation where the order they will come back in may change. Another developer could decide to change the order for whatever reason. The stored procedure now becomes this:
SELECT * FROM dbo.Set1;
SELECT * FROM dbo.Set3;
SELECT * FROM dbo.Set2;
How can I handle this?
My initial attempt was to iterate each grid, checking the column names. This seemed to work well at first, but I wasn't able to figure out how to then project the grid into a class, besides manually setting each field. The main reason I'm using Dapper is so it can do this for me.
while (true)
{
var grid = gridReader.Read();
IDictionary<string, object> row = grid.FirstOrDefault();
if (row == null)
break;
if (row.Keys.Contains("Set1_UniqueColumnName"))
{
// Need something like grid.Read<Set1>();
}
else if (row.Keys.Contains("Set2_UniqueColumnName")) { }
else if (row.Keys.Contains("Set3_UniqueColumnName")) { }
}
My second idea was to read each grid into a class, check the unique fields of the class for nulls/default values, and trying the next class if the test failed. This obviously won't work though. .Read() will return the next grid of results. This solution would require me to be able to read the same grid over and over.
Dapper provides an IDataReader.GetRowParser extension method that enables type switching per row. From the Dapper docs here...
Usually you'll want to treat all rows from a given table as the same
data type. However, there are some circumstances where it's useful to
be able to parse different rows as different data types. This is where
IDataReader.GetRowParser comes in handy.
Imagine you have a database table named "Shapes" with the columns: Id,
Type, and Data, and you want to parse its rows into Circle, Square, or
Triangle objects based on the value of the Type column.
var shapes = new List<IShape>();
using (var reader = connection.ExecuteReader("select * from Shapes"))
{
// Generate a row parser for each type you expect.
// The generic type <IShape> is what the parser will return.
// The argument (typeof(*)) is the concrete type to parse.
var circleParser = reader.GetRowParser<IShape>(typeof(Circle));
var squareParser = reader.GetRowParser<IShape>(typeof(Square));
var triangleParser = reader.GetRowParser<IShape>(typeof(Triangle));
var typeColumnIndex = reader.GetOrdinal("Type");
while (reader.Read())
{
IShape shape;
var type = (ShapeType)reader.GetInt32(typeColumnIndex);
switch (type)
{
case ShapeType.Circle:
shape = circleParser(reader);
break;
case ShapeType.Square:
shape = squareParser(reader);
break;
case ShapeType.Triangle:
shape = triangleParser(reader);
break;
default:
throw new NotImplementedException();
}
shapes.Add(shape);
}
}
You'll need to get access to the IDataReader that the GridReader wraps or change your code to use the good old-fashioned ADO.NET SqlConnection & SqlCommand objects like this...
using (command = new SqlCommand("sp", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(parameters);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
// read row columns
}
}
}
Davmos's answer pointed me in the right direction. Needed to use a combination of ADO.NET and Dapper. Essentially use ADO.NET to retrieve and iterate through the data, but use Dapper to parse the rows into my objects. Note the use of FieldCount in the while loop in case a result set actually does return 0 rows. We want it to move on to the next result set, not break out of the loop.
Set1 set1 = null;
var set2 = new List<Set2>();
Set3 set3 = null;
using (var command = new SqlCommand("sp", conn))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(parameters);
command.Connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.FieldCount > 0)
{
var set1Parser = reader.GetRowParser<Set1>();
var set2Parser = reader.GetRowParser<Set2>();
var set3Parser = reader.GetRowParser<Set3>();
var isSet1 = HasColumn(reader, "Set1_UniqueColumnName");
var isSet2 = HasColumn(reader, "Set2_UniqueColumnName");
var isSet3 = HasColumn(reader, "Set3_UniqueColumnName");
while (reader.Read())
{
if (isSet1)
{
set1 = set1Parser(reader);
}
else if (isSet2)
{
set2.Add(set2Parser(reader));
}
else if (isSet3)
{
set3 = set3Parser(reader);
}
}
reader.NextResult();
}
}
}
public static bool HasColumn(IDataReader reader, string columnName)
{
for (var i = 0; i < reader.FieldCount; i++)
{
if (reader.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
}
return false;
}
Related
I attempted to passing value from object params array to each parameter of stored procedure using loop, but I get this error
'Procedure or function Proc_GetPaging_Product has too many arguments specified.'.
The problem is cmd.Parameters already has parameters when using SqlCommandBuilder.DeriveParameters(cmd) so no need to add value but assigning.
public ServiceResult<T> GetPaging<T>(params object[] values)
{
var result = new ServiceResult<T>();
using(_conn)
{
_conn.Open();
using (SqlCommand cmd = _conn.CreateCommand())
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[SalesLT].[Proc_GetPaging_Product]";
SqlCommandBuilder.DeriveParameters(cmd);
int lengthParams = cmd.Parameters.Count;
for (int i = 1; i < lengthParams; i++)
{
cmd.Parameters.AddWithValue(cmd.Parameters[i].ParameterName, values[i - 1]);
}
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var item = Activator.CreateInstance<T>();
var properties = typeof(T).GetProperties();
foreach (var property in properties)
{
//Type convertTo = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
property.SetValue(item, reader[property.Name], null);
}
result.ListResult.Add(item);
}
reader.NextResult();
}
}
}
return result;
}
Error I got in debugger
You're adding the parameters twice.
Once in this row:
SqlCommandBuilder.DeriveParameters(cmd);
and once for each parameter in the loop:
for (int i = 1; i < lengthParams; i++)
{
cmd.Parameters.AddWithValue(cmd.Parameters[i].ParameterName, values[i - 1]);
}
Instead of using AddWithValue, simply do cmd.Parameter[i].Value = values[i].
Also, the parameters collection, like any other built-in collection in .Net, is zero based and not one based.
You loop should look like this:
for (int i = 0; i < lengthParams; i++)
{
cmd.Parameters[i].Value = values[i];
}
Also, I you should add a condition before the loop to make sure the number of values you have match the number of parameters the stored procedure have.
And one more note: you should consider setting the parameters in the code instead of using DeriveParameters: Official documentation clearly states:
DeriveParameters requires an additional call to the database to obtain the information. If the parameter information is known in advance, it is more efficient to populate the parameters collection by setting the information explicitly.
my problem is very common, but I have not found any solution.
This is my code:
public async Task<QueryResult> RollbackQuery(ActionLog action)
{
var inputParameters = JsonConvert.DeserializeObject<Parameter[]>(action.Values);
var data = DeserailizeByteArrayToDataSet(action.RollBackData);
using (var structure = PrepareStructure(action.Query, action.Query.DataBase, inputParameters))
{
//_queryPlanner is the implementor for my interface
return await _queryPlanner.RollbackQuery(structure, data);
}
}
I need to load DataTable (from whereever) and replace data to database. This is my Rollback function. This function use a "CommandStructure" where I've incapsulated all SqlClient objects. PrepareStructure initialize all objects
//_dataLayer is an Helper for create System.Data.SqlClient objects
//ex: _dataLayer.CreateCommand(preSelect) => new SqlCommand(preSelect)
private CommandStructure PrepareStructure(string sql, string preSelect, DataBase db, IEnumerable<Parameter> inputParameters)
{
var parameters = inputParameters as IList<Parameter> ?? inputParameters.ToList();
var structure = new CommandStructure(_logger);
structure.Connection = _dataLayer.ConnectToDatabase(db);
structure.SqlCommand = _dataLayer.CreateCommand(sql);
structure.PreSelectCommand = _dataLayer.CreateCommand(preSelect);
structure.QueryParameters = _dataLayer.CreateParemeters(parameters);
structure.WhereParameters = _dataLayer.CreateParemeters(parameters.Where(p => p.IsWhereClause.HasValue && p.IsWhereClause.Value));
structure.CommandBuilder = _dataLayer.CreateCommandBuilder();
structure.DataAdapter = new SqlDataAdapter();
return structure;
}
So, my function uses SqlCommandBuilder and DataAdapter to operate on Database.
PreSelectCommand is like "Select * from Purchase where CustomerId = #id"
The table Purchase has one primaryKey on ID filed
public virtual async Task<QueryResult> RollbackQuery(CommandStructure cmd, DataTable oldData)
{
await cmd.OpenConnectionAsync();
int record = 0;
using (var cmdPre = cmd.PreSelectCommand as SqlCommand)
using (var dataAdapt = new SqlDataAdapter(cmdPre))
using (var cmdBuilder = new SqlCommandBuilder(dataAdapt))
{
dataAdapt.UpdateCommand = cmdBuilder.GetUpdateCommand();
dataAdapt.DeleteCommand = cmdBuilder.GetDeleteCommand();
dataAdapt.InsertCommand = cmdBuilder.GetInsertCommand();
using (var tbl = new DataTable(oldData.TableName))
{
dataAdapt.Fill(tbl);
dataAdapt.FillSchema(tbl, SchemaType.Source);
tbl.Merge(oldData);
foreach (DataRow row in tbl.Rows)
{
row.SetModified();
}
record = dataAdapt.Update(tbl);
}
}
return new QueryResult
{
RecordAffected = record
};
}
I Execute the code and I don't have any errors, but the data are not updated.
variable "record" contain the right number of modified (??) record, but..... on the table nothing
can someone help me?
EDIT 1:
With SQL Profiler I saw that no query is executed on DB. Only select query on .Fill(tbl) command.
EDIT 2:
Now I have made one change:
tbl.Merge(oldData) => tbl.Merge(oldData, true)
so I see perform the expected query but, with reversed parameters.
UPDATE Purchase SET price=123 where id=6 and price=22
instead of
UPDATE Purchase SET price=22 where id=6 and price=123
I have a View that typically gets query results from a WebMatrix Query (IEnumerable<dynamic> data type), and displays the results in a table:
#model MySite.Models.Entity
#foreach(var row in Model.Data)
{
<tr>
#foreach (var column in row.Columns)
{
<td>#column<span>:</span> #row[column]</td>
}
</tr>
}
Here's my model where I query the database:
public class Entity
{
public dynamic Data {get; set; }
public Entity(String table)
{
if (table == "User" || table == "Group)
{
WebMatrix.Data.Database db = new WebMatrix.Data.Database();
db.Open(ConString);
Data = db.Query("SELECT * FROM " + table);
}
else
{
using (OdbcConnection con = ne4w OdbcConnection(ConString))
{
OdbcCommand com = new OdbcCommand("Select * From " + table);
command.CommandType = System.Data.CommandType.Text;
connection.Open();
OdbcDataReader reader = command.ExecuteReader();
Here's all the different things I've tried from reading various other posts:
// Atempt 1
Data = reader;
// Error in view, 'Invalid attempt to call FieldCount when reader is closed' (on 'var row `in` Model.Data')
// Atempt 2
Data = reader.Cast<dynamic>;
// Error: 'Cannot convert method group "Cast" to non-delegate type "dynamic". Did you intend to invoke the method?
// Atempt 3
Data = reader.Cast<IEnumerable<dynamic>>;
// Error same as Atempt 2
// Atempt 4
Data = reader.Cast<IEnumerable<string>>;
// Error same as Atempt 2
}
}
}
}
I'm looking for the best way to get the reader object to a IEnumerable<dynamic> object.
Please note this is a simplified example, and while the reason for the two query types is not obvious, they are necessary in my code.
You're missing basic C# syntax.
Data = reader;
// You cant do this. You have to loop the reader to get the values from it.
// If you simply assign reader object itself as the data you wont be
// able to get data once the reader or connection is closed.
// The reader is typically closed in the method.
Data = reader.Cast<dynamic>;
// You should call the Cast method. And preferably execute the resulting query.
// As of now you're merely assigning method reference to a variable
// which is not what you want.
// Also bear in mind that, as I said before there's no real benefit in casting to dynamic
Data = reader.Cast<IEnumerable<dynamic>>;
// Cast method itself returns an IEnumerable.
// You dont have to cast individual rows to IEnumerable
Data = reader.Cast<IEnumerable<string>>;
// Meaningless I believe.
// The data you get from database is not always strings
The major mistake you make is not calling the method. This is what you want:
Data = reader.Cast<IDataRecord>().ToList();
^^ // notice the opening and closing parentheses
You could go about this a number of ways depending on what is easier to process (say, to display in front-end).
Return data records.
public IEnumerable<IDataRecord> SelectDataRecord()
{
....
using (var reader = cmd.ExecuteReader())
foreach (IDataRecord record in reader as IEnumerable)
yield return record; //yield return to keep the reader open
}
Return ExpandoObjects. Perhaps this is what you wanted?
public IEnumerable<dynamic> SelectDynamic()
{
....
using (var reader = cmd.ExecuteReader())
{
var names = Enumerable.Range(0, reader.FieldCount).Select(reader.GetName).ToList();
foreach (IDataRecord record in reader as IEnumerable)
{
var expando = new ExpandoObject() as IDictionary<string, object>;
foreach (var name in names)
expando[name] = record[name];
yield return expando;
}
}
}
Return sequence of property bag
public IEnumerable<Dictionary<string, object>> SelectDictionary()
{
....
using (var reader = cmd.ExecuteReader())
{
var names = Enumerable.Range(0, reader.FieldCount).Select(reader.GetName).ToList();
foreach (IDataRecord record in reader as IEnumerable)
yield return names.ToDictionary(n => n, n => record[n]);
}
}
Return sequence of plain object array
public IEnumerable<List<object>> SelectObjectArray()
{
....
using (var reader = cmd.ExecuteReader())
{
var indices = Enumerable.Range(0, reader.FieldCount).ToList();
foreach (IDataRecord record in reader as IEnumerable)
yield return indices.Select(i => record[i]).ToList();
}
}
Return data rows
public IEnumerable<DataRow> SelectDataRow()
{
....
using (var reader = cmd.ExecuteReader())
{
var table = new DataTable();
table.BeginLoadData();
table.Load(reader);
table.EndLoadData();
return table.AsEnumerable(); // in assembly: System.Data.DataSetExtensions
}
}
Last but not least, if it helps, you can return a strongly-typed sequence without any manual plumbing. You can use expression trees to compile code at run-time. See this for e.g.
Try looping the reader results:
OdbcDataReader reader = command.ExecuteReader();
while(reader.Read())
{
var item = reader["yourField"].ToString();
}
It looks like you are just trying to set an object to the unexecuted IQueriable result of command.ExecuteReader();
i have tried to search some examples about my approach but all questions was not close enough to what i was trying to achieve .
for the TLDR sake , Question is : how do i make it work as in plain sql query?
using c# - Winforms with SqlCompact4 and Linq to SQL
my scenario involves a form with all the relevant Db table columns as availble filters to query
and then on text change event of each filtertextbox as a filter, the datasource of the gridview updates accordingly
and because i allow filtered search via many of them columns i was trying to avoid use of some extra
lines of code.
so lets say we only concentrate on 4 columns
custID, name, email, cellPhone
each has its corresponding TextBox.
i am trying to make a query as follows :
first i systematically collect all Textbox into a List
var AllFormsSearchFiltersTBXLst = new List<TextBox>();
code that collects all tbx on current form
var AllFormsSearchFiltersTBXLst = [currentFormHere].Controls.OfType<TextBox>();
so now i have all of textboxes as filters regardless if they have any value
then check who has some value in it
forech textbox in this filters textboxes if text length is greater than zero
it means that current filter is active
then.. a second list AllFormsACTIVESearchfiltersTBXLst will contain only active filters
what i was trying to achieve was in same way i didn't have to specify each of textbox objects
i just looped through each of them all as a collection and didn't have to specify each via it's id
now i want to make a filter on a dbContext using only those active filters
so i will not have to ask if current tbxName is email
like
query = db.Where(db=>db.email.Contains(TbxEmail.Text));
and again and again for each of 10 to 15 columns
what i have got so far is nothing that implements what i was heading to.
using (SqlCeConnection ClientsConn = new SqlCeConnection(ConfigurationManager.ConnectionStrings["Conn_DB_RCL_CRM2014"].ConnectionString))
{
System.Data.Linq.Table<ContactsClients> db = null;
// get all column names from context
var x =(System.Reflection.MemberInfo[]) typeof(ContactsClients).GetProperties();
using (DB_RCL_CRM2014Context Context = new DB_RCL_CRM2014Context(ClientsConn))
{
if (!Filtered)
db = Context.ContactsClients;//.Where(client => client.Name.Contains("fler"));
else
{
db = Context.ContactsClients;
// filters Dictionary contains the name of textBox and its value
// I've named TBX as Columns names specially so i could equalize it to the columns names when needed to automate
foreach (KeyValuePair<string,string> CurFltrKVP in FiltersDict)
{
foreach (var memberInfo in x)
{
// couldn't find out how to build the query
}
}
}
BindingSource BS_Clients = new BindingSource();
BS_Clients.DataSource = db;
GV_ClientInfo_Search.DataSource = BS_Clients;
what i normally do when working with plain sql is
foreach textbox take its value and add it into a string as filter
var q = "where " ;
foreach(tbx CurTBX in ALLFILTERTBX)
{
q +=CurTBX.Name +" LIKE '%" + CurTBX.Text + "%'";
// and some checking of last element in list off cores
}
then pass this string as a filter to the main select query ... that simple
how do i make it work as in plain sql query?
I think that you're trying to get the property of db dynamically, like: db.email according to the looped name of your textbox (here 'email'). However, I recommend you to do it some other way. I'd make a switch for each type of the property, like: email, name etc. Something like this:
// Create a list for the results
var results = new List<YourDBResultTypeHere>();
foreach(tbx CurTBX in ALLFILTERTBX)
{
switch(CurTBX.Name) {
case "email":
results.AddRange(db.Where(db => db.email.Contains(tbx.Text)).ToList());
break;
case "name":
results.AddRange(db.Where(db => db.name.Contains(tbx.Text)).ToList());
break;
}
}
try this
void UpdateGridViewData(bool Filtered=false, Dictionary<string,string> FiltersDict = null)
{
using (SqlCeConnection ClientsConn = new SqlCeConnection(ConfigurationManager.ConnectionStrings["Conn_DB_RCL_CRM2014"].ConnectionString))
{
System.Data.Linq.Table<ContactsClients> db = null;
IEnumerable<ContactsClients> IDB = null;
BindingSource BS_Clients = new BindingSource();
System.Reflection.MemberInfo[] AllDbTblClientsColumns = (System.Reflection.MemberInfo[])typeof(ContactsClients).GetProperties();
using (DB_RCL_CRM2014Context Context = new DB_RCL_CRM2014Context(ClientsConn))
{
if (!Filtered)
{
db = Context.ContactsClients;
BS_Clients.DataSource = db;
}
else
{
string fltr = "";
var and = "";
if (FiltersDict.Count > 1) and = "AND";
for (int i = 0; i < FiltersDict.Count; i++)
{
KeyValuePair<string, string> CurFltrKVP = FiltersDict.ElementAt(i);
if (i >= FiltersDict.Count-1) and = "";
for (int j = 0; j < AllDbTblClientsColumns.Length; j++)
{
if (AllDbTblClientsColumns[j].Name.Equals(CurFltrKVP.Key))
{
fltr += string.Format("{0} Like '%{1}%' {2} ", AllDbTblClientsColumns[j].Name, CurFltrKVP.Value, and);
}
}
}
try
{
IDB = Context.ExecuteQuery<ContactsClients>(
"SELECT * " +
"FROM ContactsCosmeticsClients " +
"WHERE " + fltr
);
BS_Clients.DataSource = IDB;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
GV_ClientInfo_Search.DataSource = BS_Clients;
}
}
}
I need to determine the structure of a result set returned by ExecuteReader. I am using the following approach:
public List<NameAndType> ResultSetStructure(DataTable columns)
{
var ret = new List<NameAndType>();
foreach (DataRow column in columns.Rows)
{
ret.Add(new NameAndType { Name = column[NameIndex].ToString(),
Type = column[TypeIndex].ToString()
});
}
return ret;
}
(snip)
using (SqlDataReader dr = command.ExecuteReader())
{
var rawColumns = dr.GetSchemaTable();
var columns = ResultSetStructure(rawColumns);
This gives me column names and types, but I would also like to know if the column is nullable, so that I know which of the following options to choose:
decimal density = dr.GetDecimal(0);
decimal? density = dr.IsDBNull(0) ? (decimal?)null : dr.GetDecimal(0);
Can I accomplish that? TIA.
Edit: I just found what I need:
column[13].ToString()
I guess there is no such way to know whether a column is nullable or not. You can try writing an extension method something like below:
public static decimal GetDecimal(this SqlDataReader reader, int columnIndex)
{
if(!reader.IsDBNull(columnIndex))
{
return reader.GetDecimal(colIndex);
}
else
{
return 0;
}
}
Hope this would be some help!!
The following code gets the job done:
ret.Add(new NameAndType { Name = column[NameIndex].ToString(),
Type = column[TypeIndex].ToString(),
IsNullable = column[13].ToString().Equals("True")