Linq - doesn't start with any prefix in range of prefixes - c#

I need to create a Linq query that would have the following logic:
IEnumerable<string> prefixes = GetListOfPrefixesFromSomewhere();
IQueryable<Record> myQuery = GetAllRecordsFromRepository();
foreach (string prefix in prefixes)
{
myQuery = myQuery.Where(x => !x.Field.StartsWith(prefix));
}
This would obviously result in a large IQueryable which can then be executed.
Is there a nice elegant way to express this is a single Linq statement?

You can at least try this:
// Only call ToList if you need to, of course... but I think EF/LINQ To SQL
// will need it as a list (or array)
List<string> prefixes = GetListOfPrefixesFromSomewhere().ToList();
IQueryable<Record> query = GetAllRecordsFromRepository()
.Where(x => !prefixes.Any(prefix => x.Field.StartsWith(prefix)));
Quite what the SQL will look like, I don't know - but I think it's logically what you want, which is usually a good start.

Related

Why do I get a different output from analogous Linq queries?

I am trying the following code in LinqPad 5 (specifically 5.26.01)
IEnumerable<string> breeds = new List<string>{
"Fantail",
"Lahore",
"Bokhara Trumpeter",
"Rhine Ringbeater",
"Birmingham Roller",
"Pomeranian Pouter",
"Racing Homer",
"Archangel"};
IEnumerable<string> GetAllBreedsContainingLetter_Fluent(IEnumerable<string> breedlist, string letter)
{
return breedlist
.Where(breedname => breedname.Contains(letter.ToUpperInvariant()) || breedname.Contains(letter.ToLowerInvariant()))
.OrderByDescending(breedname => breedname)
.Select(breedname => breedname);
}
IEnumerable<string> GetAllBreedsContainingLetter_Query(IEnumerable<string> breedlist, string letter)
{
return breedlist = from b in breedlist
where (b.Contains(letter.ToUpperInvariant()) || b.Contains(letter.ToLowerInvariant()))
orderby b descending
select b;
}
var breedsFluent = GetAllBreedsContainingLetter_Fluent(breeds, "R");
breedsFluent.Dump();
var breedsQuery = GetAllBreedsContainingLetter_Query(breeds, "R");
breedsQuery.Dump();
I think the two functions should be analogous but I noticed something odd about the output in Linqpad. The first .Dump() is identified as an IEnumerable<String>; the second .Dump() identifies as a IOrderedEnumerable<String>.
Is this something about the queries I'm running or is it an artifact of Linqpad? I haven't found anything from Googling.
In query syntax the transformation is such that when you have a trivial projection (projecting an item to itself) it only generates a Select call when that trivial projection is the only operation in the query. Since your query contains other operations, the Select is elided entirely, for performance reasons.
A proper translation of that query syntax query into method syntax would skip the Select. The way to replicate the behavior using query syntax would require something like a second query, to do the trivial projection.

C# and LINQ - arbitrary statement instead of let

Let's say I'm doing a LINQ query like this (this is LINQ to Objects, BTW):
var rows =
from t in totals
let name = Utilities.GetName(t)
orderby name
select t;
So the GetName method just calculates a display name from a Total object and is a decent use of the let keyword. But let's say I have another method, Utilities.Sum() that applies some math on a Total object and sets some properties on it. I can use let to achieve this, like so:
var rows =
from t in totals
let unused = Utilities.Sum(t)
select t;
The thing that is weird here, is that Utilities.Sum() has to return a value, even if I don't use it. Is there a way to use it inside a LINQ statement if it returns void? I obviously can't do something like this:
var rows =
from t in totals
Utilities.Sum(t)
select t;
PS - I know this is probably not good practice to call a method with side effects in a LINQ expression. Just trying to understand LINQ syntax completely.
No, there is no LINQ method that performs an Action on all of the items in the IEnumerable<T>. It was very specifically left out because the designers actively didn't want it to be in there.
Answering the question
No, but you could cheat by creating a Func which just calls the intended method and spits out a random return value, bool for example:
Func<Total, bool> dummy = (total) =>
{
Utilities.Sum(total);
return true;
};
var rows = from t in totals
let unused = dummy(t)
select t;
But this is not a good idea - it's not particularly readable.
The let statement behind the scenes
What the above query will translate to is something similar to this:
var rows = totals.Select(t => new { t, unused = dummy(t) })
.Select(x => x.t);
So another option if you want to use method-syntax instead of query-syntax, what you could do is:
var rows = totals.Select(t =>
{
Utilities.Sum(t);
return t;
});
A little better, but still abusing LINQ.
... but what you should do
But I really see no reason not to just simply loop around totals separately:
foreach (var t in totals)
Utilities.Sum(t);
You should download the "Interactive Extensions" (NuGet Ix-Main) from Microsoft's Reactive Extensions team. It has a load of useful extensions. It'll let you do this:
var rows =
from t in totals.Do(x => Utilities.Sum(x))
select t;
It's there to allow side-effects on a traversed enumerable.
Please, read my comment to the question. The simplest way to achieve such of functionality is to use query like this:
var rows = from t in totals
group t by t.name into grp
select new
{
Name = t.Key,
Sum = grp.Sum()
};
Above query returns IEnumerable object.
For further information, please see: 101 LINQ Samples

starts with over an array

I have a function like so
public List<Entry> GetEntriesForSlider(int first, int max, List<string> NameLetters)
{
//Some code
}
Inside this code, I want to search along a database, to return every result that has the firstname starting with a letter within the NameLetters.
So if I pass in the array NameLetters = ["a","b","c"]
Then it will return results such as
Amy
Bert
Aaron
Chris
It should be noted that I am ideally looking to use some sort of linq statement such as...
entries.Where(x => x.FirstName.StartsWith(partofArray));
If at all possible.
EDIT : I previously had the following :
var entries = _repository.All<Entry>().Skip(first).Take(max);
if (NameLetters != null && NameLetters.Count > 0)
entries = entries.Where(x => NameLetters.Contains(x.FirstName[0].ToString()));
But what I found was, it enumerated the query (I think) before running the where statement. Possibly because trying to access the first letter of firstname (Or the ToString).
If you're just looking to match the first letter try:
entries.Where(x => partofArray.Contains(x.FirstName[0]));
If you need to worry about null or empty strings a safer version would be:
entries.Where(x =>
!string.IsNullOrEmpty(x.FirstName) &&
partofArray.Contains(x.FirstName[0])
);
If you want to use variable-length strings try:
entries.Where(x =>
partofArray.Any( p=> x.FirstName.StartsWith(p))
);
Perhaps you should take a look at predicate builder.
Then something like
IQueryable<Entry> GetThingsThatBeginWithA(String[] prefixes)
{
var predicate = PredicateBuilder.False<Entry>();
foreach (String prefix in prefixes)
predicate = predicate.Or(p => p.FirstName.StartsWith(prefix));
return database.Entries.Where(predicate);
}
This query should correctly compile to a single query to the SQL store.
Update: It can also be done without predicate builder, but I find that working with expression trees without it is really tedious.

LINQ flavored IS IN Query

There are quite a few other questions similiar to this but none of them seem to do what I'm trying to do. I'd like pass in a list of string and query
SELECT ownerid where sysid in ('', '', '') -- i.e. List<string>
or like
var chiLst = new List<string>();
var parRec = Lnq.attlnks.Where(a => a.sysid IN chiList).Select(a => a.ownerid);
I've been playing around with a.sysid.Contains() but haven't been able to get anywhere.
Contains is the way forward:
var chiLst = new List<string>();
var parRec = Lnq.attlnks.Where(a => chiList.Contains(a.sysid))
.Select(a => a.ownerid);
Although you'd be better off with a HashSet<string> instead of a list, in terms of performance, given all the contains checks. (That's assuming there will be quite a few entries... for a small number of values, it won't make much difference either way, and a List<string> may even be faster.)
Note that the performance aspect is assuming you're using LINQ to Objects for this - if you're using something like LINQ to SQL, it won't matter as the Contains check won't be done in-process anyway.
You wouldn't call a.sysid.Contains; the syntax for IN (SQL) is the reverse of the syntax for Contains (LINQ)
var parRec = Lnq.attlnks.Where(a => chiList.Contains(a.sysid))
.Select(a => a.ownerid);
In addition to the Contains approach, you could join:
var parRec = from a in Lnq.attlnks
join sysid in chiLst
on a.sysid equals sysid
select a.ownerid
I'm not sure whether this will do better than Contains with a HashSet, but it will at least have similar performance. It will certainly do better than using Contains with a list.

Dynamic linq-to-sql that queries based on multiple keywords

I had been putting together a simple little search.
IEnumerable<Member> searchResults = (from m in members
where m.ScreenName.ToUpper().Contains(upperKeyword)
select m).AsEnumerable();
An then I realized this if the user typed in "keyword1 keyword2", this little query will always search for that exact string. So, I decided I should probably split keywords
string[] keywords = upperKeyword.split(' ');
and then I ran into an issue. I can't really do this:
IEnumerable<Member> searchResults = (from m in members
where m.ScreenName.ToUpper().Contains(keywords) // array of string
select m).AsEnumerable();
because .Contains() doesn't take array. How could I accomplish this?
Try this (untested):
IEnumerable<Member> searchResults = members.ToList().Where(m => keywords.Any(k => m.Summary.Contains(k)))
Edit
Added .ToList(), as I don't think LINQ will be able to convert the above into SQL, so we'll have to perform this in-memory.
For Exact Matches:
Try the inverse of what you have: where keywords.Contains(m.ScreenName)
For reference, Creating IN queries with LINQ-to-SQL
For Partial Matches:
string[] keywords = new[]{ ... };
var results = db.members.Where(m => keywords.Any(sn => m.ScreenName.Contains(sn)));
No compilation error here, but I don't have the data to test against.
I think you need to convert the array to a list. I'm just on my way out of the office, but I think this should work.
IEnumerable<Member> searchResults = (from m in members
where keywords.ToList().Contains(m.ScreenName.ToUpper()) // array of string
select m).AsEnumerable();

Categories

Resources