convert this linq expression with loop(s) - c#

I'm trying to debug code that a fellow developer wrote and LINQ expressions are making the task painful. I don't know how to debug around complicated LINQ expressions, so can anyone tell me what the equivalent code is without them?
instanceIdList.AddRange(
strname.Instances
.Where(z => instancehealthList.Find(y => y.InstanceId == z.InstanceId
&& y.State == "InService") != null)
.Select(x => x.InstanceId)
.ToList()
.Select(instanceid => new ServerObj(servertype, instanceid))
);
Also is this well written? In general is this sort of LINQ encouraged or frowned upon?

Refactoring the query using loops would look something like this:
var serverObjList = new List<ServerObj>();
foreach (var inst in strname.Instances)
{
foreach (var health in instancehealthList)
{
if (inst.InstanceID == health.InstanceID && health.State == "InService")
{
serverObjList.Add(new ServerObj(servertype, health.InstanceID));
break;
}
}
}

Rather than rewrite it to a series of foreach loops, you could eagerly-execute the expression after each operation, allowing you to inspect the data-set at intermediate steps, like so:
List<var> soFar = strname.Instances.Where(z => instancehealthList.Find(y => y.InstanceId == z.InstanceId && y.State == "InService") != null).ToList();
List<Int64> soFar2 = soFar.Select( x => x.InstanceId ).ToList();
List<ServerObj> soFar3 = soFar2.Select( instanceId => new ServerObj(servertype, instanceid) ).ToList();
instanceIdList.AddRange( soFar3 );
Of course, I feel this Linq isn't well-written.

Related

Add a if statement condition to a Linq statement

I am wanting a nice clean approach to adding in a conditional to an existing linq statement . I have a NEW variable personContactRoleId which will be zero or not zero.
context.PersonContactRoles
.Where(pcr => pcr.PersonId == personId);
.ToList()
.ForEach(pcr =>
{
// a lot of code
}
I was able to solve it with the iqueryable deferment of execution using an if statement, but it seems rather ugly and possibly less performant? Anything better to use?
var query = context.PersonContactRoles
.Where(pcr => pcr.PersonId == personId);
if(personContactRoleId != 0)
{
query = query.Where(b => b.PersonContactRoleId == personContactRoleId);
}
var results = query.ToList();
results.ForEach(pcr =>
{
// a lot of code
}
Again, this works, but looking for a cleaner way of doing this
The solution you have is fine, but if you want to put it all into one query without an if, you can add an additional Where condition that returns records where either personContactRoleId == 0 or prc.PersonContactRoleId == personContactRoleId is true:
context.PersonContactRoles
.Where(pcr =>
pcr.PersonId == personId &&
(personContactRoleId == 0 || pcr.PersonContactRoleId == personContactRoleId))
.ToList()
.ForEach(pcr =>
{
// a lot of code
});
This works without a need for an if block that checks that personContactRoleId is not 0
var query = context.PersonContactRoles
.Where(pcr => pcr.PersonId == personId).Where(b => {
return b.PersonContactRoleId == personContactRoleId && personContactRoleId != 0
}).ToList().ForEach(pcr =>
{
// a lot of code
}

Get Method is null off IQueryable (Entity Framework)

I'm trying to pass lambda expressions and a type to my DAL. I have this statement:
(entities).GetType().GetMethod("Where")
"entities" is the Table of entities on the DataContext.
When I run the statement I get a null even though Linq.Table inherits IQueryable.
Anyone have an idea?
Here is the entire method:
public object GetResultSet(Dictionary<Type, Func<object, bool>> values)
{
using (ICSDataContext db = DataContextFactory.CreateDataContext<ICSDataContext>(DataContexts.ICS))
{
foreach (var entry in values)
{
var property = db.GetType().GetProperty(entry.Key.Name + "s");
IQueryable entities = (IQueryable)property.GetValue(db, null);
var whereMethod = (entities).GetType().GetMethod("Where")
.MakeGenericMethod(Type.GetType(entry.Key.AssemblyQualifiedName));
return whereMethod.Invoke(entities, new object[] { entry.Value });
}
}
return null;
}
Thanks
As an alternative you could do something like
db.Set<Type>()
which will return you the DBSet of the appropriate type, with Where accessible without reflection. Also you may want to use Expression> rather than Func, expressions work on queryables where as funcs work on enumerables. If you pass a func into a Where clause it pulls the entire dbset down and processes it in memory.
Typed expressions are also a little easier to work with (intellesence, type checking).
Expression<Func<User,bool>> filter = c=>c.FirstName == "Bob";
As another alternative you can look into System.Linq.Dynamic, ScottGu has a write up on it here. The article and the code are old, but it works with EF 6. It allows things like
.Where("CategoryId=2 and UnitPrice>3")
From answer by LukeH under here:
var where1 = typeof(Queryable).GetMethods()
.Where(x => x.Name == "Where")
.Select(x => new { M = x, P = x.GetParameters() })
.Where(x => x.P.Length == 2
&& x.P[0].ParameterType.IsGenericType
&& x.P[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& x.P[1].ParameterType.IsGenericType
&& x.P[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))
.Select(x => new { x.M, A = x.P[1].ParameterType.GetGenericArguments() })
.Where(x => x.A[0].IsGenericType
&& x.A[0].GetGenericTypeDefinition() == typeof(Func<,>))
.Select(x => new { x.M, A = x.A[0].GetGenericArguments() })
.Where(x => x.A[0].IsGenericParameter
&& x.A[1] == typeof(bool))
.Select(x => x.M)
.SingleOrDefault();
Then this:
var gmi = where1.MakeGenericMethod(typeof(T));

C# predicate list passed to Linq Where clause

I have a long Linq Where clause that I would like to populate with a predicate list.
List<Expression<Func<Note, bool>>> filters = new List<Expression<Func<Note, bool>>>();
filters.Add(p => p.Title != null && p.Title.ToLower().Contains(searchString));
filters.Add(p => p.Notes != null && p.Notes.ToLower().Contains(searchString));
filters.Add(GlobalSearchUser((List < User > users = new List<User>() { p.user1, p.user2, p.user3, p.user4 }), searchString));
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(filters.ToArray()).Take(10).ToList();
However I'm getting this error:
cannot convert from 'System.Linq.Expressions.Expression<System.Func<project.Contracts.DTOs.Note,bool>>[]' to 'System.Func<project.Contracts.DTOs.Note,bool>'
Which is an error on the .where clause. Pulling out the .where compiles just fine.
I think great answer from Hogan can be simplified and shorten a bit by use of Any and All Linq methods.
To get items that fulfill all the conditions:
var resultAll = listOfItems.Where(p => filters.All(f => f(p)));
And to get the items that fulfill any condition:
var resultAny = listOfItems.Where(p => filters.Any(f => f(p)));
There are at least two errors in your code:
List<Expression<Func<Note, bool>>> filters = new List<Expression<Func<Note, bool>>>();
change it to
List<Func<Note, bool>> filters = new List<Func<Note, bool>>();
You don't need Expression trees here. You are using IEnumerable<>, not IQueryable<>
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(filters.ToArray()).Take(10).ToList();
There .Where() accepts a single predicate at a time. You could:
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(x => filters.All(x)).Take(10).ToList();
or various other solutions, like:
var notesEnu = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.AsEnumerable();
foreach (var filter in filters)
{
notesEmu = notesEmu.Where(filter);
}
notes = notesEnu.Take(10).ToList();
Because all the .Where() conditions are implicitly in &&.
You have to loop over your filters and run a test on each one.
You can do it with linq like this to return true if any of your filters are true:
.Where(p => { foreach(f in filters) if (f(p) == true) return(true); return(false)})
or like this to to return true if all of your filters are true:
.Where(p => { foreach(f in filters) if (f(p) == false) return(false); return(true)})
You can't just pass an array of predicates to the where method. You need to either iterate over the array and keep calling Where() for each expression in the array, or find a way to merge them all together into one expression and use that. You'll want to use LinqKit if you go the second route.

Need to optimize LINQ code using Nhibernate

I 'm new to NHibernate & LINQ. I have a piece of code which I think can be optimized. Please help me to do so.
foreach (var geography in geographyList.OrderBy(x => x.Name))
{
var introductionDateDetail = environment.IntroductionDateInfo
.IntroductionDateDetails
.OrderByDescending(x => x.ApplicationDate)
.FirstOrDefault(x => x.Geography.Id == geography.Id &&
x.VaccineDetail.Id == vaccineDetail.Id &&
x.MasterForecastInfo.Id == masterforecast.Id &&
x.ViewInfo.Id == viewInfoDetail.ViewInfo.Id);
}
The for loop may iterate to about thousand records.And hence the LINQ statement is also executed that many times. Can we write a piece of code where we can execute the LINQ statement just once?
You can try something like this:
var geographiesId = geographyList.Select(g => g.Id);
var introductionDetails = environment.IntroductionDateInfo
.IntroductionDateDetails
.OrderByDescending(x => x.ApplicationDate)
.FirstOrDefault(x => geographiesId.Contains(x.Geography.Id) &&
x.VaccineDetail.Id == vaccineDetail.Id &&
x.MasterForecastInfo.Id == masterforecast.Id &&
x.ViewInfo.Id == viewInfoDetail

Combining these two expression into a single LINQ expression

I am using the HTMLAgilityPack to parse through some html. I am getting the result set that I am expecting when using and xpath query combined with a linq query. Is there a way that I could combine them both into a single LINQ query?
var test = doc.DocumentNode.SelectNodes("//div[#class='product']");
foreach (var item in test)
{
var result = from input in item.Descendants("span")
where input.Attributes["class"].Value == "Bold"
where input.InnerHtml.ToUpper() == "GetItem"
select input;
return result;
}
If you'd want to gather all the spans together (if I'm correct in assuming that's what you want)...
I'd first convert it to a more fluent notation (I find SelectMany much easier to grasp that way - but that's just me)
(disclaimer: I'm writing this from memory, copy/pasting your code - not by VS at the moment - you'd need to check, make it write if any issues - but I think I got it ok more or less)
var test = doc.DocumentNode.SelectNodes("//div[#class='product']");
foreach(var item in test)
item.Descendants("span").Where(input => input.Attributes["class"].Value == "Bold").Where(input => input.InnerHtml.ToUpper() == "GetItem").Select(input => input);
and finally...
var allSpans = doc.DocumentNode.SelectNodes("//div[#class='product']")
.SelectMany(item => item.Descendants("span").Where(input => input.Attributes["class"].Value == "Bold").Where(input => input.InnerHtml.ToUpper() == "GetItem"));
...or along those lines
Just wanted to show you the other way to do SelectMany in Linq. This is a style choice, and many people here in SO would prefer the .SelectMany extension method, because they can see how the Monad is applied to the IEnumerable. I prefer this method since its much closer to how a functional programming model would do it.
return from product in doc.DocumentNode.SelectNodes("//div[#class='product']")
from input in product.Descendants("span")
where input.Attributes["class"].Value == "Bold"
where input.InnerHtml.ToUpper() == "GetItem"
select input;
return (
from result in
from item in doc.DocumentNode.SelectNodes("//div[#class='product']")
from input in item.Descendants("span")
where input.Attributes["class"].Value == "Bold"
where input.InnerHtml.ToUpper() == "GetItem"
select input
select result
).First();
If you really wanted it all in one query you could with something like this:
var result = doc.DocumentNode.SelectNodes("//div[#class='product']")
.SelectMany(e => e.Descendants("span")
.Where(x => x.Attributes["class"].Value == "Bold" &&
x.InnerHtml.ToUpper() == "GetItem"))
.ToList();
return result;
I would recommend spacing it out a bit though for the sake of readability, something more like this:
var result = new List<SomeType>();
var nodes = doc.DocumentNode.SelectNodes("//div[#class='product']");
nodes.SelectMany(e => e.Descendants("span")
.Where(x => x.Attributes["class"].Value == "Bold" &&
x.InnerHtml.ToUpper() == "GetItem"));
return result.ToList();
The SelectMany() method will flatten the results of the inner queries into a single IEnumerable<T>.

Categories

Resources