How many times will Linq to Objects iterate over a source? [duplicate] - c#

This question already has answers here:
What are the benefits of a Deferred Execution in LINQ?
(3 answers)
What do they mean when they say LINQ is composable?
(2 answers)
Numbers of iteration generated in a single LINQ query
(1 answer)
Closed 3 years ago.
Say I have a simple Linq query:
var x = words.Where(w => w.Length > 4).Select(w => w.ToUpper()).ToArray();
Will the compiler generate code that iterates over words once, filtering and transforming as it goes, or code that generates an intermediate enumeration and then iterates over that?
What if there's an OrderBy():
var x = words.Where(w => w.Length > 4).OrderBy(w => w).Select(w => w.ToUpper()).ToArray();
I could see the compiler either iterating once over words, filtering and uppercasing words as it goes and merging them into an already sorted IOrderedEnumerable, or I could see it generating an intermediate array, sorting that, and then transforming it.

Rewrite the query as follows to see the flow of the data.
var x = words
.Where(w =>
{
Console.WriteLine("Where: " + w);
return w.Length > 4;
})
.Select(w =>
{
Console.WriteLine("Select: " + w);
return w.ToUpper();
})
.ToArray();

Related

faster way to find persons not in list A but list B [duplicate]

This question already has answers here:
Using LINQ to remove elements from a List<T>
(14 answers)
c# Remove items in a custom list, based on another List<int>
(4 answers)
Filter List By items in Array (LINQ)
(1 answer)
Closed 1 year ago.
today I use this to get a list of persons that is not in list A but in list B. It works but seem to take a very long time to get the result. Is there a faster way that do the same?
var missingPeople = listofPersons.Where(p => allUsedPersons.All(p2 => p2.Id != p.Id)).ToList();
Your current implementation has O( n * m ) time complexity.
Where n is the cardinality of listofPersons.
Where m is the cardinality of allUsedPersons.
So if you have 500 listofPersons and 200 allUsedPersons your code will take 100,000 checks. That is bad.
This is because Linq's Where will run for every item in listofPersons, and inside your Where you have allUsedPersons.All, which will run the p2.Id != p.Id check for every item in allUsedPersons.
Instead, use a HashSet<T> to build a set of known values in O(n) time - which then lets you perform exists checks in O(1) time.
So if you have 500 listofPersons and 200 allUsedPersons my code below will take only 500 checks.
100,000 vs 500: spot the difference.
HashSet<Int32> allPeopleIds = listofPersons.Select( p => p.Id ).ToHashSet();
List<Person> missingPeople = allUsedPersons
.Where( p => !allPeopleIds.Contains( p.Id ) )
.ToList();
In relational-algebra (or is it relational-calculus?) what you're doing is known as an anti-join and Linq supports it via the Except method, however you would need to define a custom-comparator as Linq doesn't yet have an ExceptBy method (but MoreLinq does, though).
Another option is to provide a custom, reusable IEqualityComparer<Person>:
public class PersonIdComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x?.Id == y?.Id;
}
public int GetHashCode(Person obj)
{
return obj?.Id ?? int.MinValue;
}
}
You can use it for many LINQ methods. In this case you should use it for Except:
var missingPeople = listofPersons.Except(allUsedPersons, new PersonIdComparer()).ToList();
Except is quite efficient since it uses a collection similar to HashSet.
I think below method can help you for improve performance:
List<int> listOfIds = allUsedPersons.select(c => c.Id).ToList();
var missingPeople = listofPersons.Where(r=>!listOfIds.Contains(r.Id));
Putting something like allUsedPersons.All() within predicate will unnecessarily multiply the number of iterations. Prepare the list of required field(here p.id) beforehand and use it inside predicate.
Assuming allUsedPersons is list A, below would be faster
var usedPersonsId = allUsedPersons.Select(p => p.id).ToList();
var missingPeople = listofPersons.Where(p => !usedPersonsId.Contains(p.Id)).ToList();

How to dynamically add Where and Or statements to a Linq query [duplicate]

This question already has answers here:
Dynamic where clause (OR) in Linq to Entities
(2 answers)
Closed 3 years ago.
First time using c# and Linq. I have a string coming in through my route. I want to search a couple of different columns for the values in my string. Assuming I'm splitting each word on a space, foreach one of these items I want to dynamically add a .Where to my linq statement. I'm thinking I may need to dynamically add an .Or as well.
foreach (string q in query)
{
results = results.Where(u => u.Name.Contains(r));
results = results.Where(u => u.Text.Contains(r));
}
I'm used to JS where you could do something like results += results.Where(...) I'm not sure the appropriate way to structure this kind of thing using linq.
edit: here is the entire method for clarity
using (var context = new MessageContext())
{
string[] words = query.Split(" ");
var messages = (from m in context.Messages
join u in context.Users on m.UserId equals u.UserID
select new
{
m.Id,
m.Date,
m.Name,
m.Text,
m.UserId,
u.Image
});
foreach (string word in words)
{
messages = messages.Where(u => u.Name.Contains(word)).Union(messages.Where(u => u.Text.Contains(word)));
return messages.ToList();
}
Linq uses lazy evaluation (the results are not evaluated until you start to iterate over the results, or until you call a method like ToList()). As John pointed out each successive call is really just modifiying the search criteria. therefore in your example
results = results.Where(u => u.Name.Contains(r));
results = results.Where(u => u.Text.Contains(r));
is equivalent to
results = results.Where(u => u.Name.Contains(r)).Where(u => u.Text.Contains(r));
which implies a AND condition. If you want an OR condition you would need to us the Union operator.
results = results.Where(u => u.Name.Contains(r)).Union(results.Where(u => u.Text.Contains(r)));
The benefit of this lazy evaluation is that you can extract a base query and add on additional search criteria, thus simplifying your code.
I hope this helps.
foreach (string q in query)
{
results = results.Where(u => u.Name.Contains(r) || u.Text.Contains(r));
}
Or may be you need to elaborate your question a bit more

Multiple OrderBy not ordering correctly [duplicate]

This question already has answers here:
Multiple "order by" in LINQ
(7 answers)
Closed 3 years ago.
I'm trying to order by WeekId, then Order in my SQL table (end result should have workouts together by id, then ordered by the order specified), yet it is giving the wrong order. Is there something wrong with my LINQ statement?
private List<Workout> GetWorkouts(int id)
{
return new OPPDBContext().Workouts
.Where(p=>p.ClientId == id).OrderBy(p => p.Order).OrderBy(p => p.WeekId).ToList();
}
The table:
The results:
Expected results:
Lat Pulldowns
Squats
Lat Pulldowns
Squats
Reverse Lunges
That's because the second .OrderBy replaces the first .OrderBy (you are sorting by ClientId, and then effectively discard that to sort by WeekId).
You need to use .OrderBy(...).ThenBy(...) instead:
return new OPPDBContext().Workouts.Where(p=>p.ClientId == id).OrderBy(p => p.Order).ThenBy(p => p.WeekId).ToList();
OrderBy docs
ThenBy docs

Possible to "flatten" linq output into a period delimited list? [duplicate]

This question already has answers here:
How do I create a comma delimited string from an ArrayList?
(8 answers)
Closed 4 years ago.
I have a key-value pair array that contains an LDAP distinguished name, and I want to retrieve the DNS domain name of the host. (Only the DC's not the fqdn)
Assume that the LDAP parsing is done correctly, and the DC entries when combined constitute the DNS domain name of the host.
Given the code below, is it possible to convert
DC = my
DC = domain
DC = com
into
my.domain.com
I could use a for...each with a stringbuilder but it doesn't feel elegant. Is there a better way?
My code is below:
var kvList = ParseDistinguishedName(ldapName);
StringBuilder sb = new StringBuilder();
var names = (from k in kvList
where k.Key == "DC"
select k.Value);
Very easily, fortunately: string.Join does exactly what you want:
var dotSeparated = string.Join(".", names);
You might want to consider using method calls rather than a query expression for such a simple query, mind you. What you've got is precisely equivalent to:
var names = kvList.Where(k => k.Key == "DC").Select(k => k.Value);
The query expression is fine, but can end up being verbose for simple queries. (Query expressions really shine with let, join etc - anything that introduces transparent identifiers.)
Probably something like this:
kvList.Where(x => x.Key == "DC")
.Select(x => x.Value)
.Aggregate((x,y) => x + "." + y)

Error in expression [duplicate]

This question already has answers here:
Linq Convert.ToInt32 in Query
(2 answers)
Closed 4 years ago.
Can anyone help me with the following error?
LINQ to Entities does not recognize the method 'Int32
Int32(System.String)' method, and this method cannot be translated
into a store expression.
Below is my code, I am trying in several ways to fix this error, but I have not been successful:
public IEnumerable<Dia1> GetPendenciasByUser(int centroId)
{
var query = Db.Dia1S
.Join(Db.Cadastros, dia1 => dia1.PatientId, cad => cad.PatientId, (dia1, cad) => new { dia1, cad })
.Join(Db.Randomizacao, dia1 => dia1.dia1.PatientId, rand => rand.PatientId, (dia1, rand) => new { dia1, rand })
.Where(s => s.dia1.dia1.dtd1 == null ? (Convert.ToInt32(DateTime.Now - s.rand.RandomizacaoData)) > 1 : (Convert.ToInt32(Convert.ToDateTime(s.dia1.dia1.dtd1) - s.rand.RandomizacaoData)) > 1 )
.Select(s => s.dia1.dia1)
.ToList();
return query;
}
The error message is clear, LINQ doesn't know how to convert the Convert.ToInt32() function to SQL. You can either use direct casting like this:
(int)(DateTime.Now - s.rand.RandomizacaoData)
Or you'll have to execute the query and get the data, then convert it in memory using Convert.ToInt32() as you wish.

Categories