LINQ: Build a where clause at runtime to include ORs ( || )? - linq

I need to build a where clause at runtime but I need to do an OR with the where clause. Is this possible?
Here is my code. Basically "filter" is a enum Bitwise, son hence filter could be equal to more than 1 of the following. Hence I need to build up the where clause.
If I execute the WHEREs separately then imagine if I do the Untested first, and it returns 0 records that means I can't execute a where on the Tested because its now 0 records.
I will put some pseudo-code below:
string myWhere = "";
if ((filter & Filters.Tested) == Filters.Tested)
{
if (myWhere != "" ) myWhere =myWhere + "||";
myWhere = myWhere " Status == "Tested";
}
if ((filter & Filters.Untested) == Filters.Untested)
{
if (myWhere != "" ) myWhere =myWhere + "||";
myWhere = myWhere " Status == "Untested";
}
if ((filter & Filters.Failed) == Filters.Failed)
{
if (myWhere != "" ) myWhere =myWhere + "||";
myWhere = myWhere " Status == "Failed";
}
// dataApplications = a List of items that include Tested,Failed and Untested.
// dataApplication.Where ( myWhere) --- Confused here!
Is this possible?
I don't want to include lots of "IFs" because there are lots of combinations i.e. no filter, filter= tested Only, filter = Untested and Tested ... and lots more.

If you have this:
IEnumerable<MyType> res = from p in myquery select p;
You can define a
var conditions = new List<Func<MyType, bool>>();
conditions.Add(p => p.PropertyOne == 1);
conditions.Add(p => p.PropertyTwo == 2);
res = res.Where(p => conditions.Any(q => q(p)));
And now the trick to make Lists of Funcs of anonymous objects (and you can easily change it to "extract" the type of anonymous objects)
static List<Func<T, bool>> MakeList<T>(IEnumerable<T> elements)
{
return new List<Func<T, bool>>();
}
You call it by passing the result of a LINQ query. So
var res = from p in elements select new { Id = p.Id, Val = p.Value };
var conditions = MakeList(res);

var statusTexts = new List<string>(); // Add desired status texts
dataApplication.Where(item =>
statusTexts.Any(status => item.Status == status))

Use HashSet<> for statuses, then .Contains will be O(1) instead of usual O(n) for List<>:
var statuses = new HashSet<string>() {"a", "b", "c"};
var list = new[] {
new { Id = 1, status = "a"},
new { Id = 2, status = "b"},
new { Id = 3, status = "z"}
};
var filtered = list.Where(l => statuses.Contains(s => l.status == s));

Related

How to optimize slow LINQ query?

I tried delete a statement that use contain() and use skip() take()
but it's still take a long time on execution
public JsonNetResult GetList(C.Lib.ListParam param, string type, DateTime? WorkDate, string MNo, string ANo, string AName, string Specifi, string PNo, string SName, string PType, string OrNo)
{
var idObj = (mis.Models.Security.CIdentity)Csla.ApplicationContext.User.Identity;
var c = new mis.Models.CEntities();
var targetList = (from obj in c.WorkOrders
join obj2 in c.Partners on obj.PartnerId equals obj2.PartnerId
join obj3 in c.Assets on obj.AssetsId equals obj3.AssetsId
join obj4 in c.WorkOrderItems on obj.WorkOrderId equals obj4.WorkOrderId
join obj5 in c.WorkItems on obj4.WorkId equals obj5.WorkId
join obj6 in c.Products on obj3.AssetsId equals obj6.AssetsId
where true && obj.TenantId == idObj.TenantId && obj2.Customer == true && obj.WorkOrderStatus <= 1
select new
{
WorkProgressId = obj.WorkOrderId,
OrderNo = obj.InOrderItem.OrderItem.Order.OrderNo,
ProductType = obj5.ProductType,
WorkOrderId = obj.WorkOrderId,
MakeQty = obj.MakeQty,//delete some column
PrepaidDate = obj.PrepaidDate,
CompleteDate = (from subobj in c.WorkOrderItems
where subobj.WorkOrderId == obj.WorkOrderId && subobj.CompleteDate != null
orderby subobj.CompleteDate descending, subobj.WorkItem.WorkNo descending
select subobj.CompleteDate).FirstOrDefault(),
WorkNoName = (from subobj in c.WorkOrderItems
where subobj.WorkOrderId == obj.WorkOrderId && subobj.CompleteDate != null
orderby subobj.WorkItem.WorkNo descending
select new { WorkNoName = subobj.WorkItem.WorkNo + " " + subobj.WorkItem.WorkName }).FirstOrDefault().WorkNoName,
});
targetList = targetList.GroupBy(x => x.MakeNo).Select(x => new
{
WorkProgressId = x.FirstOrDefault().WorkOrderId,
OrderNo = x.FirstOrDefault().OrderNo,
ProductType = x.FirstOrDefault().ProductType,
WorkOrderId = x.FirstOrDefault().WorkOrderId,
WorkOrderDate = x.FirstOrDefault().WorkOrderDate,
OrderQty = x.FirstOrDefault().OrderQty,
MakeQty = x.FirstOrDefault().MakeQty,
PrepaidDate = x.FirstOrDefault().PrepaidDate,//delete some column
CompleteDate = x.FirstOrDefault().CompleteDate,
WorkNoName = x.FirstOrDefault().WorkNoName
}).OrderByDescending(x => x.WorkOrderDate).ThenByDescending(x => x.WorkNoName);
param.SetCount(targetList);
targetList.OrderByDescending(x => x.WorkOrderDate).ThenByDescending(x=> x.WorkNoName);
var tk= targetList.Skip((param.Page - 1) * param.Rows).Take(param.Rows);
return JsonNetHelper.ReturnData(param,tk);
}
I'm wondering what is the best way to optimize it? Or would the query profiler do that for me?

"invalidcastexception specified cast is not valid linq" exeception in LINQ query

var Player = from PSI in regConfig.Player_SeasonalInfos
from PPI in regConfig.Player_PermanentInfos
where PSI.PaymentId == PlayerPayment.PaymentId
&& PPI.PlayerId == PSI.PlayerId
select new {
PlayerIds = string.Join(",", PSI.PlayerId),
PlayerSeasonalId = PSI.PlayerSeasonalId,
CityId = PPI.CityId
};
foreach (var item in Player)
{
Player_SeasonalInfo PlayerSeasonalInfos =
(from PSI in regConfig.Player_SeasonalInfos
where PSI.PlayerSeasonalId ==item.PlayerSeasonalId
select PSI).FirstOrDefault();
PlayerSeasonalInfos.StatusId = item.CityId == 1 ? 1 : 2;
regConfig.SubmitChanges();
}
i have write this code but i am getting excepiton"invalidcastexception specified cast is not valid linq" on line"
from PSI in regConfig.Player_SeasonalInfos
where PSI.PlayerSeasonalId ==item.PlayerSeasonalId
select PSI"
please suggest.

Combining Sub Queries Into 1 Query Linq

Is there a way I can rewrite the following query to make it just one query?
try
{
var fileIds = (from f in context.SignalTo
where f.SignalFileID == 2
select new { f.GFileID }).ToList();
foreach (var id in fileIds)
{
var pp = (from p in context.ProjectFiles
where p.FileID == id.GFileID && p.ProjectID == ProjectID
select p);
if (pp != null)
{
ProjectFiles projectFile =(ProjectFiles) pp;
projectFile.MStatus = Status;
projectFile.DateLastUpdated = DateTime.Now;
context.SaveChanges();
}
}
}
You can combine the two query parts of your code into one.
You would then need to loop over the result set, making your updates. You would then call context.SaveChanges to submit all changes in one batch.
I can't tell if your existing code actually runs or compiles, but you need something like this:
Get the list of file ids you're interested in:
var fileIds = from f in context.SignalTo
where f.SignalFileID == 2
select f.GFileID;
fileIds is at this point an IQueryable where I assume T is an Int. No query has been excuted yet.
Now you can do
var pps = from p in context.ProjectFiles
where fileIds.Contains(p.FileID) && p.ProjectID == ProjectID
select p;
Still no query executed.
Then iterate over the result set
foreach( var pp in pps ) // query executed now
{
pp.MStatus = Status;
pp.DateLastUpdated = DateTime.Now;
}
context.SaveChanges(); // batch of updates executed now

using if else with LINQ Where

I want to generate dynamic query to check manage the where clause with number of parameters available...if some parameter is null i don't want to include it in the where clause
var test = from p in _db.test
where if(str1 != null){p.test == str} else i dnt wanna check p.test
I have around 14 parameters for the where clause
need help,
thanks
You can do it in steps:
// set up the "main query"
var test = from p in _db.test select _db.test;
// if str1 is not null, add a where-condition
if(str1 != null)
{
test = test.Where(p => p.test == str);
}
In addition to #Fredrik's answer, you can also use the short-circuit rules when evaluating boolean expressions like so:
var test = from p in _db.test
where str1 == null || p.test == str1;
Edit If you have lots of strings to test, (str1, str2, etc...) then you can use the following, which will be translated to an SQL IN clause:
var strings = new List<string>();
if (str1 != null) strings.Add(str1);
if (str2 != null) strings.Add(str2);
if (str3 != null) strings.Add(str3);
...
var test = from p in _db.test
where strings.Contains(p.test);
It's even easier if your strings are already in a collection (which, if you've got 14 of them, I assume they would be...)
Consider param1 and param2 are the parameters. Your query should be as under:
string param1 = "Value1";
string param2 = "Value2";
var q = from bal in context.FxBalanceDetails
where (string.IsNullOrEmpty(param1) || bal.Column1 == param1)
&& (string.IsNullOrEmpty(param2) || bal.Column2 == param2)
select bal;
This will ensure that the where clause gets applied for the particular parameter only when it is not null.
var test =
from p in _db.test
where p.str1 != null ? p.str1 : ""
select p;
Do you check the strings against the same Field of the entity?
If so you can write something like:
var strings = new[] { "foo", "bar", "ok", "", null };
var query = dataContext.YourTable.AsQueryable();
query = strings.Where(s => !string.IsNullOrEmpty(s))
.ToList()
.Aggregate(query, (q, s) => q.Where(e => e.YourField == s));
EDIT:
The previous solution is overcomplicated:
var strings = new[] { "foo", "bar", "ok", "", null }.Where(s => !string.IsNullOrEmpty(s))
.ToList();
var query = dataContext.YourTable.Where(e => strings.Contains(e.YourField));

LINQ Next Item in List

Taking a look at my question HERE, I now want to return the next recommendation object (after) the one that matches the criteria.
So say I found item 6 out of 10, I'd like the query to return item 7 instead.
Or is there a better way?
Here's my current best method:
MyList.SkipWhile(item => item.Name != "someName").Skip(1).FirstOrDefault()
An earlier answer uses Skip(1).Take(1) which works, but returns a list of one result. In my case (and perhaps the OP's case), we're looking for the actual item. So my code skips until it gets to the one we're looking for (a Where would return a subset so we wouldn't have access to the next item) then skips one more and then gets the item.
Try this one
NEXT Item
MyList.SkipWhile(x => x != value).Skip(1).FirstOrDefault();
PREVIOUS Item note:Reverse() will not work for LINQ to SQL
var MyList2 = MyList.ToList();
MyList2.Reverse();
MyList2.SkipWhile(x => x != value).Skip(1).FirstOrDefault();
Since you have a List<T> object you can use its FindIndex method instead of Where to get the index of the first matching item rather than the item itself:
int index = recommendations.FindIndex(rp =>
rp.Products.Any(p => p.Product.Code == "A")
&& rp.Products.Any(p => p.Product.Code == "B")
);
Once you have the index you can get the next item or previous item or whatever you want.
How about something like this:
public static class Extension
{
public static T Next<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
bool flag = false;
using (var enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
{
if (flag) return enumerator.Current;
if(predicate(enumerator.Current))
{
flag = true;
}
}
}
return default(T);
}
}
You could then call it like you would for Where
Products.Next(x => x.Whatever);
This should do it. I haven't constructed a test for it specifically.
var nextProducts = from item1 in recommendations.Select((rec, idx) => new { Rec = rec, Index = idx })
join item2 in recommendations.Select((rec, idx) => new { Rec = rec, Index = idx })
on item1.Index equals item2.Index - 1
where item1.Rec.Products.Any(p => p.Code == "A")
&& item1.Rec.Products.Any(p => p.Code == "B")
select item2.Rec;
If you needed both records, the select statement could be
select new { MatchingItem = item1.Rec, NextItem = item2.Rec };
But then you would have to do a grouping to account for a matching item being the last item in the list (there would not be a next item in that case).
var nextProducts = from item1 in recommendations.Select((rec, idx) => new { Rec = rec, Index = idx })
join item2 in recommendations.Select((rec, idx) => new { Rec = rec, Index = idx })
on item1.Index equals item2.Index - 1
into groupjoin
from i2 in groupjoin.DefaultIfEmpty ()
where item1.Rec.Products.Any(p => p.Code == "A")
&& item1.Rec.Products.Any(p => p.Code == "B")
select new { MatchingItem = item1.Rec, NextItem = i2 == null ? null : i2.Rec };
The code I did test was something similar with a list of strings.
List<string> list = new List<string>() { "a", "b", "c", "a", "d", "a", "e" };
var query = from item1 in list.Select((s, idx) => new { Item = s, Index = idx })
join item2 in list.Select((s, idx) => new { Item = s, Index = idx })
on item1.Index equals item2.Index - 1
where item1.Item == "a"
select item2.Item;
Which returns b, d, and e.
If you are sure that:
the item is unique in the list
the item is gotten from the same list
there is the next item
you can get the next item this way
myList[myList.IndexOf(item) + 1];
// or
myList.ElementAt(myList.IndexOf(item) + 1);
If you're not sure there is the next item you can use try + catch or:
myList.ElementAtOrDefault(myList.IndexOf(item) + 1);
Is item 7 a match to your Where clause? If so, use the Skip() and Take() extension methods:
var myProducts =
from rp in recommendations
where
cp.Products.Any(p => p.Product.Code == "A") &&
cp.Products.Any(p => p.Product.Code == "B")
select rp;
var nextProduct = myProducts.Skip(1).Take(1);

Resources