Linq to sql querying multiple columns - query help - c#

I have a text field on my screen where users can search for a few things from one input field:
Address
Postcode
Client name
ID
What is the best way to write a query that will query these columns?
How should I take the input in, should I split string on space? or comma?
User input = 67 pottors lane 99
where "67 pottors lane" is address
"99" is/could be ID
What I have so far:
//q = querystring
//filteredlist = extension method for the table that I am querying
//clientlist = list of clients I have passed in as a param
//Here is my query for
if (!string.IsNullOrEmpty(q))
{
var result = q.Trim();
filteredlist = filteredlist
.Where(x => x.ID.Contains(q) || x.SubjectPropertyAddress1.Contains(q)
|| x.SubjectPropertyPostCode.Contains(q)
|| clientlist.Any(y=> x.ClientID == y.ClientID && (y.ID.Contains(q) || y.Name.Contains(q)) ));
}
NOTE: I will make use of indexing using sql.

Perhaps an idea: make an extra column in your database with all the columns you want to search for as one big concattenated string. Then do a free text search of your input string versus that field in the DB.
Since L2SQL does not support free text search, you need to do it in stored procedure in that case or do like here (http://stackoverflow.com/questions/67706/linqtosql-and-full-text-search-can-it-be-done)

Ouch - is there no way for you to split the input into separate fields for ID, Address, Postcode and Name?
If so, you can keep appending Where clauses, like
var query = filteredList;
if (!String.IsNullOrEmpty(id))
{
query = query.Where(c => c.ID.Contains(id))
}
if (!String.IsNullOrEmpty(name))
{
query = query.Where(c => c.Name.Contains(name))
}
.. same for Name, address etc
Otherwise, your query resembles more of a search engine query than a RDBMS query.

I have done a very similar thing in the past. I split the search string on the space character and restricted it to a maximum of 6 search "words", so that the linq is still manageable.
I came up with something like the following:
string[] words = q.ToLower().Split(' ');
string[] wordsFixed = new string[] {"", "", "", "", "", "" };
for(int i = 0; i < 6 && i < words.Length; i++)
wordsFixed[i] = words[i];
var data = from item in list
where (item.ID + item.Name + item.Address1 + item.Address2 + item.PostCode).ToLower().Contains(wordsFixed[0]) &&
(item.ID + item.Name + item.Address1 + item.Address2 + item.PostCode).ToLower().Contains(wordsFixed[1]) &&
(item.ID + item.Name + item.Address1 + item.Address2 + item.PostCode).ToLower().Contains(wordsFixed[2]) &&
(item.ID + item.Name + item.Address1 + item.Address2 + item.PostCode).ToLower().Contains(wordsFixed[3]) &&
(item.ID + item.Name + item.Address1 + item.Address2 + item.PostCode).ToLower().Contains(wordsFixed[4]) &&
(item.ID + item.Name + item.Address1 + item.Address2 + item.PostCode).ToLower().Contains(wordsFixed[5])
select item;

Related

Avoiding twice coding, One in if Condition and One in Assignment - C#

Here is my sample code:
if (_Timing != target.Split(':')[0] + ": ")
_Timing = target.Split(':')[0] + ": ";
I check if _Timing is not equal to target.Split(':')[0] + ": " then I will assing it to _Timing. I execute this target.Split(':')[0] + ": " two times. I need to avoid it.
another example is this:
if (db.Students.FirstOrDefault(x => x.Name == "Joe") != null)
var a = db.Students.FirstOrDefault(x => x.Name == "Joe");
here again, I execute db.Students.FirstOrDefault(x => x.Name == "Joe") two times. These samples are just some examples. I need to avoid twice coding like these.
I can do this due to answers received:
var splitted= target.Split(':')[0] + ": ";
if (_Timing != splitted)
_Timing = splitted;
But I don't want to do something like this, because splitted will remain in memory. I'm looking for a way to not saving this temporary data into memory.
Is there any advice?
You can hold the reusable statement in a variable and reuse wherever needed further down in your flow control:
var splitted= target.Split(':')[0] + ": ";
if (_Timing != splitted)
_Timing = splitted;
the same goes for the linq queries :
var student = db.Students.FirstOrDefault(x => x.Name == "Joe");
if (student != null)
{
// do something here with student
}
if you want to avoid writing the linq query then you can introduce methods too:
public Student GetStudentByName(string name)
{
return db.Students.FirstOrDefault(x => x.Name == "Joe");
}
and reuse it:
var student = GetStudentByName("Joe");
if (student != null)
{
// do something here with student
}
According to your requirement you say that:
I can do this due to answers received:
var splitted= target.Split(':')[0] + ": ";
if (_Timing != splitted)
_Timing = splitted;
But I don't want to do something like this, because splitted will remain in memory. I'm looking for a way to not saving this temporary data into memory.
Is there any advice?
Yes There is one way to explicity remove variable from a memory.
You can try this to achieve that same thing and the variable splitted no longer remains in memory:
var splitted= target.Split(':')[0] + ": ";
if (_Timing != splitted)
{
_Timing = splitted;
splitted = null;
GC.Collect(); // It this stage the 'splitted' is not longer remain in the memory.
}
Remove the if block.
_Timing = target.Split(':')[0] + ": ";
Move assignment out of the if block
var student = db.Students.FirstOrDefault(x => x.Name == "Joe");
if (student != null)
{
//
}

Passing whole function as lambda expression to LINQ

I've getting auctions from my database as:
var auctions = from o in db.auctions select o;
I'd like to pass this function as lambda expression in .Where clause of my linq to filter my auctions result:
private bool wordsHasProductName(auction a, string[] words)
{
if (!a.product_name.Contains(' ')) // Auction product name is single word - check if
{
if (words.Length > 1) return false; // array is passed, single word is searching
else return a.product_name.Contains(words[0]); // check if product name consists passed string
}
else // Auction product name has more words check if passed string is partially in this product name
{
string[] productName = a.product_name.Split(' ');
var list = new List<string>(words);
foreach (string item in productName)
{
if (list.Contains(item)) list.Remove(item);
}
return list.Count == 0;
}
}
I'm calling it in my code, and while debugging there's no error at this line, here's place where I call .Where() clause in linq:
string[] words = searchName.Split(' ');
auctions = auctions.Where((a) => wordsHasProductName(a, words));
But at the end at return statement I'm getting exception error:
return View(auctions.ToPagedList(pageNumber, pageSize));
Error code:
An exception of type 'System.NotSupportedException' occurred in
EntityFramework.SqlServer.dll but was not handled in user code
Additional information: LINQ to Entities does not recognize the method
'Boolean wordsHasProductName(IEP_Projekat.Models.auction,
System.String[])' method, and this method cannot be translated into a
store expression.
How could I succeed sending full bool function as lambda expression in my linq .Where()?
As others already mentioned, method calls cannot be used in LINQ to Entities because they cannot be translated to SQL. So the only possible way would be if you can rewrite the method as compatible expression. And here comes the other problem - you are using string.Split method which is not supported.
So you are out of luck. Or may be not. Here is the trick. Instead of splitting the product_name and checking if it contains every word in words, you can use the following (IMO equivalent) criteria:
words.All(word => (" " + product_name + " ").Contains(" " + word + " "))
Enclosing both product_name and word with space allows you to match the whole word at the beginning, middle or at the end of the target string. And it uses only constructs that are supported by the EF.
Putting it all together. The function would be like this:
private static Expression<Func<auction, bool>> wordsHasProductName(string[] words)
{
if (words.Length == 1)
{
var word = words[0]; // Avoid ArrayIndex not supported
return a => !a.product_name.Contains(" ") && a.product_name.Contains(word);
}
else
{
return a => words.All(word => (" " + a.product_name + " ").Contains(" " + word + " "));
}
}
and the usage:
string[] words = searchName.Split(' ');
auctions = auctions.Where(wordsHasProductName(words));
However the above implementation does not produce very good SQL query, especially for more than one word. A much better SQL is produced if rewriting it by building the predicate expression manually:
private static Expression<Func<auction, bool>> wordsHasProductName(string[] words)
{
Expression<Func<auction, string>> product_name;
Expression condition;
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
if (words.Length == 1)
{
// a => !a.product_name.Contains(" ") && a.product_name.Contains(words[0])
product_name = a => a.product_name;
condition = Expression.AndAlso(
Expression.Not(Expression.Call(product_name.Body, containsMethod, Expression.Constant(" "))),
Expression.Call(product_name.Body, containsMethod, Expression.Constant(words[0])));
}
else
{
// a => (" " + a.product_name + " ").Contains(" " + words[0] + " ")
// && (" " + a.product_name + " ").Contains(" " + words[1] + " ")
// ...
// && (" " + a.product_name + " ").Contains(" " + words[N-1] + " ")
product_name = a => " " + a.product_name + " ";
condition = words
.Select(word => Expression.Call(product_name.Body, containsMethod, Expression.Constant(" " + word + " ")))
.Aggregate<Expression>(Expression.AndAlso);
}
return Expression.Lambda<Func<auction, bool>>(condition, product_name.Parameters);
}
Welcome to the world of EF and expressions :)
You can't pass this complex C# logic as an Expression Tree that can interpreted by the EF provider and the hardest part for the provider is transforming it into SQL statements.
So, your option is to create a stored procedure where you pass in the required parameters, then you write the logic using pure SQL. Then, you map your SP to EF and call it to return the list of objects you want.

Concat without blank in C# Linq

I am concatenating different Address fields in my LINQ Query to get one Address with merge.
public static IList GetOfferList()
{
using (var objEntity = new dbContext())
{
string[] ListCategoryID = CategoryID.Split(',');
return (from TBL.OfferMaster
select new
{
PrimaryID = OM.OfferID,
Address = OM.StreetAddress + " ," + OM.City + " ," + OM.State + " ," + OM.Country + " ," + OM.ZipCode,
}).ToList();
}
}
Currently i get fields like
Address=Fákafen 11 ,Reykjavik , ,Iceland ,108,
Or
Address: " , , , ,",;
I want
Address=Fákafen 11 ,Reykjavik ,Iceland ,108
means blank fields not required.
I would do this.
Address = string.Join(" ," (new string[] {OM.StreetAddress, OM.City, OM.State, OM.Country, OM.ZipCode})
.Where(x=> !string.IsNullOrEmpty(x)));
Use this:
var results = (from TBL.OfferMaster
select new
{
PrimaryID = OM.OfferID,
Address = String.Join(", ", (new string[] { OM.StreetAddress, OM.City, OM.State, OM.Country, OM.ZipCode })
.Where(x => !String.IsNullOrWhiteSpace(x))),
}).ToList();

How to get same functionality as ISNULL in LINQ query

I have been trying to find a solution for this since part few hours and I feel like my brain is about to help. I have the below LINQ query.
DropDownListItem item = (
from c in context.Practitioners
where c.PractitionerID == id
select new DropDownListItem
{
Id = c.PractitionerID,
DisplayValue = c.FirstName + " " + c.MiddleName + " " + c.LastName,
IsActive = c.IsActive,
DisplayOrder = c.PractitionerID,
CreatedById = new Guid("COFFEEOO-LOVE-LIFE-LOVE-C0FFEEC0FFEE"),
CreatedDate = c.CreatedDate,
}).FirstOrDefault() ?? new DropDownListItem();
response.Data = item;
There are instaces when c.MiddleName could be null. How Can I handle it in this query so that if c.MiddleName is null I can just assign a blank "" string to it ?
Here's what I tried already which did not work out.
- Created extension to check IsNullOrEmpty. I found out this does not work on LINQ queries.
- tried converting c.Middle to string by doing something like c.MiddleName.ToString() which did not work for LINQ query either.
Please give me more more direction as to which I should move toward. Thanks!
You can check for nulls and empty strings instead of using any methods that LINQ to Entities does not understand (Trim will be translated to SQL):
DisplayValue = c.FirstName + " " + ((c.MiddleName == null || c.MiddleName.Trim() == string.Empty) ? string.Empty : (c.MiddleName + " ")) + c.LastName,
I'm a bit confused about your question. You are making a string, so even if c.MiddleName is null it should be interpreted as an empty string.
You can also try this:
DisplayValue = (c.MiddleName != null) ?
c.FirstName + " " + c.MiddleName + " " + c.LastName :
c.FirstName + " " + c.LastName,
But pretty much all other answers are very similar.
As a note, you are missing brackets behind select new DropDownListItem, so that might be problem.
I'll assume what you mean by c.MiddleName is empty is that c.MiddleName is null (because if it's an empty string you question makes no sense :p).
If it is indeed the case try writing c.MiddleName ?? ""
This means that if the left part of ?? is null the use the right part of the expression
See https://msdn.microsoft.com/en-us/library/ms173224.aspx for more documentation
And you would have to change your code in this fashion :
DropDownListItem item = (
from c in context.Practitioners
where c.PractitionerID == id
select new DropDownListItem
{
Id = c.PractitionerID,
DisplayValue = (c.FirstName ?? "") + " " + (c.MiddleName ?? "") + " " + (c.LastName ?? ""),
IsActive = c.IsActive,
DisplayOrder = c.PractitionerID,
CreatedById = new Guid("COFFEEOO-LOVE-LIFE-LOVE-C0FFEEC0FFEE"),
CreatedDate = c.CreatedDate,
}).FirstOrDefault() ?? new DropDownListItem();
response.Data = item;
To be noted that the ?? operator have one of the lowest priorities in C#.
Check the operators priority in C# here https://msdn.microsoft.com/en-us/library/6a71f45d.aspx
try this
DisplayValue = (c.FirstName?? string.Empty) + " " + (c.MiddleName ?? string.Empty) + " " + (c.LastName?? string.Empty)

Build a linq query based on specific criteria c#

I have a List ListPeople "list of people named ListPeople" and the class for an object People is:
class People
{
public string Name {get;set;}
public DateTime Dob {get;set;}
public int Wieght {get;set;}
}
How could I perform a search with criteria chosen by user: Something like:
for example if the user would chose something like:
Then I would know how to set up that query:
var query = (from a in ListPeople
where a.Name == "Tom" &&
a.Weight > 25 &&
a.Dob < "dateTime.Now() - 7 months" // you know what I mean
select a).ToList();
do I have to build 4*4*4 (all posible combinations) number of queries?
You do not need to build all possible combinations ahead of time, you just need the ability to keep building upon your query. A rough draft:
var myQuery = ListPeople.AsEnumerable();
if (name.Selection == "Is")
myQuery = myQuery.Where(p => p.Name == nameInput.Text);
else if (name.Selection == /* continues */
You can continue doing this for each of your UI elements to build appropriate predicates for your query and then after you're done, evaluate it as normal.
You can do the same thing for Linq-to-SQL or EF, you just want to use AsQueryable() instead of AsEnumerable() so you can finish the query before sending it against the database.
var myQuery = context.People.AsQueryable();
// continues
In order to do this with LINQ, you'd need to pull all the data, then write separate where clauses to filter out what you need. You would pass all variables to the function as a string so that you can easily tell what is empty. This is the function you would setup:
public List<ListPeople> GetPeopleList(string Name, string opName, string Weight, string opWeight, string DOB, string opDOB)
{
var items = from a in ListPeople
select a;
//--- repeat the section below for Weight and DOB
if (!string.IsNullOrEmpty(Name))
{
switch(opName.ToLower())
{
case "contains":
{
items = items.Where(a => SqlMethods.Like(a.Name, "%" + Name + "%"));
break;
}
case "does not contain":
{
items = items.Where(a => !SqlMethods.Like(a.Name, "%" + Name + "%"));
break;
}
case "is":
{
items = items.Where(a => a.Name == Name));
break;
}
case "is not":
{
items = items.Where(a => a.Name != Name));
break;
}
}
}
//--- end repeat
return items.ToList();
}
Good Luck!
EDIT:
Since my answer here, I have found a better way to do these types of queries and it will dramatically increase the performance. Checkout http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
This class allows you to build your LINQ query dynamically in a string format and then pass it to the query. Here is an example of how I have used it in a Real Estate website on the property search function (slimmed down for ease):
public IQueryable GetSearchResults(string PriceFrom, string PriceTo, string Beds, string Baths, string SqftFrom, string SqftTo, string YearFrom, string YearTo)
{
DatabaseDataContext db = new DatabaseDataContext();
string WhereClause = string.Empty;
if (!string.IsNullOrEmpty(PriceFrom))
WhereClause = "ListPrice >= " + PriceFrom + " AND ";
if (!string.IsNullOrEmpty(PriceTo))
WhereClause += "ListPrice <= " + PriceTo + " AND ";
if (!string.IsNullOrEmpty(Beds))
WhereClause += "Beds >= " + Beds + " AND ";
if (!string.IsNullOrEmpty(Baths))
WhereClause += "FullBaths >= " + Baths + " AND ";
if (!string.IsNullOrEmpty(SqftFrom))
WhereClause += "SqFtHeated >= " + SqftFrom + " AND ";
if (!string.IsNullOrEmpty(SqftTo))
WhereClause += "SqFtHeated <= " + SqftTo + " AND ";
if (!string.IsNullOrEmpty(YearFrom))
WhereClause += "YearBuilt >= " + YearFrom + " AND ";
if (!string.IsNullOrEmpty(YearTo))
WhereClause += "YearBuilt <= " + YearTo + " AND ";
if (WhereClause.EndsWith(" AND "))
WhereClause = WhereClause.Remove(WhereClause.Length - 5);
IQueryable query = db.Listings
.Where(WhereClause)
.OrderBy("ListPrice descending");
return query;
}
Good Luck!
Have a look at predicate builder.

Categories

Resources