Best practice to build dynamic SQL queries - c#

I am looking for some tips and tricks how to build dynamic queries. I am having an application which lets the user search 10 fields in the database table. Depending on which fields in the UI are filled with a value the query should search in an additional field in the DB.
Currently I am trying to build the query using StringBuilder and adding the where clause but I really don't like this and I am wondering if there is a better way to do this, for example with LINQ if possible.
Maybe someone can bring up ideas or better some example code. Thanks and have a nice day!

With LINQ it is pretty trivial:
IQueryable<User> users = db.Users;
if(name != null) users = users.Where(u => u.Name == name);
if(dept != null) users = users.Where(u => u.Dept == dept);
...
var page = users.OrderBy(u => u.Name).Take(100).ToList();
Each successive Where composes the query with more filters; exactly what you want.
With raw TSQL, StringBuilder isn't unreasonable; just make sure that you fully parameterize it. This might mean adding parameters in each term; for example:
...
if(name != null) {
sql.Append(" and u.Name = #name");
cmd.Parameters.AddWithValue("name", name);
}
if(dept != null) {
sql.Append(" and u.Dept = #dept");
cmd.Parameters.AddWithValue("dept", dept);
}
...

Related

Is it possible to retrieve records from a database using an either all records or matching id method?

Basically I want to know if it is possible using as an example, Entity Framework, to retrieve all records from the DB table when the provided id doesn't match any of the id's in the table, but if there are id's that match then only retrieve those records.
It is possible to do obviously if you use an if statement or a ?: expression, as an example below.
var dbDocuments = new List<tblSalesQuoteDocument>();
if (id < 0)
dbDocuments = dbContext.tblSalesQuoteDocuments.ToList();
else
dbDocuments = dbContext.tblSalesQuoteDocuments.Where(x => x.HeaderId == id).ToList();
But I find this pretty ugly because if you want all records your URL is basically Documents/Index/-1 or any value less than 0.
Is there a better way?
Why I want one ActionResult is because I do a lot of filtering and code specific stuff in it. I could use two methods, 1 for all records, and another for specific records.
So should I do it as my question above or just use two methods and abstract all my filtering and other code away in Helper Methods to reduce code duplication?
You could add your filter expression on demand. Example:
ActionResult MyAction(int? id = null)
{
// ...
IQueryable<QuoteDocuments> docs = dbContext.tblSalesQuoteDocuments;
if (id != null)
{
docs = docs.Where(x => x.HeaderId == id.Value);
}
var list = docs.ToList();
// ...
}
docs = dbContext.tblSalesQuoteDocuments.Any(x => x.HeaderId == id)? dbContext.tblSalesQuoteDocuments.Where(x => x.HeaderId == id) : dbContext.tblSalesQuoteDocuments.ToList();
You should rather use
if(dbContext.tblSalesQuoteDocuments.Any(x => x.HeaderId == id)){
...
}

Parameterized Linq Query -Is there a better performance option?

I am using the following parameterized linq query expression to query from approximately 100,000 records in a SQL server. Is there a better way?
public IList<Article> GetArticles(string language, string category, string subcategory, bool exclusives, int pageIndex, int pageSize = 200)
{
IQueryable<Article> query;
query = db.Articles.Where(t => t.IsActive && t.ArticleStatus);
if (exclusives) { query = query.Where(t => t.IsExclusive); }
if (language.ToUpper() != "ALL")
{
query = query.Where(t => t.Language.ToUpper() == language);
}
if (category.ToUpper() != "ALL")
{
query = query.Where(t => t.Category.ToUpper() == category);
}
if (subcategory.ToUpper() != "ALL")
{
query = query.Where(t => t.SubCategory.ToUpper() == subcategory);
}
query = query.Where(t => t.ArticleDate <= DateTime.Now);
query = query.OrderByDescending(t => t.ArticleNo).Skip((pageIndex - 1) * pageSize).Take(pageSize);
if (query.Any() == false)
{
return null;
}
else
{
return query.ToList();
}
}
Entity Framework isn't known for being the fastest option at runtime--it's supposed to be maintainable, easy, and fast to write. If you want something fast, go with a different ORM that lets you write T-SQL manually (or use EF's Database.SqlQuery<T>(string) methods).
That said, you're actually running your query twice.
if (query.Any() == false)
{
return null;
}
else
{
return query.ToList();
}
Each of these methods will execute a varied form of your query against the database.
Instead, test out performance with only calling with once.
var returnList = query.ToList();
if (returnList.Count == 0)
{
return null;
}
else
{
return returnList;
}
The improvement here should be evident, but it's possible that statistics will be messed up on your database, so definitely test.
Aside from that, I would just check indexes. Entity Framework builds its queries in sometimes-unusual ways, so I like to run the query plan explorer (or SQL Profiler) against them.
Simplest way to do that is to put a breakpoint at your ToList() call for a few inputs, hover or use the Watch windows to get query.ToString(), paste that in something that can get query plans (like VS's database tooling or SSMS), and see if it has anything obvious.
It's sometimes the case that you an rearrange calls to EF to improve the query, but generally the case that you'll want to reassess indexes server-side to meet what EF is giving you.
But, again, remember to do that for varied inputs--you're changing your query, so you want to make sure you've got all your bases covered.

Poorly performing query needs rewriting

I have a piece of code that's performing badly, and need to rewite it to introduce a proper where clause before starting the .ToList however, that's where I'm getting stuck.
Currently the code looks lke this (roughly, I've taken some of the search criteria out to make it easier to display)
var Widgets = from b in _caspEntities.Widgets.Include("WidgetRegionLogs")
.Include("WidgetStatusLogs").Include("WidgetVoltageTests")
select b;
IEnumerable<Widget> results = Widgets.ToList();
if (comboBoxRegion.SelectedValue.ToString() != "0")
{
results = from b in results
where b.CurrentRegionLog != null && b.CurrentRegionLog.RegionId == int.Parse(comboBoxRegion.SelectedValue.ToString())
select b;
}
if (comboBoxStatus.SelectedValue != null)
{
results = from b in results
where b.CurrentStatusLog != null && b.CurrentStatusLog.StatusId == comboBoxStatus.SelectedValue.ToString()
select b;
}
if (txtCode.Text.Trim().Length > 0)
{
results = from b in results
where b.CodeNumber == txtCode.Text.Trim()
select b;
}
dataGridViewWidget.DataSource = results.ToList();
I can write the SQL easily enough, essentially the model is simple, I have a Widget it has a RegionLog and a StatusLog, both of which store a history. The current region and status are retrieved from this by grouping by WidgetID and selecting the most recent Date Updated (and then going off to Region and Status tables to get the actual value).
So, I need to translate this into LINQ, but to be honest I don't have a clue but am ken and willing to learn. In my head, I think I need to add some better where clauses, and then do the Widget.toList after I have applied the where clauses. I'm struggling with the CurrentRegionLog and CurrentStatusLog concepts as they are not populated until I run the IEnumerable.
If anyone can give some pointers, I'd be grateful,
Thanks
Edit - Added
public BatteryRegionLog CurrentRegionLog
{
get { return _currentRegionLog; }
}
private BatteryRegionLog _currentRegionLog
{
get
{
if (this.BatteryRegionLogs.Count > 0)
{
BatteryRegionLog log = this.BatteryRegionLogs.OrderByDescending(item => item.LastModifiedDate).First();
return log;
}
else
{
return null;
}
}
}
You can compose the query like this:
if (comboBoxRegion.SelectedValue.ToString() != "0")
{
var id = int.Parse(comboBoxRegion.SelectedValue.ToString()
Widgets = from b in Widgets
let currentRegionLog =
b.BatteryRegionLogs
.OrderByDescending(item => item.LastModifiedDate)
.FirstOrDefault()
where currentRegionLog.RegionId == id)
select b;
}
... // Same for the other criteria.
dataGridViewWidget.DataSource = Widgets.ToList();
The whole query is not executed before you do ToList(). As everything is translated to SQL you don't need the null check b.CurrentRegionLog != null. SQL will evaluate b.CurrentRegionLog.RegionId == id just fine when there is no CurrentRegionLog.
Edit
Since CurrentRegionLog is a calculated property of your Widget class it cannot be translated into SQL. I made an effort to incorporate the code of calculated property into the query in a way that only the basic navigation property is used, so EF can translate it to SQL again.
try remove this line:
IEnumerable<Widget> results = Widgets.ToList();
and just use the Widgets variable you get in at the top
The .ToList() goes to the database and materialiaze all the data into entities.
if you don't call the .ToList() the query is still "open" for a where clause

LINQ lambda expression append OR statement

If I want to append a AND statement to my query, I can do:
query = query.Where(obj=>obj.Id == id);
if(name.HasValue)
query = query.Where(obj=>obj.Name == name);
and it will give me:
query.Where(obj=>obj.Id == id && obj.Name == name)
How can I append a OR statement that will result in:
query.Where(obj=>obj.Id == id || obj.Name == name)
You can't do it natively. However, you can use PredicateBuilder to compose the query before you run it, and it supports ORs.
var predicate = PredicateBuilder.False<Product>();
predicate = predicate.Or (obj=>obj.Id == id);
if(name.HasValue) predicate = predicate.Or (obj=>obj.Name == name);
return query.Where(predicate);
Simply this if I'm not missing something:
query.Where(obj=>obj.Id == id || (obj.Name == name && name.HasValue))
You might want to read this question (my question...) and answer for more complicated scenarios:
How to filter IEnumerable based on an entity input parameter
I would just build this into a single condition:
if (name.HasValue)
query = query.Where(obj=>obj.Id == id && obj.Name == name);
else
query = query.Where(obj=>obj.Id == id);
I would use gdoron's solution, but if it seems unpractical for larger sets of queries, a slightly more complicated solution containing set operations might help you:
var queryById = query.Where(obj => obj.Id == id);
var queryByName = query.Where(obj => obj.Name == name);
query = queryById.Union(queryByName);
It gets much more difficult if your original query contains duplicate items.
Another way may be using Expression to formulate your queries. You can modify the expression tree before executing it, so you can add more conditions to the Where sub-tree. That is a whole lot of work and it's an overkill on 99.9% (rough estimate :) ) of cases.
Part of the problem is that you over write your original query. Your OR operation would be equivalent to:
subquery1 = query.Where(obj=>obj.Id == id);
subquery2 = query.Where(obj=>obj.Name == name);
query = subquery1.Concat(subquery2).Distinct();
As you can see, that becomes pretty unwieldy as well, however if You are going to do this form, then you must maintain the original sequence so that you can get both the right and left sides of the equation processed then you need to be sure duplicates get removed.
Instead of all that, I would try to figure a way to build up that conditional statement dynamically using lambdas e.g.
I haven't actually run this, but something like this should work.
var cond = obj=>obj.Id == id;
...
// need an or
cond = obj=>cond(obj) || obj.Name == name;
query = query.Where(obj=>cond(obj));
Hope this gives you an idea.

get the names starts with numbers or special characters in linq to sql

I need to get the list of names that starts with special characters or numbers in the linq to sql query for my asp.net mvc(C#) application.
I have tried like this (Which may not be efficient):
public List<User> GetUsersStartingWithNonCharacter()
{
List<User> _dbUsers = this.GetAllUsers();
return _dbUsers.Where(p => ((p.FirstName != null && p.FirstName != string.Empty && !char.IsLetter(p.FirstName.ToLower()[0])) || (p.FirstName == null || p.FirstName == string.Empty))).ToList();
}
public List<Users> GetAllUsers()
{
return (from u in _context.pu_Users
where !u.Is_Deleted
select new User
{
UserId = u.User_Id,
Email = u.Email_Address,
FirstName = u.First_Name,
LastName = u.Last_Name
}).ToList();
}
Can anyone suggest the most efficient way to do this in linq to sql?
How do you know if it isn't already efficient? Use somekind of profiler tool, like SQL Server Profiler if you're using MSSQL, that way you can trace your call against the database and see the actual SQL. Of course you can only debug the code to see the generated SQL but it's easier with a profiler tool and you'll see how long time the query takes.
EDIT: I see one part in how you can make it more efficient:
public List<User> GetUsersStartingWithNonCharacter()
{
List<User> _dbUsers = this.GetAllUsers();
return _dbUsers.Where(p => ((p.FirstName != null && p.FirstName != string.Empty && !char.IsLetter(p.FirstName.ToLower()[0])) || (p.FirstName == null || p.FirstName == string.Empty))).ToList();
}
public IQueryable<Users> GetAllUsers()
{
return from u in _context.pu_Users
where !u.Is_Deleted
select new User
{
UserId = u.User_Id,
Email = u.Email_Address,
FirstName = u.First_Name,
LastName = u.Last_Name
};
}
Changing your GetAllUsersto return IQueryable will delay the query to execute until you've applied your filters. This might affect some other aspects of your design but you should consider it since that change might make your where clause run in the database instead of in the code which will result in less data traffic between your application and database. Again, use a profiler to see the difference :).
I'll use Regular Expression in this scenerio
Here is my sample code
return _dbUsers.Where(p=>p.FirstName!=String.Empty)
. Where(p => Regex.Match(p.Firstname[0].ToString(), "[a-zA-Z]").Success).ToList();
I suspect all rows will be retrieved and filted in your application due to the condition:
char.IsLetter(p.FirstName.ToLower()[0])
(Using a regular expression like suggested in another answer will also pull in all rows, and filter them on the client.)
It is possible to check characters in a string with the PATINDEX function, but it's seems only to be available for LINQ via the Entity framework.
You could write a stored procedure using PATINDEX directly to check for the first character to optimize your query. Sample queries can be found at http://www.databasejournal.com/features/mssql/article.php/3071531/Using-SQL-Servers-CHARINDEX-and-PATINDEX.htm.
Sometimes LINQ to whatever will not yield the most optimized solution, but that's just life. In most cases it will give clearer code, but special cases might require work arounds in order to use special operators of the underlying system.

Categories

Resources