How can I store linq queries? - c#

I've researched but I haven't found anything about this situation.
I have two linq queries:
//checking all columns if there is
mockDataList = mockDataList.Where(w =>
w.Email.ToLower().Contains(search) ||
w.Gender.ToLower().Contains(search) ||
w.Name.ToLower().Contains(search) ||
w.Surname.ToLower().Contains(search) ||
w.Id.ToString().ToLower().Contains(search)
).Skip(start).Take(length).ToList();
//getting count info
var filteredTotal = mockDataList.Where(w =>
w.Email.ToLower().Contains(search) ||
w.Gender.ToLower().Contains(search) ||
w.Name.ToLower().Contains(search) ||
w.Surname.ToLower().Contains(search) ||
w.Id.ToString().ToLower().Contains(search)
).Count();
I want to count before to take 10 of them. Therefore I've had to write two queries. I don't want it to duplicate. How can I store without executing query?
*sorry for my grammar mistakes

LINQ queries are executed, when your loop through them via foreach or when you call methods like FirstOrDefault(), ToList(), ToArray(), ... So the following is no problem:
var query = mockDataList.Where(w =>
w.Email.ToLower().Contains(search) ||
w.Gender.ToLower().Contains(search) ||
w.Name.ToLower().Contains(search) ||
w.Surname.ToLower().Contains(search) ||
w.Id.ToString().ToLower().Contains(search)
); // nothing is done here, no filtering
mockDataList = query.Skip(start).Take(length).ToList(); // here, the filtering is done
var filteredTotal = query.Count(); // here, the filtering is done again

Just store LINQ query in the local variable:
var query = mockDataList.Where(w =>
w.Email.ToLower().Contains(search) ||
w.Gender.ToLower().Contains(search) ||
w.Name.ToLower().Contains(search) ||
w.Surname.ToLower().Contains(search) ||
w.Id.ToString().ToLower().Contains(search));
var filteredTotal = query.Count();
mockDataList = query.Skip(start).Take(length).ToList();

I think the better practice would be to store the IEnumerable as suggested by #SomeBody.
IEnumerable<MyClass> query = mockDataList.Where(w => .....);
List<MyClass> PaginatedFilteredItems = query.Skip(start).Take(length).ToList();
int FilteredItemsTotal = query.Count();
// Or
int PaginatedFilteredItemsTotal = PaginatedFilteredItems.Count;
Another approach whould be to use a Func or Expression to store your query:
public class MyClass{
public int Id;
public string Email;
public string Gender;
public string Name;
public string Surname;
}
Func<MyClass, bool> MyClassFunc = w =>
w.Email.ToLower().Contains(search) ||
w.Gender.ToLower().Contains(search) ||
w.Name.ToLower().Contains(search) ||
w.Surname.ToLower().Contains(search) ||
w.Id.ToString().ToLower().Contains(search);
then you could use it like this:
mockDataList = mockDataList.Where(MyClassFunc).Skip(start).Take(length).ToList();

Related

C# linq query - Where with multiple ANDs

I am trying to query using EF. The user can use up to 3 search words but they are not required. How do I write an EF query that will work as an AND for all of the search words that are used but be able to remove an AND for any search word that is empty?
Example, I want the following to return the first two elements in the array for s1='mobile', s2='', and s3='laptop'. It's not returning any. It should return the first two if s2 is changed to s2='burke'.
Example:
using System;
using System.Linq;
public class Simple {
public static void Main() {
string[] names = { "Burke laptop mobile", "laptop burke mobile", "Computer Laptop",
"Mobile", "Ahemed", "Sania",
"Kungada", "David","United","Sinshia" };
//search words
string s1 = "mobile";
string s2 = "";
string s3 = "laptop";
var query = from s in names
where (!string.IsNullOrEmpty(s1) && s.ToLower().Contains(s1))
&& (!string.IsNullOrEmpty(s2) && s.ToLower().Contains(s2))
&& (!string.IsNullOrEmpty(s3) && s.ToLower().Contains(s3))
orderby s
select s.ToUpper();
foreach (string item in query)
Console.WriteLine(item);
}
}
Instead of trying to write a single where statement, you can optionally extend an existing IQueryable<T> (or IEnumerable<T> like our code sample would be using) by chaining Where statements.
var query = names;
if (!string.IsNullOrEmpty(s1))
{
query = query.Where(x => x.Contains(s1));
}
if (!string.IsNullOrEmpty(s2))
{
query = query.Where(x => x.Contains(s2));
}
// ... (or make a foreach loop if s1,s2,s3 were an array.
var results = query.OrderBy(x => x).Select(x => x.ToUpper());
Chaining Where like this is equivalent to "anding" all where predicates together.
EDIT:
To update why your specific implementation doesn't work is because your && operators are incorrect for the given use-case.
(string.IsNullOrEmpty(s1) || s.ToLower().Contains(s1)) &&
(string.IsNullOrEmpty(s2) || s.ToLower().Contains(s2)) &&
(string.IsNullOrEmpty(s3) || s.ToLower().Contains(s3))
Remember that && requires both left and right statements to be true, so in your case !string.IsNullOrEmpty(s2) && s.ToLower().Contains(s2) this is saying that s2 must always be not-empty/null.
Please consider this:
using System;
using System.Linq;
public class Simple {
public static void Main() {
string[] names = { "Burke laptop mobile", "laptop burke mobile", "Computer Laptop",
"Mobile", "Ahemed", "Sania",
"Kungada", "David","United","Sinshia" };
//search words
string s1 = "mobile";
string s2 = "";
string s3 = "laptop";
var query = from s in names
where (s1 != null && s.ToLower().Contains(s1))
&& (s2 != null && s.ToLower().Contains(s2))
&& (s3 != null && s.ToLower().Contains(s3))
orderby s
select s.ToUpper();
foreach (string item in query)
Console.WriteLine(item);
}
}
If you use an array or list instead of multiple strings, you could do something like
List<string> searchWords = new List<string>
{
"mobile",
"",
"laptop"
};
var query = names
.Where(n => searchWords
.Where(s => !string.IsNullOrEmpty(s))
.All(s => n.ToLower().Contains(s)))
.Select(n => n.ToUpper())
.OrderBy(n => n);
This also is more flexible, as you can have any number of search words, without changing the query.

How to handle Null array in Linq

I have a methods that takes multiple arrays and filter data using linq, the problem I am having is that one or more arrays could be null and I am not sure how to handle null arrays in Linq.:
public IPagedList<SGProduct> GetFilteredProducts(string catID, string[] houseID, int page = 1)
{
return MongoContext.Products.AsQueryable<SGProduct>().AsEnumerable().Where(x =>houseID.Contains(x.HouseID.ToString()) && x.ProductCategoryID.ToString() == catID).ToList().ToPagedList(page, 6);
}
The houseID array could be null also I will have more parameters to this method like Size array etc.
Any suggestions.
Thanks
Just add null check for array, end create empty one if it's null.
public IPagedList<SGProduct> GetFilteredProducts(string catID, string[] houseID, int page = 1)
{
if(houseID == null) houseID = new string[] {};
return MongoContext.Products.AsQueryable<SGProduct>().AsEnumerable().Where(x =>houseID.Contains(x.HouseID.ToString()) && x.ProductCategoryID.ToString() == catID).ToList().ToPagedList(page, 6);
}
Also, you can query variable and build it with conditions as you go. And then you have finished, just materialize and return records.
public IPagedList<SGProduct> GetFilteredProducts(string catID, string[] houseID, int page = 1)
{
var query = MongoContext.Products.AsQueryable<SGProduct>();
if(houseID == null)
query = query.Where(x =>houseID.Contains(x.HouseID.ToString()) && x.ProductCategoryID.ToString() == catID);
return query.ToList().ToPagedList(page, 6);
}

The LINQ expression node type 'ArrayLength' is not supported in LINQ to Entities

I have implemented a linq expression to return a resultset and getting the following error
{"The LINQ expression node type 'ArrayLength' is not supported in LINQ to Entities."}
public IEnumerable<TBI.JV.Business.Objects.Asset> GetAssetsBasicBySedols(string[] sedols)
{
var priceDate = DateTime.UtcNow.Date.AddMonths(-8);
var typeList = new string[]
{
"UNIT TRUST",
"OEIC",
"INVESTMENT TRUST",
"INVESTMENT COMPANY",
"PENSION FUND",
"INSURANCE BOND",
"LISTED EQUITY",
"PREFERENCE SHARE",
"ZERO DIVIDEND PREF",
"GILT (CONVENTIONAL)",
"GILT (INDEX LINKED)",
"AIM",
"VCT",
"OFFSHORE FUND",
"ETP"
};
using (var dealingContext = new dbDealingContainer())
{
return (from fundprice in dealingContext.FundPrices
where (fundprice.FUND_STATUS == "ACTIVE" || fundprice.FUND_STATUS == "SUSPENDED") &&
(fundprice.INVNAME != null || fundprice.INVNAME != "") &&
!fundprice.INVNAME.StartsWith("IFSL Bestinvest") &&
// fundprice.WaterlooTradable == true &&
fundprice.BID_MID_PRICE > 0 && typeList.Contains(fundprice.FUND_TYPE)
&& ((sedols.Length > 0 && sedols.Contains(fundprice.SEDOL_NUMBER))
||sedols.Contains(fundprice.SEDOL_NUMBER_ACC)) || sedols.Length == 0
select new TBI.JV.Business.Objects.Asset
{
AssetName = fundprice.INVNAME,
AssetId = fundprice.Id,
AssetType = fundprice.FUND_TYPE,
Epic = fundprice.INVESTMENT_CODENAME,
StarRating = fundprice.STARLEN,
Sedol = fundprice.SEDOL_NUMBER,
SedolAcc = fundprice.SEDOL_NUMBER_ACC
}).ToList();
}
}
The error is thrown at the following line of code sedols.Length > 0 and also sedols.Length == 0. How do I resolve this. My method should be able to take an empty string array as input and return all records.
Define two variables above the query and than use them instead in the query:
var isGreaterThanZero = sedols.Length > 0;
var isEmpty = sedols.Length == 0;
The Any() extension method is supported by LINQ to Entities.
Using exampleArray.Any() and !exampleArray.Any() means you don't have to declare local variables and fortunately it's a nice, succinct syntax.

Search in a List of List Object in C#

I am having a List of List object in C#. I would like to search this list using ´findall´ method of LINQ.
Here is the code I am using :
String searchString = "Keyword";
List<IntVector> newList = UserData.FindAll(s =>
s.ClientName.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0 ||
s.CustomerID.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0 ||
s.AddInfo.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0 ||
s.MobileNo.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
However, I am not able to search the nested list. One of the member of ´UserData´ is ´CustomerInfo´ which itself is a list of String.
Any idea how to do it ?
Thanks
First, instead of repeating same code many times, I'd create extension method
public static bool IgnoreCaseContains(this string s, string value)
{
return s.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;
}
Now use Where and Any operators:
var query = from u in UserData
where u.ClientName.IgnoreCaseContains(searchString) ||
u.CustomerID.IgnoreCaseContains(searchString) ||
u.AddInfo.IgnoreCaseContains(searchString) ||
u.MobileNo.IgnoreCaseContains(searchString) ||
u.CustomerInfo.Any(i => i.IgnoreCaseContains(searchString))
select u;
BTW FindAll returns list, so you don't need to create copy of that list with ToList call.
One step further, you can move all this complex search into user search specification, or even in another extension method:
public static bool Matches(this IntVector user, string searchString)
{
return user.ClientName.IgnoreCaseContains(searchString) ||
user.CustomerID.IgnoreCaseContains(searchString) ||
user.AddInfo.IgnoreCaseContains(searchString) ||
user.MobileNo.IgnoreCaseContains(searchString) ||
user.CustomerInfo.Any(i => i.IgnoreCaseContains(searchString))
}
In this case your code will look like
List<IntVector> newList = UserData.FindAll(u => u.Matches(searchString));
Or
var spec = new UserSearchSpecification("keyword");
List<IntVector> newList = UserData.FindAll(u => spec.IsSatisfiedBy(u));
You can use Any()extension method to see if any of the string in list of strings matches your keyword-
List<IntVector> newList = UserData.FindAll(s =>
s.ClientName.IndexOf(searchString,
StringComparison.OrdinalIgnoreCase) >= 0 ||
s.CustomerID.IndexOf(searchString,
StringComparison.OrdinalIgnoreCase) >= 0 ||
s.AddInfo.IndexOf(searchString,
StringComparison.OrdinalIgnoreCase) >= 0 ||
s.MobileNo.IndexOf(searchString,
StringComparison.OrdinalIgnoreCase) >= 0 ||
s.CustomerInfo.Any(t => t.IndexOf(searchString,
StringComparison.OrdinalIgnoreCase)) >= 0);

Wildcard search in using Lambda in EF

I have a search with optional arguments:
string id1 = HttpContext.Current.Request["id1"];
string id2 = HttpContext.Current.Request["id2"];
List<Foo> list = context.Foo.Where(l =>
(string.IsNullOrEmpty(id1) || l.Id1.Contains(id1)) &&
(string.IsNullOrEmpty(id2) || l.Id2.Contains(id2)))
.Take(10)
.ToList());
I would like to extend it so that if the string starts with a *, the EndsWith() - method should be used.
For example, if the first searchstring is *123, I want to do a l.Id1.EndsWith("123").
Is there any way to extend my current code, or should I use a different approach?
I'm quite sure if this is what you were intending, but you can break your logic up by using IQueryable(T). The query won't be executed until you try to use the collection.
public IList<Foo> Search(DbContext<Foo> context, string id1, string id2)
{
Func<Foo, bool> predicate = l =>
(string.IsNullOrEmpty(id1) || l.Id1.Contains(id1))
&& (string.IsNullOrEmpty(id2) || l.Id2.Contains(id2)));
IQueryable<Foo> list = context.Foo.Where(predicate);
if(id1.StartsWith("*") && id1.Length > 1)
{
var searchTerm = id1.Substring(1, id1.Length);
list = list.Where(l => l.EndsWith(searchTerm));
}
return list.Take(10).ToList(); // Execution occurs at this point
}
Building up the query:
public void BasicSearch(IQueryable<foo> list, string id1, string id2)
{
Func<Foo, bool> predicate = l =>
(string.IsNullOrEmpty(id1) || l.Id1.Contains(id1))
&& (string.IsNullOrEmpty(id2) || l.Id2.Contains(id2)));
list.Where(predicate);
}
public void WildcardSearch(IQueryable<Foo> list, string id1)
{
if(!id1.StartsWith("*") || id1.Length <= 1) return;
var searchTerm = id1.Substring(1, id1.Length);
list.Where(l => l.EndsWith(searchTerm));
}
IQueryable<Foo> list = context.Foo;
BasicSearch(list, id1, id2);
WildcardSearch(list, id1);
var result = list.Take(10); // Execution occurs at this point

Categories