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

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();

Related

Linq challenge: converting this piece of code from method chain to standard Linq

The challenge is about converting from method chain to standard linq a piece of code full of group by.
The context
To fully understand the topic here you can read the original question (with class definitions, sample data and so on): Linq: rebuild hierarchical data from the flattened list
Thanks to #Akash Kava, I've found the solution to my problem.
Chain method formulation
var macroTabs = flattenedList
.GroupBy(x => x.IDMacroTab)
.Select((x) => new MacroTab
{
IDMacroTab = x.Key,
Tabs = x.GroupBy(t => t.IDTab)
.Select(tx => new Tab {
IDTab = tx.Key,
Slots = tx.Select(s => new Slot {
IDSlot = s.IDSlot
}).ToList()
}).ToList()
}).ToList();
But, for sake of knowledge, I've tried to convert the method chain to the standard Linq formulation but something is wrong.
What happens is similar to this..
My attempt to convert it to Linq standard syntax
var antiflatten = flattenedList
.GroupBy(x => x.IDMacroTab)
.Select(grouping => new MacroTab
{
IDMacroTab = grouping.Key,
Tabs = (from t in grouping
group grouping by t.IDTab
into group_tx
select new Tab
{
IDTab = group_tx.Key,
Slots = (from s in group_tx
from s1 in s
select new Slot
{
IDSlot = s1.IDSlot
}).ToList()
}).ToList()
});
The result in LinqPad
The classes and the sample data on NetFiddle:
https://dotnetfiddle.net/8mF1qI
This challenge helped me to understand what exactly returns a Linq Group By (and how prolix is the Linq syntax with Group By).
As LinqPad clearly shows a Group By returns a List of Groups. Group is a very simple class which has just one property: a Key
As this answer states, from definition of IGrouping (IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable) the only way to access to the content of the subgroups is to iterate through elements (a foreach, another group by, a select, ecc).
Here is shown the Linq syntax formulation of the method chain.
And here is the source code on Fiddle
But let's go on trying to see another solution:
What we usually do in SQL when we do a Group By is to list all the columns but the one which have been grouped. With Linq is different.. it still returns ALL the columns.
In this example we started with a dataset with 3 'columns' {IDMacroTab, IDTab, IDSlot}. We grouped for the first column, but Linq would return the whole dataset, unless we explicitly tell him..

Intersect two collections which contain different types

Suppose I have one collection, call it ids it is of type IEnumerable<string>, I have a second collection call it objects it's of type MyObject[]. MyObject has a string property called id. I would like a LINQ statement that returns all off the objects in the objects collection who's id matches any value in the ids collection. ids will be a strict subset of objects.Select(x => x.id). Meaning, for every string in ids I know there will be exactly one corresponding MyObject in objects. Can someone post a pure LINQ solution? I've tried a couple things with no luck. I can come up with an iterative solution easily enough so unless it's impossible to do with only LINQ please don't post any.
"Just" LINQ:
var r = obj.Where(o => ids.Any(id => id == o.id));
But better, for larger n, with a set:
var hs = new HashSet(ids);
var r = obj.Where(o => hs.Contains(o.id));
I think this is pretty straightforward with query syntax.
It would look something like:
var a = from o in objects
join i in ids on o.id equals i
select o;
If you just want a list of MyObject that match, you can do :
var solution = objects.Where(x=> ids.Contains(x.id));
With this instead, you'll get a List<T> where T is an Anonymous type with 2 properties, Id that is the string that work as "key" in this specific case, and Obj, a list of MyObject which id correspond to the Id property.
var solution = ids.Select(x=>new{ Id = x, Obj=objects.Where(y=>y.id == x).ToList()})
.ToList();
If you just want to know if there is any object in the intersection (which was what I was looking for)
Based on this
var a = from o in objects
join i in ids on o.id equals i
select o;
You can do this as well
var isEmpty = objects.Any(x => ids.Any(y => y == x.ToString()));
The accepted answer is correct. However, if someone doesn't like using SQL style LINQ, here is the LINQ extension method approach to solving the same problem.
var filteredObjects = objects.Join(ids, obj => obj.Id, id => id, (obj, _) => obj);
We are joining two different types, so the 2nd & 3rd Join parameter signify that join will be made on id.
The fourth parameter is used to select an object out of the resultant (obj, id) pair after applying join.

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.

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

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.

How to perform group by in LINQ and get a Iqueryable or a Custom Class Object?

Here is my query -
var data = Goaldata.GroupBy(c => c.GoalId).ToList();
This returns a Igrouping object and I want an Iqueryable object which I can directly query to get the data while in this case I have to loop through using a foreach() and then get the data. Is there another way to group by in LINQ which returns directly as a list of Iqueryable or a List as similar to what happens for order by in LINQ.
The easiest way is probably
var data = Goaldata.GroupBy(c => c.GoalId).SelectMany(c => c).ToList();
In the OO sense they aren't really grouped, but they are ordered with the groups together.
Whilst the accepted answer is correct, it seems to be unnecessarily complicated. Assuming GoalId is an int you can just use OrderBy:
var data = Goaldata.OrderBy(c => c.GoalId).ToList();
Or .GroupBy(c => c.GoalId).AsQueryable()...

Categories

Resources