Optimize code for reduce Reflection impact on performance - reflection

I'm building a web API Rest on .Net Core and ADO.Net
I use this code to populate my model object from DataRow:
public IEnumerable<TEntity> Map(DataTable table)
{
List<TEntity> entities = new List<TEntity>();
var columnNames = table.Columns.Cast<DataColumn>().Select(x => x.ColumnName).ToList();
var properties = (typeof(TEntity)).GetProperties().ToList();
foreach (DataRow row in table.Rows)
{
TEntity entity = new TEntity();
foreach (var prop in properties)
{
PropertyMapHelper.Map(typeof(TEntity), row, prop, entity);
}
entities.Add(entity);
}
return entities;
}
And use this other code for create the necesary SQL Update command:
protected void base_UpdateCommand(IDbCommand myCommand, TEntity entity, string sWhere)
{
var properties = (typeof(TEntity)).GetProperties().ToList();
string sProps = "";
string sCommand = "";
foreach (var prop in properties)
{
bool bIgnore = prop.GetCustomAttributes(true).Any(a => a is KeyAttribute);
if (prop.Name.ToUpper() != sKeyField.ToUpper() && !bIgnore)
{
sProps = sProps + prop.Name + "=#" + prop.Name + ", ";
var p = myCommand.CreateParameter();
p.ParameterName = prop.Name;
if (prop.GetValue(entity) == null)
p.Value = DBNull.Value;
else
p.Value = prop.GetValue(entity);
myCommand.Parameters.Add(p);
}
}
sProps = sProps.Substring(0, sProps.Length - 2);
sCommand = "UPDATE [" + sTable + "] SET " + sProps;
sCommand = sCommand + " WHERE " + sWhere;
myCommand.CommandText = sCommand;
}
I know that reflection has impact on performance, so i'm looking for suggestion on how to improve this code.
Thanks!

You might consider using Dapper. It is a wrapper around ADO.NET so technically it can't be faster than ADO.NET, but in most cases it uses better coding practices comparing to custom code you use to manupulate data via ADO.NET, so potentially it could give the better performance results.

Related

How to use GUID as Primary Key with System.Data.Sqlite

I'm not saying it's advisable, but sometimes you inherit something which just needs to work. In this case it's Guids for Primary Keys...
Out of the box, you'll get an error about System.Guid not working with byte[] (or String if you're using BinaryGUID=False).
To fix this, you need to intercept byte[] arrays (or strings) and instead return Guid type. This is possible with the System.Data.Sqlite provider.
First
using System.Data.SQLite;
Then, put this in the constructor of your code-first db context:
var con = (SQLiteConnection)base.Database.Connection;
var bind = System.Data.SQLite.SQLiteTypeCallbacks.Create(
null,
new SQLiteReadValueCallback(GuidInterceptor), null, null);
con.SetTypeCallbacks("uniqueidentifier", bind);
con.SetTypeCallbacks("", bind); //Sometimes, the system just doesn't know
con.Flags |= SQLiteConnectionFlags.UseConnectionReadValueCallbacks;
And then this is the magic function:
private void GuidInterceptor(SQLiteConvert convert, SQLiteDataReader reader, SQLiteConnectionFlags flags, SQLiteReadEventArgs args, string typename, int index, object userdata, out bool complete)
{
complete = false;
if (typename == "uniqueidentifier")
{
var e = (SQLiteReadValueEventArgs)args;
var o = reader.GetGuid(index);
e.Value.Value = o;
e.Value.GuidValue = o;
complete = true;
}
else
{
var o = reader.GetValue(index);
if (o is byte[])
{
var b = (byte[])o;
if (b.Length == 16)
{
var e = (SQLiteReadValueEventArgs)args;
var g = new Guid(b);
e.Value.Value = g;
e.Value.GuidValue = g;
complete = true;
}
}
else if (o is string)
{
var s = (string)o;
if (s.Length == 36)
{
var e = (SQLiteReadValueEventArgs)args;
var goGuid = (e.MethodName == "GetGuid");
if (!goGuid)
goGuid = (s[8] == '-' && s[13] == '-' && s[18] == '-' && s[23] == '-');
Guid g;
if (goGuid && Guid.TryParse(s, out g))
{
e.Value.Value = g;
e.Value.GuidValue = g;
complete = true;
}
else
{
}
}
}
}
}
That only fixes reading in of data. If you try to .Where() on a Guid member, and you're using BinaryGUID=True no rows will be returned (when there should be). For now, you'll need BinaryGUID=False, which does take up more space, but it's a simple solution.
If you can help it, try and define your Guid properties instead as:
[Key]
[MaxLength(16)]
[MinLength(16)]
public byte[] AccountUserId { get; set; }
You'll be forced to call guidValue.ToByteArray() in your application layer. You might be able to create some helpers to you're not having to call that function. Having your own custom function proxy for creating and parsing Guids may help, so you can easily change the implementation.
In my case, I don't have the luxury of time to convert all the Guid properties and usages to byte[]

passing array of parameters in ASP .NET Web API

Creating a Web API through which array of id is passed and returns the result from the OracleDB.
public class SampleController : ApiController
{
public string Getdetails([FromUri] int []id)
{
string inconditons = "";
for (int i = 0; i < id.Length; i++)
{
if (i == id.Length - 1)
{
inconditons = inconditons + id[i];
}
else
{
inconditons = inconditons + id[i] + ", ";
}
}
using (var dbConn = new OracleConnection("DATA SOURCE=X;PASSWORD=03JD;PERSIST SECURITY INFO=True;USER ID=IN"))
{
dbConn.Open();
var strQuery = #"Select PRIO_CATEGORY_ID as PRIO,LANG_ID as LANG, REC_DATE as REC, REC_USER as RECUSER, DESCR,COL_DESCR AS COL,ROW_DESCR as DROW,ABBR from STCD_PRIO_CATEGORY_DESCR where REC_USER IN (" + inconditons + ");";
var queryResult = dbConn.Query<SamModel>(strQuery);
return JsonConvert.SerializeObject(queryResult);
}
}
}
And called the API as http://localhost:35432/api/Sample?id=1&id=83 it throws an error saying on var queryResult = dbConn.Query(strQuery);
But if I just give one parameter as below it works
var strQuery = #"Select PRIO_CATEGORY_ID as PRIO,LANG_ID as LANG, REC_DATE as REC, REC_USER as RECUSER, DESCR,COL_DESCR AS COL,ROW_DESCR as DROW,ABBR from STCD_PRIO_CATEGORY_DESCR where REC_USER =" +id ;
Can anyone please suggest me what is the issue here as a single parameter works. Thanks
Check to make sure your don't have any stray characters in your query.
As stated in the comments
Use parameterized queries, otherwise you're vulnerable to errors like
this and SQL Injection attacks.
So pass the id array into the parameterized query when executing.
Here is a refactored version of your example.
public class SampleController : ApiController {
public string Getdetails([FromUri] int[] id) {
var inconditions = id.Distinct().ToArray();
using (var dbConn = new OracleConnection("DATA SOURCE=h;PASSWORD=C;PERSIST SECURITY INFO=True;USER ID=T")) {
dbConn.Open();
var strQuery = "SELECT PRIO_CATEGORY_ID AS PRIO, LANG_ID AS LANG, REC_DATE AS REC, REC_USER AS RECUSER, DESCR, COL_DESCR AS COL, ROW_DESCR AS DROW, ABBR FROM STCD_PRIO_CATEGORY_DESCR WHERE REC_USER IN (:p)";
var queryResult = dbConn.Query<SamModel>(strQuery, new { p = inconditions });
return JsonConvert.SerializeObject(queryResult);
}
}
}
Your code looks fine to me. It might fail if your id array parameter is empty (but it will be a different error than what you see now). Put a breakpoint in your code and inspect the value of that.
Also for converting your array to string, You may use the String.Join method.
var ids = String.Join(",",id);
This will give the result like "1,3,5", assuming your int array has 3 items ,1,3 and 5
Now you can use this string variable in your query. Also you may consider passing this data as a parameter.
var q= " ... where REC_USER IN (#ids);" //Please fill the missing part of query
var result = con.Query<SomeModel>(q,new { ids });

Select query with multiple parameterized in-clauses

I have a form with 2 dropdown lists and 3 checkbox sections that users select from to search the database. Since all of the tables are small, I'd like to use a single SELECT statement to return the results. I found an extension on mikesdotnetting that works great with a single IN clause
using System;
using System.Collections.Generic;
using WebMatrix.Data;
using System.Linq;
public static class DatabaseExtensions
{
public static IEnumerable<dynamic> QueryIn(this Database db, string commandText, string values)
{
if (string.IsNullOrEmpty(values))
throw new ArgumentException("Value cannot be null or an empty string", "values");
var temp = values.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var temp2 = values.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var temp3 = values.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var parms = temp.Select((s, i) => "#" + i.ToString()).ToArray();
var parms2 = temp2.Select((s, i) => "#" + i.ToString()).ToArray();
var parms3 = temp3.Select((s, i) => "#" + i.ToString()).ToArray();
var inclause = string.Join(",", parms, parms2, parms3);
return db.Query(string.Format(commandText, inclause), temp, temp2, temp3);
}
public static int ExecuteIn(this Database db, string commandText, string values)
{
if (string.IsNullOrEmpty(values))
throw new ArgumentException("Value cannot be null or an empty string", "values");
var temp = values.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var parms = temp.Select((s, i) => "#" + i.ToString()).ToArray();
var inclause = string.Join(",", parms);
return db.Execute(string.Format(commandText, inclause), temp);
}
}
Here is the page code showing how I envision the query:
var gender = Request["genderSvd"];
var person = Request["chkPerson"];
var spec = Request["chkSpec"];
var county = Request["ctyLoc"];
var crim = Request["chkCrim"];
var db = Database.Open("HBDatabase");
var sql = "SELECT DISTINCT tblProgram.* FROM lnkPrgPersonSvd INNER JOIN tblProgram ON lnkPrgPersonSvd.prgId = tblProgram.prgId";
sql += "INNER JOIN lnkPrgSpecial ON tblProgram.prgId = lnkPrgSpecial.prgId INNER JOIN tblProgram.prgId = lnkPrgCriminal.prgId ";
sql += "WHERE (lnkPrgPersonSvd.personId IN ({0})) AND (lnkPrgSpecial.specId IN ({1})) AND tblProgram.prgGenderSvdId = #2 ";
sql += "AND tblProgram.prgCounty = #3 AND (lnkPrgCriminal.criminalId IN ({4}))";
var prgList = db.QueryIn(sql, person, spec, gender, county, crim);
That, obviously, doesn't work, because the extension cannot take that many arguments. I'm looking for a way to modify the class to allow for the additional parameters so that the query can run with just the one execution. This may not be possible.
Also, I did find several threads that dealt with handling a single parameterized in clause, but no real mention of multiples with variable lists.
I am not adept with stored procedures or writing my own classes. Any suggestions?

Windows Sharepoint Services - FullTextSqlQuery Document library Unable to find items created by SYSTEM ACCOUNT

We have created an ASP.NET web app that upload files to WSS Doc Libary. The files get added under 'SYSTEM ACCOUNT' in the library. The FullTextSqlQuery class is used to search the document libary items. But it only searches files that has been uploaded by a windows user account like 'Administrator' and ignores the ones uploaded by 'SYSTEM ACCOUNT'. As a result the search results are empty even though we have the necessary data in the document library. What could be the reason for this?
The code is given below:
public static List GetListItemsFromFTSQuery(string searchText)
{
string docLibUrl = "http://localhost:6666/Articles%20Library/Forms/AllItems.aspx";
List items = new List();
DataTable retResults = new DataTable();
SPSecurity.RunWithElevatedPrivileges(delegate
{
using (SPSite site = new SPSite(docLibUrl))
{
SPWeb CRsite = site.OpenWeb();
SPList ContRep = CRsite.GetListFromUrl(docLibUrl);
FullTextSqlQuery fts = new FullTextSqlQuery(site);
fts.QueryText =
"SELECT Title,ContentType,Path FROM portal..scope() WHERE freetext('" +
searchText +
"') AND (CONTAINS(Path,'\"" +
ContRep.RootFolder.ServerRelativeUrl + "\"'))";
fts.ResultTypes = ResultType.RelevantResults;
fts.RowLimit = 300;
if (SPSecurity.AuthenticationMode != System.Web.Configuration.AuthenticationMode.Windows)
fts.AuthenticationType = QueryAuthenticationType.PluggableAuthenticatedQuery;
else
fts.AuthenticationType = QueryAuthenticationType.NtAuthenticatedQuery;
ResultTableCollection rtc = fts.Execute();
if (rtc.Count > 0)
{
using (
ResultTable relevantResults =
rtc[ResultType.RelevantResults])
retResults.Load(relevantResults,
LoadOption.OverwriteChanges);
foreach (DataRow row in retResults.Rows)
{
if (!row["Path"].ToString().EndsWith(".aspx"))
//if (row["ContentType"].ToString() == "Item")
{
using (
SPSite lookupSite =
new SPSite(row["Path"].ToString()))
{
using (SPWeb web = lookupSite.OpenWeb())
{
SPFile file =
web.GetFile(row["Path"].ToString());
items.Add(file.Item);
}
}
}
}
}
} //using ends here
});
return items;
}
Try amending the freetext clause in your query to be :
... freetext(*, '" + searchText + "' ....

Flex / Air sqlite async issue

I am writing an app that parses a csv file to an array and then insert the array into a sqlite database. I am having issues with Async connection to the sqlite database, I get a 3106 error...... I think the problem is that it executes the next statement before the previous is finished but I can't find a way to deal with this. Any help would be greatly appreciated.
public function addData(categories:Array):void{
status = "Adding data to table";
var insrtStmt:SQLStatement = new SQLStatement();
insrtStmt.sqlConnection = conn;
for(var i:int=categories.length-1; i>=0; i--){
insrtStmt.text = "";
insrtStmt.text += "INSERT INTO masterlist ";
insrtStmt.text += "(mainid, transactionDate, tradeId, ccyPair, account, buySell, customer, date,";
insrtStmt.text += " additionalid, dealType, traderName, genericType, owner) ";
insrtStmt.text += "VALUES(#mainid, #transactionDate, #tradeId, #ccyPair, #account, #buySell, #customer, #date,";
insrtStmt.text += " #additionalid, #dealType, #traderName, #genericType, #owner);";
insrtStmt.parameters["#mainid"] = categories[i].mainid;
insrtStmt.parameters["#transactionDate"] = categories[i].transactionDate;
insrtStmt.parameters["#tradeId"] = categories[i].tradeId;
insrtStmt.parameters["#ccyPair"] = categories[i].ccyPair;
insrtStmt.parameters["#account"] = categories[i].account;
insrtStmt.parameters["#buySell"] = categories[i].buySell;
insrtStmt.parameters["#customer"] = categories[i].customer;
insrtStmt.parameters["#date"] = categories[i].date;
insrtStmt.parameters["#additionalid"] = categories[i].additionalid;
insrtStmt.parameters["#dealType"] = categories[i].dealType;
insrtStmt.parameters["#traderName"] = categories[i].traderName;
insrtStmt.parameters["#genericType"] = categories[i].genericType;
insrtStmt.parameters["#owner"] = categories[i].owner;
insrtStmt.execute();
}
}
if you think the problem is that it is still executing, just add an event listener to the statement for it's "result" event, and then fire off the next statements.
public function addDataSet( categories : Array ) : void {
_categories = categories;
_loopcounter = categories.length;
_insrtStmt : SQLStatement = new SQLStatement();
_insrtStmt.addEventListener( "result", addData );
addData();
}
public function addData(event : Event = null) : void {
_loopcounter--;
// Set up rest of statement
_insrtStmt.execute();
}
Maybe you should use a synchronous connection instead of an async one? If needed you can open multiple connections to the same db and use the appropriate connection.
For executing multiple commands at once - use transactions.
Simple example from http://www.zedia.net/2009/air-sqlite-optimization-tricks/
_updateStmt.sqlConnection = _conn;
_updateStmt.text = "UPDATE main.myTable SET statusF=#STATUS WHERE keyId=#ID";
_conn.begin();//_conn is a SQLConnection, I didn't take the time to write the code for it, but this is where the magic happens
for (var i:uint = 0; i < currentArray.length; i++){
_updateStmt.parameters["#STATUS"] = currentArray[i].status;
_updateStmt.parameters["#ID"] = currentArray[i].id;
_updateStmt.execute();
}
_conn.commit();
Also have a look at the adobe flex livedocs chapter 'Improving database performance'
Or you could create a new SQLStatement for each query, that way you don't need to wait and you can fire the inserts off as fast as the client machine can process them.
public function addData(categories:Array):void {
status = "Adding data to table";
var insrtStmt:SQLStatement;
for(var i:int=categories.length-1; i>=0; i--) {
insrtStmt = new SQLStatement();
insrtStmt.sqlConnection = conn;
insrtStmt.text = "";
insrtStmt.text += "INSERT INTO masterlist ";
insrtStmt.text += "(mainid, transactionDate, tradeId, ccyPair, account, buySell, customer, date,";
insrtStmt.text += " additionalid, dealType, traderName, genericType, owner) ";
insrtStmt.text += "VALUES(#mainid, #transactionDate, #tradeId, #ccyPair, #account, #buySell, #customer, #date,";
insrtStmt.text += " #additionalid, #dealType, #traderName, #genericType, #owner);";
insrtStmt.parameters["#mainid"] = categories[i].mainid;
insrtStmt.parameters["#transactionDate"] = categories[i].transactionDate;
insrtStmt.parameters["#tradeId"] = categories[i].tradeId;
insrtStmt.parameters["#ccyPair"] = categories[i].ccyPair;
insrtStmt.parameters["#account"] = categories[i].account;
insrtStmt.parameters["#buySell"] = categories[i].buySell;
insrtStmt.parameters["#customer"] = categories[i].customer;
insrtStmt.parameters["#date"] = categories[i].date;
insrtStmt.parameters["#additionalid"] = categories[i].additionalid;
insrtStmt.parameters["#dealType"] = categories[i].dealType;
insrtStmt.parameters["#traderName"] = categories[i].traderName;
insrtStmt.parameters["#genericType"] = categories[i].genericType;
insrtStmt.parameters["#owner"] = categories[i].owner;
insrtStmt.execute();
}
}

Resources