Been using Dapper lately, so of course there is lots of code like:
var id = 123;
var s = "hola";
conn.Execute("update foo set bar = #a where id = #b", new { a = s, b = id })
This was also my first time with C# 6.0, so I've noticed similarity between the above and string interpolation:
$"update foo set bar = {s} where id = {id}"
However. this just works on plain strings, without any knowledge of parameters, escaping and so on. Would it be somehow possible to reflect on the intermediate structure generated by the compiler and then use it to properly set up the parameters? So, instead of resulting string, one could obtain something something that contains the string with holes, and the array of objects. Feels to me one could do quite a lot of things with such data.
Try something like this:
public static class DbExtensions
{
public static IDbCommand CreateCommand(this IDbConnection connection, FormattableString commandText)
{
var command = connection.CreateCommand();
command.CommandType = CommandType.Text;
if (commandText.ArgumentCount > 0)
{
var commandTextArguments = new string[commandText.ArgumentCount];
for (var i = 0; i < commandText.ArgumentCount; i++)
{
commandTextArguments[i] = "#p" + i.ToString();
command.AddParameter(commandTextArguments[i], commandText.GetArgument(i));
}
command.CommandText = string.Format(CultureInfo.InvariantCulture, commandText.Format, commandTextArguments);
}
else
{
command.CommandText = commandText.Format;
}
return command;
}
}
Related
I have two blocks; in each block, I am retrieving the data from a table using command and reader and transforming the data, and then updating the same. What I am looking to extract the common function by passing the table name, type, and transform function to that function from these two blocks
Below is the code for the same,
// Block #1
using var queryProjectSteamSystemsCommand = dbContext.Database.GetDbConnection().CreateCommand();
queryProjectSteamSystemsCommand.CommandText = #"SELECT ""Id""::varchar, ""InitialObject""::varchar from ""DesignHubProjectSteamSystems""";
using var steamSystemProjectsReader = queryProjectSteamSystemsCommand.ExecuteReader();
if (steamSystemProjectsReader.HasRows)
{
while (steamSystemProjectsReader.Read())
{
var id = steamSystemProjectsReader.IsDBNull(0) ? Guid.Empty : Guid.Parse(steamSystemProjectsReader.GetString(0));
var steamSystemJson = steamSystemProjectsReader.IsDBNull(1) ? "null" : steamSystemProjectsReader.GetString(1);
var projSteamSystemInitialObj = JsonConvert.DeserializeObject<OldSteamSystem>(steamSystemJson);
string json = JsonConvert.SerializeObject(TransformProjectSteamSystem(projSteamSystemInitialObj)).Replace("'", "''", StringComparison.Ordinal);
migrationBuilder.Sql($"UPDATE \"DesignHubProjectSteamSystems\" SET \"InitialObject\" = '{json}'::jsonb WHERE \"Id\" = '{id}'");
}
}
steamSystemProjectsReader.Close();
// Block #2
using var queryProjectFuelSystemsCommand = dbContext.Database.GetDbConnection().CreateCommand();
queryProjectFuelSystemsCommand.CommandText = #"SELECT ""Id""::varchar, ""InitialObject""::varchar from ""DesignHubProjectFuelSystems""";
using var fuelSystemProjectsReader = queryProjectFuelSystemsCommand.ExecuteReader();
if (fuelSystemProjectsReader.HasRows)
{
while (fuelSystemProjectsReader.Read())
{
var id = fuelSystemProjectsReader.IsDBNull(0) ? Guid.Empty : Guid.Parse(fuelSystemProjectsReader.GetString(0));
var fuelSystemJson = fuelSystemProjectsReader.IsDBNull(1) ? "null" : fuelSystemProjectsReader.GetString(1);
var projFuelSystemInitialObj = JsonConvert.DeserializeObject<OldFuelSystem>(fuelSystemJson);
string json = JsonConvert.SerializeObject(TransformProjectFuelSystem(projFuelSystemInitialObj)).Replace("'", "''", StringComparison.Ordinal);
migrationBuilder.Sql($"UPDATE \"DesignHubProjectFuelSystems\" SET \"InitialObject\" = '{json}'::jsonb WHERE \"Id\" = '{id}'");
}
}
fuelSystemProjectsReader.Close();
I cannot combine OldSteamSystem and OldFuelSystem two classes, which are different. I can't make any familiar interface and abstract class out of these two.
So, could anyone please let me know how to make a common function out of it?
Many thanks in advance!!!
Use generics, and ask for the table name and a method to transform the object.
void DoTheThing<TSystem>(string tableName, Func<TSystem, TSystem> transform)
{
using var command = dbContext.Database.GetDbConnection().CreateCommand();
command.CommandText = #$"SELECT ""Id""::varchar, ""InitialObject""::varchar from ""{tableName}""";
// I know I complain about SQL injection, you get to fix this one...
using var reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
var id = reader.IsDBNull(0) ? Guid.Empty : Guid.Parse(reader.GetString(0));
var initialJson = reader.IsDBNull(1) ? "null" : reader.GetString(1);
var initialObj = JsonConvert.DeserializeObject<TSystem>(initialJson);
// this uses the "transform" parameter, IE a method your caller provides
string transformedJson = JsonConvert.SerializeObject(transform(initialObj)).Replace("'", "''", StringComparison.Ordinal);
migrationBuilder.Sql($"UPDATE \"{tableName}\" SET \"InitialObject\" = '{transformedJson}'::jsonb WHERE \"Id\" = '{id}'");
// again, SQL injection
}
}
reader.Close();
}
If the transform method takes one type and returns a different type, then change the method signature to
void DoTheThing<TSystem, TTransformed>(string tableName, Func<TSystem, TTransformed> transform)
When using the C# code below to construct a DB2 SQL query the result set only has one row. If I manually construct the "IN" predicate inside the cmdTxt string using string.Join(",", ids) then all of the expected rows are returned. How can I return all of the expected rows using the db2Parameter object instead of building the query as a long string to be sent to the server?
public object[] GetResults(int[] ids)
{
var cmdTxt = "SELECT DISTINCT ID,COL2,COL3 FROM TABLE WHERE ID IN ( #ids ) ";
var db2Command = _DB2Connection.CreateCommand();
db2Command.CommandText = cmdTxt;
var db2Parameter = db2Command.CreateParameter();
db2Parameter.ArrayLength = ids.Length;
db2Parameter.DB2Type = DB2Type.DynArray;
db2Parameter.ParameterName = "#ids";
db2Parameter.Value = ids;
db2Command.Parameters.Add(db2Parameter);
var results = ExecuteQuery(db2Command);
return results.ToArray();
}
private object[] ExecuteQuery(DB2Command db2Command)
{
_DB2Connection.Open();
var resultList = new ArrayList();
var results = db2Command.ExecuteReader();
while (results.Read())
{
var values = new object[results.FieldCount];
results.GetValues(values);
resultList.Add(values);
}
results.Close();
_DB2Connection.Close();
return resultList.ToArray();
}
You cannot send in an array as a parameter. You would have to do something to build out a list of parameters, one for each of your values.
e.g.: SELECT DISTINCT ID,COL2,COL3 FROM TABLE WHERE ID IN ( #id1, #id2, ... #idN )
And then add the values to your parameter collection:
cmd.Parameters.Add("#id1", DB2Type.Integer).Value = your_val;
Additionally, there are a few things I would do to improve your code:
Use using statements around your DB2 objects. This will automatically dispose of the objects correctly when they go out of scope. If you don't do this, eventually you will run into errors. This should be done on DB2Connection, DB2Command, DB2Transaction, and DB2Reader objects especially.
I would recommend that you wrap queries in a transaction object, even for selects. With DB2 (and my experience is with z/OS mainframe, here... it might be different for AS/400), it writes one "accounting" record (basically the work that DB2 did) for each transaction. If you don't have an explicit transaction, DB2 will create one for you, and automatically commit after every statement, which adds up to a lot of backend records that could be combined.
My personal opinion would also be to create a .NET class to hold the data that you are getting back from the database. That would make it easier to work with using IntelliSense, among other things (because you would be able to auto-complete the property name, and .NET would know the type of the object). Right now, with the array of objects, if your column order or data type changes, it may be difficult to find/debug those usages throughout your code.
I've included a version of your code that I re-wrote that has some of these changes in it:
public List<ReturnClass> GetResults(int[] ids)
{
using (var conn = new DB2Connection())
{
conn.Open();
using (var trans = conn.BeginTransaction(IsolationLevel.ReadCommitted))
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = trans;
var parms = new List<string>();
var idCount = 0;
foreach (var id in ids)
{
var parm = "#id" + idCount++;
parms.Add(parm);
cmd.Parameters.Add(parm, DB2Type.Integer).Value = id;
}
cmd.CommandText = "SELECT DISTINCT ID,COL2,COL3 FROM TABLE WHERE ID IN ( " + string.Join(",", parms) + " ) ";
var resultList = new List<ReturnClass>();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var values = new ReturnClass();
values.Id = (int)reader["ID"];
values.Col1 = reader["COL1"].ToString();
values.Col2 = reader["COL2"].ToString();
resultList.Add(values);
}
}
return resultList;
}
}
}
public class ReturnClass
{
public int Id;
public string Col1;
public string Col2;
}
Try changing from:
db2Parameter.DB2Type = DB2Type.DynArray;
to:
db2Parameter.DB2Type = DB2Type.Integer;
This is based on the example given here
I have a web-site connected to a SQL Server database, and I want to add a simple SQL query to it (for administrators). I was hoping to use the DataContext, and run a query, then return the results as a simple list. Is there any way to do this?
Using
string full_query = "SELECT " + query;
IEnumerable<string> results = DB.DB().ExecuteQuery<string>(full_query);
Doesn't work, throwing errors where ints come through. Changing the template parameter to "object" doesn't help much either.
So I need to run a select statement, and return the results as a list on a page.
Any ideas?
Normally you would want to use:
var results = DB.DB().SqlQuery(full_query);
If you want insert/update/delete, you can use:
DB.DB().ExecuteSqlCommand(full_query);
Hope it helps.
After a bit of messing around, I found something that works. I am using a class called DatabaseResults to hold the results:
public class DatabaseResults
{
public List<string> ColumnNames { get; set; }
public List<List<string>> Rows { get; set; }
public DatabaseResults()
{
ColumnNames = new List<string>();
Rows = new List<List<string>>();
}
}
The method then goes and runs the query, grabbing the headings and putting them in the results objects. It then reads the rows, taking the strings of the column values. "query" is the string passed in. It is the "select" query, with the select bit missing.
DatabaseResults results = new DatabaseResults();
string full_query = "SELECT " + query;
DbConnection connection = DB.DB().Connection;
connection.Open();
var command = connection.CreateCommand();
command.CommandText = full_query;
try
{
using (var reader = command.ExecuteReader())
{
for (int i = 0; i < reader.FieldCount; i++)
{
results.ColumnNames.Add(reader.GetName(i));
}
while (reader.Read())
{
List<string> this_res = new List<string>();
for (int i = 0; i < reader.FieldCount; ++i)
{
this_res.Add(reader[i].ToString());
}
results.Rows.Add(this_res);
}
}
}
catch (Exception ex)
{
results.ColumnNames.Add("Error");
List<string> this_error = new List<string>();
this_error.Add(ex.Message);
results.Rows.Add(this_error);
}
finally
{
connection.Close();
}
I can't destroy the connection, as it is used by the systems db object, so I need to open and close it. The try/catch/finally makes sure this happens.
I have two functions that each return the same list of objects. But, the one that uses TSQL is much faster than the one using Entity Framework and I do not understand why one would be faster than the other. Is it possible to modify my EF function to work as fast as the TSQL one?
Any help will be appreciated. My code is below:
TSQL:
public static List<ChartHist> ListHistory_PureSQL()
{
List<DataRow> listDataRow = null;
string srtQry = #"Select LoginHistoryID,
LoginDuration as LoginDuration_Pass,
0 as LoginDuration_Fail,
LoginDateTime,
LoginLocationID,
LoginUserEmailID,
LoginApplicationID,
LoginEnvironmentID,
ScriptFrequency,
LoginStatus,
Reason
From LoginHistory
Where LoginStatus = 'Pass'
UNION
Select LoginHistoryID,
0 as LoginDuration_Pass,
LoginDuration as LoginDuration_Fail,
LoginDateTime,
LoginLocationID,
LoginUserEmailID,
LoginApplicationID,
LoginEnvironmentID,
ScriptFrequency,
LoginStatus,
Reason
From LoginHistory
Where LoginStatus = 'Fail'";
using (SqlConnection conn = new SqlConnection(Settings.ConnectionString))
{
using (SqlCommand objCommand = new SqlCommand(srtQry, conn))
{
objCommand.CommandType = CommandType.Text;
DataTable dt = new DataTable();
SqlDataAdapter adp = new SqlDataAdapter(objCommand);
conn.Open();
adp.Fill(dt);
if (dt != null)
{
listDataRow = dt.AsEnumerable().ToList();
}
}
}
var listChartHist = (from p in listDataRow
select new ChartHist
{
LoginHistoryID = p.Field<Int32>("LoginHistoryID"),
LoginDuration_Pass = p.Field<Int32>("LoginDuration_Pass"),
LoginDuration_Fail = p.Field<Int32>("LoginDuration_Fail"),
LoginDateTime = p.Field<DateTime>("LoginDateTime"),
LoginLocationID = p.Field<Int32>("LoginLocationID"),
LoginUserEmailID = p.Field<Int32>("LoginUserEmailID"),
LoginApplicationID = p.Field<Int32>("LoginApplicationID"),
LoginEnvironmentID = p.Field<Int32>("LoginEnvironmentID"),
ScriptFrequency = p.Field<Int32>("ScriptFrequency"),
LoginStatus = p.Field<String>("LoginStatus"),
Reason = p.Field<String>("Reason")
}).ToList();
return listChartHist;
}
EF:
public static List<ChartHist> ListHistory()
{
using (var db = new LatencyDBContext())
{
var loginHist = (from hist in db.LoginHistories
select new { LoginHistory = hist }).ToList();
//PUT LOGIN HISTORY RECORDS INTO A LOCAL LIST
var listHistory = new List<ChartHist>();
foreach (var item in loginHist)
{
var localHistData = new ChartHist();
localHistData.LoginHistoryID = item.LoginHistory.LoginHistoryID;
//split up the duration for pass and fail values
if (item.LoginHistory.LoginStatus.ToUpper() == "PASS")
{
localHistData.LoginDuration_Pass = Convert.ToDouble(item.LoginHistory.LoginDuration);
localHistData.LoginDuration_Fail = 0;
}
else if (item.LoginHistory.LoginStatus.ToUpper() == "FAIL")
{
localHistData.LoginDuration_Pass = 0;
localHistData.LoginDuration_Fail = Convert.ToDouble(item.LoginHistory.LoginDuration);
}
localHistData.LoginDateTime = item.LoginHistory.LoginDateTime;
localHistData.LoginLocationID = item.LoginHistory.LoginLocationID;
localHistData.LoginUserEmailID = item.LoginHistory.LoginUserEmailID;
localHistData.LoginApplicationID = item.LoginHistory.LoginApplicationID;
localHistData.LoginEnvironmentID = item.LoginHistory.LoginEnvironmentID;
localHistData.LoginStatus = item.LoginHistory.LoginStatus;
localHistData.Reason = item.LoginHistory.Reason;
localHistData.ScriptFrequency = item.LoginHistory.ScriptFrequency;
listHistory.Add(localHistData);
}
return listHistory;
}
}
Of course EF will take longer to execute than a plain old SQL query, and there's very little that you can do about it (except write the most optimal LINQ queries that you can).
There's a very simple reason why this is so. Running a direct SQL command will just send back the data, with no muss and no fuss attached to it, waiting for you to do the data manipulations to get it to the point where it fits nicely into whatever data structure you want it in. Running EF, on the other hand, means that not only does it run the SQL command, but it massages the data for you into objects that you can manipulate right away. That extra action of going through ADO.NET and converting the data into the objects automatically means that it will take longer than just doing the plain SQL query.
On the flip side of that coin, however, EF does provide a very nice and simple way to debug and solve whatever problems you might have from a specific query/function (like by any exceptions thrown).
I can't performance test this, but try this solution instead before you remove EF entirely:
var loginHist = db.LoginHistories.Where(item => item.LoginStatus.ToUpper() == "PASS" || item.LoginStatus.ToUpper() == "FAIL")
.Select(item => new ChartHist()
{
LoginHistoryID = item.LoginHistoryID,
LoginDuration_Pass = item.LoginStatus.ToUpper() == "PASS" ? Convert.ToDouble(item.LoginDuration) : 0,
LoginDuration_Fail = item.LoginStatus.ToUpper() == "FAIL" ? Convert.ToDouble(item.LoginDuration) : 0,
LoginDateTime = item.LoginDateTime,
LoginLocationID = item.LoginLocationID,
LoginUserEmailID = item.LoginUserEmailID,
LoginApplicationID = item.LoginApplicationID,
LoginEnvironmentID = item.LoginEnvironmentID,
LoginStatus = item.LoginStatus,
Reason = item.Reason,
ScriptFrequency = item.ScriptFrequency,
});
return loginHist.ToList();
This is the "correct" way to populate a new object from a select. It will only retrieve the data you care about, and will put it directly into the object, rather than converting it into an object and then converting it again, from one object to another.
Note: I prefer the functional calls to the from / select form, but it'd be correct either way.
I need to read the values from a variable which is of type object for e..g.., i have a variable called result as:
object result= h[key];
h[key] is a hash table which returns 5 values to result variable. How do I read the 1st value to my local variable of type string in C# script in SSIS package?
I can see only GetType, Equals, ToString() options for result variable.
Any help please?
there is the sample:
there is a sample; public void SQLLoop()
{
string bp,ap,ep,s,vs;
LocationInfo info = new LocationInfo();
string connection = "Server=Sname;Database=Dname;Integrated Security=SSPI";
SqlConnection conn = new SqlConnection(connection);
conn.Open();
SqlCommand sqlcmd = new SqlCommand("SELECT Bp,Ap,EP,SL,VSr from Table1", conn);
SqlDataReader rs=sqlcmd.ExecuteReader();
while (rs.Read())
{
bp = rs.GetValue(0).ToString();
ap = rs.GetValue(1).ToString();
ep = rs.GetValue(2).ToString();
s = rs.GetValue(3).ToString();
vs = rs.GetValue(4).ToString();
info.loadLocationInfo(ap, bp, ep, s, vs);
h.Add(s, info);
}
conn.Close();
}
public class LocationInfo
{
String A;
String B;
String E;
String S;
String V;
int id;
public LocationInfo()
{
}
public void loadLocationInfo(String a,String b,String e,String s,String v)
{
A =a ;
B =b ;
E=e ;
S =s;
V = v;
}
}
now
public void fun1()
{
var result = (object )h[subject];
///read values from the hash table
}
Supposing you know the type of the result you can cast the objectvar result = (MyType) h[key]
EDIT: use this inside your function to get first value var result = ((LocationInfo) h[key]).A
Update:
Ok well you have the LocationInfo class so do something like this:
LocationInfo result = (LocationInfo)h[key];
Then just make some properties on the LocationInfo class to retrieve your strings.
Your probably need to cast the object that is in the hashtable. So something like:
result = (Type)h[key];
Here is an example of how it would work:
Person1 = new Person("David", "Burris");
Person2 = new Person("Johnny", "Carrol");
Person3 = new Person("Ji", "Jihuang");
//The Add method takes Key as the first parameter and Value as the second parameter.
try
{
MyTable.Add(Person1.Lname, Person1);
MyTable.Add(Person2.Lname, Person2);
MyTable.Add(Person3.Lname, Person3);
}
catch (ArgumentException ae)
{
MessageBox.Show("Duplicate Key");
MessageBox.Show(ae.Message);
}
So when you want to retrieve from the table you would do:
Person result = (Person)h[key];
You have to cast result to the class or interface you are expecting.
var result = (IExpectedObject)h[key];