I'm new to Dapper, and writing a query that will pull from a provided schema and table, along with using dynamic ordering and filtering.
Dapper make dynamic parameters very simple, however, I'm not sure how to do this with tables in the order by and where clauses. Here's my method below, and I see the issues with SQL injection:
public GridData GetGridData(string schema, string table, TableDataParameters tableDataParameters)
{
using (var dbConnection = VarConnection)
{
dbConnection.Open();
if (!this.TableExists(dbConnection, schema, table))
{
throw new ItemNotFoundException($"Could not locate table {schema}.{table}.");
}
string orderyByClause = string.Join(",", tableDataParameters.SortModel.Select(s => $"[{s.ColId}] {(s.Sort.ToLower() == "asc" ? "asc" : "desc")}"));
var parameters = new DynamicParameters();
string whereClause;
if (tableDataParameters.FilterModel == null || !tableDataParameters.FilterModel.Any())
{
whereClause = "1=1";
}
else
{
whereClause = string.Join(" AND ", tableDataParameters.FilterModel.Select((fm, i) =>
{
string whereParam = $"whereParam{i}";
parameters.Add(whereParam, fm.Filter);
if (fm.Operation == "startsWith")
{
return $"[{fm.Column}] LIKE #{whereParam} + '%'";
}
throw new InvalidOperationException($"Unsupported filter operation '{fm.Operation}'");
}));
}
var query = $"SELECT COUNT(1) [total] " +
$"FROM [{schema}].[{table}] " +
$"WHERE {whereClause} " +
$"SELECT * " +
$"FROM [{schema}].[{table}] " +
$"WHERE {whereClause} " +
$"ORDER BY {orderyByClause} " +
$"OFFSET {tableDataParameters.StartIndex.Value} ROWS " +
$"FETCH NEXT {tableDataParameters.StopIndex.Value - tableDataParameters.StartIndex.Value} ROWS ONLY";
int total = 0;
using (var reader = dbConnection.ExecuteReader(query, parameters))
{
// First batch, it's the count
if (reader.Read())
{
total = reader.GetInt32(0);
}
var gridColumns = new List<GridColumn>();
var gridRows = new List<string[]>();
if (reader.NextResult() && reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
string key = reader.GetName(i);
gridColumns.Add(new GridColumn(key, key, null, ""));
}
var items = new object[reader.FieldCount];
reader.GetValues(items);
gridRows.Add(items.Select(i => i.ToString()).ToArray());
}
while (reader.Read())
{
var items = new object[reader.FieldCount];
reader.GetValues(items);
gridRows.Add(items.Select(i => i.ToString()).ToArray());
}
return new GridData(tableDataParameters.StartIndex.Value, tableDataParameters.StopIndex.Value, total, gridRows.Count(), gridColumns.ToArray(), gridRows.ToArray());
}
}
}
Should I use something like DbCommandBuilder.QuoteIdentifier, https://msdn.microsoft.com/en-us/library/system.data.common.dbcommandbuilder.quoteidentifier(v=vs.110).aspx
in this case? That doesn't seem like it would help so much here.
Thanks!
Dynamic parameters is an oxymoron! Dapper makes parameters easy, but you can't paramaterize table and column names. This is a restriction of SQL, not dapper. If you really want to do this, you have to use dynamic sql and string methods, and you're on your own as regards SQL injection.
You will be happier and live longer if you don't do this. It's just a bad road. You're not adding much value, and you're potentially introducing a load of problems and limitations.
It looks like you're writing an app to browse a database. Good tools already exist for this!
Related
I have a stored procedure with around 14 different result sets. How do I retrieve them all as by now I only get the first result set.
[HttpGet]
[Route("tire-tabel")]
public List<DeviationCalculation_Result> TireTabel(decimal presentWidth, decimal presentAspectRatio, string presentRimSize, int maxDeviation)
{
using (var context = new OminiTireEntities())
{
var result = context.Database.SqlQuery<DeviationCalculation_Result>(
"exec [Tabel].[DeviationCalculation] #PresentWidth = '" + presentWidth + "', " +
"#PresentAspectRatio= '" + presentAspectRatio + "', " +
"#PresentInches= '" + presentRimSize + "', " +
"#MaxDeviation= '" + maxDeviation + "'").ToList<DeviationCalculation_Result>();
return result;
}
}
Sample Code:
using (var db = new BloggingContext())
{
// If using Code First we need to make sure the model is built before we open the connection
// This isn't required for models created with the EF Designer
db.Database.Initialize(force: false);
// Create a SQL command to execute the sproc
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = "[dbo].[GetAllBlogsAndPosts]";
try
{
db.Database.Connection.Open();
// Run the sproc
var reader = cmd.ExecuteReader();
// Read Blogs from the first result set
var blogs = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Blog>(reader, "Blogs", MergeOption.AppendOnly);
foreach (var item in blogs)
{
Console.WriteLine(item.Name);
}
// Move to second result set and read Posts
reader.NextResult();
var posts = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Post>(reader, "Posts", MergeOption.AppendOnly);
foreach (var item in posts)
{
Console.WriteLine(item.Title);
}
}
finally
{
db.Database.Connection.Close();
}
}
The Translate method accepts the reader that we received when we executed the procedure, an EntitySet name, and a MergeOption. The EntitySet name will be the same as the DbSet property on your derived context. The MergeOption enum controls how results are handled if the same entity already exists in memory.
Reference : https://msdn.microsoft.com/en-us/library/jj691402(v=vs.113).aspx
I also recommend to use Parameters instead of executing the queries as mentioned in the question as it can result in SQL injection
With Dapper it is super simple:
public DeviationCalculationResult Get(decimal presentWidth, decimal presentAspectRatio, string presentRimSize, int maxDeviation)
{
using (var context = new OminiTireEntities())
{
var reader = context.Database.Connection.QueryMultiple("[Tabel].[DeviationCalculation]",
new
{
PresentWidth = presentWidth,
PresentAspectRatio = presentAspectRatio,
PresentInches = presentRimSize,
MaxDeviation = maxDeviation
}, commandType: CommandType.StoredProcedure);
var first = reader.Read<First>().ToList().First();
var second = reader.Read<Second>().ToList().First();
var third = reader.Read<Third>().ToList().First();
//...and so on...
return new DeviationCalculationResult
{
First = first,
Second = second,
Third = third,
//...
};
}
}
I'm wondering if its possible to make this easier.
So, I have an arraylist of strings called names and an arraylist of strings called last_name.
When I'm constructing select statement, I want to combine every entry in names with everything in last_name and search in database like:
SELECT * FROM DB WHERE (names='NAMES[0]' AND last_name='LAST_NAME[0]')
or (names='NAMES[0]' AND last_name='LAST_NAME[1]')
or (names='NAMES[1]' AND last_name='LAST_NAME[0]')
or (names='NAMES[1]' AND last_name='LAST_NAME[1]')
This is an example, In my project I have 6 Lists, and I need every combination, and the easiest way was to make for in for in for in for...
Thanks a lot
This should work by using a query like this :
SELECT *
FROM DB
WHERE Name IN (#name1,#name2,#name3) AND LastName IN (#lastName1, #lastName2, #lastName3)
You can build this query in a for-loop, like this :
var names = new[] {"John", "Peter"};
var lastnames = new[] { "Doe", "Waylander" };
var nameParams = "";
var lastNameParams = "";
var cnt = 0;
foreach (var name in names)
{
var nameString = "#name" + cnt;
if (cnt!=0)
{
nameParams += ",";
}
nameParams += nameString;
cmd.Parameters.Add(nameString, name);
cnt++;
}
cnt = 0;
foreach (var lastName in lastnames)
{
var lastNameString = "#lastName" + cnt;
if (cnt != 0)
{
lastNameParams += ",";
}
lastNameParams += lastNameString;
cmd.Parameters.Add(lastNameString, lastName);
cnt++;
}
cmd.CommandText = #"SELECT *
FROM DB
WHERE NAME IN (" + nameParams + #")
AND LastName IN (" + lastNameParams + ")";
var result = cmd.ExecuteReader();
The only limitation is the number of parameters (IIRC it is about 1000). Another good alternative would be a stored procedure, like already mentioned in the comments.
I am implementing search by using dynamic LINQ, where the query gets column name and search value in runtime. In this way, I need to parse the data according to the column type-
if (isNumeric)
{
int x = Int32.Parse(txtHistorySearch.Text);
truncatedData = ((IQueryable<object>)rawData).Where(columnName + "=#0", x).ToList();
}
else if (DateTime.TryParse(txtHistorySearch.Text, out temp))
{
var parsedDt = DateTime.Parse(txtHistorySearch.Text);
var nextDay = parsedDt.AddDays(1);
truncatedData = ((IQueryable<object>)rawData).Where(columnName + ">= #0 && " + columnName + " < #1", parsedDt, nextDay).ToList();
}
else
{
truncatedData = ((IQueryable<object>)rawData).Where(columnName + "=#0", searchValue).ToList();
}
Can this be done for all data types using single where clause?
You can do this using expression trees.
https://msdn.microsoft.com/en-us/library/bb397951.aspx
https://msdn.microsoft.com/en-us/library/bb882637.aspx
I'm trying to Select an SQL table and grouping columns using Linq to SQL, Entities, or Object (I don't really know what.) I'm a bit new to Linq and could use some help. The code structure is straight-forward in my view. When I don't add in the GroupBy method, it works fine. JT_Temp is an entity model by the way. When I run my code below, it goes to the exception:
The entity or complex type 'JT_Temp' cannot be constructed in LINQ to Entities query.
I have tried this and various stackoverflow solutions but they don't seem to solve and apply to my case.
Here is my current code:
//Goal:
//SELECT EnvelopeCode, Branch_COA, AQ_COA, AQ_Branch, SUM(Amount), AQ_PostStatus FROM JT_Temp
//GROUP BY EnvelopeCode, Branch_COA, AQ_COA, AQ_Branch, AQ_PostStatus
//var csvFilteredRecord = Context.JT_Temp.SqlQuery("SELECT * FROM JT_Temp").ToList<JT_Temp>();
// GROUP BY -- No go; Manual SELECT -- No go;
try
{
var csvFilteredRecord = (
from c in Context.JT_Temp
group c by new
{
c.EnvelopeCode,
c.Branch_COA,
c.AQ_COA,
c.AQ_Branch,
c.AQ_PostStatus
} into i
select new JT_Temp
{
EnvelopeCode = i.Key.EnvelopeCode,
Branch_COA = i.Key.Branch_COA,
AQ_COA = i.Key.AQ_COA,
AQ_Branch = i.Key.AQ_Branch,
//TO-DO SUM(Amount),
AQ_PostStatus = i.Key.AQ_PostStatus
}).ToList();
foreach (var Record in csvFilteredRecord)
{
Console.WriteLine(
Record.EnvelopeCode
+ Record.Branch_COA
+ Record.AQ_COA
//+ Record.Amount
+ Record.AQ_PostStatus
);
}
}
catch (Exception e)
{
Console.WriteLine("---------- " + e.Message);
Console.ReadLine();
}
You can't project into JT_Temp. Just use an anonymous object. Also, no reason to make it a list, so I removed the .ToList()
Query syntax:
var csvFilteredRecord = (
from c in Context.JT_Temp
group c by new
{
c.EnvelopeCode,
c.Branch_COA,
c.AQ_COA,
c.AQ_Branch,
c.AQ_PostStatus
} into i
select new
{
EnvelopeCode = i.Key.EnvelopeCode,
Branch_COA = i.Key.Branch_COA,
AQ_COA = i.Key.AQ_COA,
AQ_Branch = i.Key.AQ_Branch,
//TO-DO SUM(Amount),
AQ_PostStatus = i.Key.AQ_PostStatus
});
Method syntax:
var csvFilteredRecord = Context.JT_Temp.GroupBy(k=> new
{
c.EnvelopeCode,
c.Branch_COA,
c.AQ_COA,
c.AQ_Branch,
c.AQ_PostStatus
},
v=>v.Amount,
(k,v)=>new {
k.EnvelopeCode,
k.Branch_COA,
k.AQ_COA,
k.AQ_Branch,
k.AQ_PostStatus
Amount=v.Sum()
});
foreach (var Record in csvFilteredRecord)
{
Console.WriteLine(
Record.EnvelopeCode
+ Record.Branch_COA
+ Record.AQ_COA
+ Record.Amount
+ Record.AQ_PostStatus
);
}
I have this method that should take an unknown amount of id's.
I got this method almost done but it isnt secure yet for obvious reasons, i know i could write my own method to strip the parameters but i would be more comfortable by using some build in method for this.
Here is the method
public static List<LocationModel> FetchCitiesByAreas(IEnumerable<string> areas)
{
using (var db = new BoligEnt())
{
var sqlQuery = new StringBuilder();
var first = true;
sqlQuery.Append("SELECT DISTINCT a.city AS City, a.zip AS Zip ");
sqlQuery.Append("FROM zip_city AS a ");
sqlQuery.Append("WHERE country = 1 ");
foreach (var d in areas)
{
if (first)
{
sqlQuery.Append("AND a.area_id = '" + d + "'");
first = false;
}
else
{
sqlQuery.Append("OR a.area_id = '" + d + "'");
}
}
return db.Database.SqlQuery<LocationModel>(sqlQuery.ToString()).ToList();
}
}
i know it have this function built in but as i stated earlier i dont know the exact amount of ids that will come in
db.Database.SqlQuery<LocationModel>("SELECT * FROM table WHERE id = #p0 ;", id).ToList();
Thanks
While I completely agree with paqogomez, in that you should just use LINQ to do the query, the .SqlQuery has the ability to take a parameter array. You could change your statement to look like this:
var sqlQuery = new StringBuilder();
sqlQuery.Append("SELECT DISTINCT a.city AS City, a.zip AS Zip ");
sqlQuery.Append("FROM zip_city AS a ");
sqlQuery.Append("WHERE country = 1 ");
for (int i = 0; i < areas.Count; i++)
{
if (i == 0)
{
sqlQuery.Append("AND (a.area_id = #p" + i.ToString());
}
else
{
sqlQuery.Append(" OR a.area_id = #p" + i.ToString());
}
}
sqlQuery.Append(")");
var results = db.Database.SqlQuery<LocationModel>(sqlQuery.ToString(), areas.ToArray()).ToList();
I added the missing parenthesis needed to your query to correctly filter out the OR results as well. I've also taken the assumption that areas is something like a List, or at least something you can easily get the count from.
Why dont you just use Linq?
var locations = (from zip in db.zip_city
where areas.Contains(zip.area_id) && zip.Country == 1
select new LocationModel{
City = zip.City,
Zip = zip.Zip
})
.Distinct()
.ToList();
If you still want to parameterize your query, you need to use EntityCommand
Also note that your query will fail because you havent put parenthesis around your OR statements.
I suggest structuring your sql like this:
string sqlQuery =
#"SELECT DISTINCT a.city AS City, a.zip AS Zip
FROM zip_city AS a
WHERE country = 1 AND (1=0 "
for (int i = 0; i < areas.Count; i++)
{
sqlQuery.Append("OR a.area_id = #d" + i.ToString() + " ");
}
sqlQuery.Append(")");