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?
Related
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.
I am preparing store procedure on cosmosdb by Javascript, however, it gets less documents than the real number of documents in collection.
The sproc is called by C#, C# pass a parameter "transmitterMMSI" which is also the partition key of this collection.
First, the following query is executed in sproc:
var query = 'SELECT COUNT(1) AS Num FROM AISData a WHERE a.TransmitterMMSI="' + transmitterMMSI + '"';
The result is output in response, and the value is 5761, which is the same as the real number of documents in collection.
However, when I change the query to the following:
var query = 'SELECT * FROM AISData a WHERE a.TransmitterMMSI="' + transmitterMMSI + '"';
The documents.length is output as 5574, which is smaller than the real number.
I have already changed the pageSize: -1, which should mean unlimited.
I did some search with google and stack overflow, it seems that continuation can be help. However, I tried some examples, and they don't work.
Anyone familiar with this can help?
The following list the scripts.
The sproc js script is here, which is also the file "DownSampling.js" used in the C# code:
function DownSampling(transmitterMMSI, interval) {
var context = getContext();
var collection = context.getCollection();
var response = context.getResponse();
var receiverTime;
var tempTime;
var groupKey;
var aggGroup = new Object();
var query = 'SELECT * FROM AISData a WHERE a.TransmitterMMSI="' + transmitterMMSI + '"';
var accept = collection.queryDocuments(collection.getSelfLink(), query, { pageSize: -1},
function (err, documents, responseOptions) {
if (err) throw new Error("Error" + err.message);
// Find the smallest deviation comparting to IntervalTime in each group
for (i = 0; i < documents.length; i++) {
receiverTime = Date.parse(documents[i].ReceiverTime);
tempTime = receiverTime / 1000 + interval / 2;
documents[i].IntervalTime = (tempTime - tempTime % interval) * 1000;
documents[i].Deviation = Math.abs(receiverTime - documents[i].IntervalTime);
// Generate a group key for each group, combinated of TransmitterMMSI and IntervalTime
groupKey = documents[i].IntervalTime.toString();
if (typeof aggGroup[groupKey] === 'undefined' || aggGroup[groupKey] > documents[i].Deviation) {
aggGroup[groupKey] = documents[i].Deviation;
}
}
// Tag the downsampling
for (i = 0; i < documents.length; i++) {
groupKey = documents[i].IntervalTime;
if (aggGroup[groupKey] == documents[i].Deviation) {
documents[i].DownSamplingTag = 1;
} else {
documents[i].DownSamplingTag = 0;
}
// Remove the items that are not used
delete documents[i].IntervalTime;
delete documents[i].Deviation;
// Replace the document
var acceptDoc = collection.replaceDocument(documents[i]._self, documents[i], {},
function (errDoc, docReplaced) {
if (errDoc) {
throw new Error("Update documents error:" + errDoc.message);
}
});
if (!acceptDoc) {
throw "Update documents not accepted, abort ";
}
}
response.setBody(documents.length);
});
if (!accept) {
throw new Error("The stored procedure timed out.");
}
}
And the C# code is here:
private async Task DownSampling()
{
Database database = this.client.CreateDatabaseQuery().Where(db => db.Id == DatabaseId).ToArray().FirstOrDefault();
DocumentCollection collection = this.client.CreateDocumentCollectionQuery(database.SelfLink).Where(c => c.Id == AISTestCollectionId).ToArray().FirstOrDefault();
string scriptFileName = #"..\..\StoredProcedures\DownSampling.js";
string scriptId = Path.GetFileNameWithoutExtension(scriptFileName);
var sproc = new StoredProcedure
{
Id = scriptId,
Body = File.ReadAllText(scriptFileName)
};
await TryDeleteStoredProcedure(collection.SelfLink, sproc.Id);
sproc = await this.client.CreateStoredProcedureAsync(collection.SelfLink, sproc);
IQueryable<dynamic> query = this.client.CreateDocumentQuery(
UriFactory.CreateDocumentCollectionUri(DatabaseId, AISTestCollectionId),
new SqlQuerySpec()
{
//QueryText = "SELECT a.TransmitterMMSI FROM " + AISTestCollectionId + " a",
QueryText = "SELECT a.TransmitterMMSI FROM " + AISTestCollectionId + " a WHERE a.TransmitterMMSI=\"219633000\"",
}, new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true, MaxDegreeOfParallelism = -1, MaxBufferedItemCount = -1 });
List<dynamic> transmitterMMSIList = query.ToList(); //TODO: Remove duplicates
Console.WriteLine("TransmitterMMSI count: {0}", transmitterMMSIList.Count());
HashSet<string> exist = new HashSet<string>();
foreach (var item in transmitterMMSIList)
{
//int transmitterMMSI = Int32.Parse(item.TransmitterMMSI.ToString());
string transmitterMMSI = item.TransmitterMMSI.ToString();
if (exist.Contains(transmitterMMSI))
{
continue;
}
exist.Add(transmitterMMSI);
Console.WriteLine("TransmitterMMSI: {0} is being processed.", transmitterMMSI);
var response = await this.client.ExecuteStoredProcedureAsync<string>(sproc.SelfLink,
new RequestOptions { PartitionKey = new PartitionKey(transmitterMMSI) }, transmitterMMSI, 30);
string s = response.Response;
Console.WriteLine("TransmitterMMSI: {0} is processed completely.", transmitterMMSI);
}
}
private async Task TryDeleteStoredProcedure(string collectionSelfLink, string sprocId)
{
StoredProcedure sproc = this.client.CreateStoredProcedureQuery(collectionSelfLink).Where(s => s.Id == sprocId).AsEnumerable().FirstOrDefault();
if (sproc != null)
{
await client.DeleteStoredProcedureAsync(sproc.SelfLink);
}
}
I tried to comment the 2 loops in the JS codes, only the documents.length output, while the response number is still less. However, I changed the query to SELECT a.id, the documents.length is correct. Looks like it is the continuation issue.
The sproc is probably timing out. To use a continuation token in these circumstances, you will need to return it to your C# calling code then make another call to the sproc passing in your token. If you show us your sproc code we can help more.
You can use a continuation token to make repeated calls to queryDocuments() from within the sproc without additional roundtrips to the client. Keep in mind that if you do this too many times your sproc will eventually timeout, though. In your case, it sounds like you're already very close to getting all of the documents you're seeking so maybe you will be OK.
Here is an example of using a continuation token within a sproc to query multiple pages of data:
function getManyThings() {
var collection = getContext().getCollection();
var query = {
query: 'SELECT r.id, r.FieldOne, r.FieldTwo FROM root r WHERE r.FieldThree="sought"'
};
var continuationToken;
getThings(continuationToken);
function getThings(continuationToken) {
var requestOptions = {
continuation: continuationToken,
pageSize: 1000 // Adjust this to suit your needs
};
var isAccepted = collection.queryDocuments(collection.getSelfLink(), query, requestOptions, function (err, feed, responseOptions) {
if (err) {
throw err;
}
for (var i = 0, len = feed.length; i < len; i++) {
var thing = feed[i];
// Do your logic on thing...
}
if (responseOptions.continuation) {
getThings(responseOptions.continuation);
}
else {
var response = getContext().getResponse();
response.setBody("RESULTS OF YOUR LOGIC");
}
});
if (!isAccepted) {
var response = getContext().getResponse();
response.setBody("Server rejected query - please narrow search criteria");
}
}
}
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 });
I got this tblDocument table which has a one to many relationship to a couple of other tables. I have created this querystring that displays the content of the document. In this soulution i only display the DocPerson id. What im trying to do is to display the name of the person which is located in the tblPerson table. Can someone help me?
if (!IsPostBack)
{
string strId = Request.QueryString["id"];
int id;
if (int.TryParse(strId, out id))
{
var db = new MyModelContext();
var p = db.tblDocuments.SingleOrDefault(x => x.DocId == id);
if (p != null)
{
lblCaseNr.Text = p.DocNr;
lblPerson.Text = p.DocPerson.ToString();
lblCourt.Text = p.DocCourt.ToString();
lblYear.Text = p.Docyear.ToString();
lblResume.Text = p.DocResume;
lblResult.Text = p.DocResult;
lblLaw.Text = p.DocLaw.ToString();
}
}
}
}
For your LINQ expression, try the following:
var q = from d in db.tblDocuments join p in db.tblPerson
on d.DocId equals p.DocId
where d.DocId == id
select new {d.DocId, p.DocPerson}
If you need to access other fields, simply add them to your select new clause.
LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.
public ActionResult PopulateFromDB(string sidx, string sord, int page, int rows)
{
var context = new NerdDinnerEntities();
var jsonData = new
{
total = 1,
page = page,
sord =sord,
records = context.Authors.Count(),
rows = (from n in context.Authors
select new
{ AuthorId = n.AuthorId ,
cell = new string[] { n.AuthorId.ToString(), n.Name.ToString(), n.Location.ToString() }
}).ToList()
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
}
I am writting ToList or Toarray is it not working the error comes :
public ActionResult PopulateFromDB(string sidx, string sord, int page, int rows)
{
var context = new NerdDinnerEntities();
var jsonData = new
{
total = 1,
page = page,
sord =sord,
records = context.Authors.Count(),
rows = (from n in context.Authors
select new
{ AuthorId = n.AuthorId ,
cell = new string[] { n.AuthorId.ToString(), n.Name.ToString(), n.Location.ToString() }
}).ToList()
};
return Json(jsonData,JsonRequestBehavior.AllowGet);
}
From your code I assume your adding a custom property cell for display/storage purposes on the client-side. I would avoid this as your essentially coupling your API call to one particular client. I would suggest you simply return the data required & deal with it at the client-side specifically e.g.
Server
...
select new
{
Id = n.AuthorId,
Name = n.Name,
Location = n.Location
}).ToList();
...
Client
var response = ...
foreach (var author in response)
{
var cell = new string[] { author.Id.ToString(), author.Name, author.Location };
// do something with cell
}
You should try SqlFunctions.StringConvert to convert this, There is no overload for int so you should cast your number to a double or a decimal.
public ActionResult PopulateFromDB(string sidx, string sord, int page, int rows)
{
var context = new NerdDinnerEntities();
var jsonData = new
{
total = 1,
page = page,
sord =sord,
records = context.Authors.Count(),
rows = (from n in context.Authors
select new
{ AuthorId = n.AuthorId ,
cell = new string[] { SqlFunctions.StringConvert((double)n.AuthorId), n.Name, n.Location }
}).ToList()
};
return Json(jsonData,JsonRequestBehavior.AllowGet);
}
You are not using LinqToSql Classes, if you were using that your code should work, but as you mention that you are using LinqToEntity then You should use SqlFunctions.StringConvert to convert to string.