How can I build Linq query with dynamic OR statements? - c#

The following code:
var dynamicQuery = from a in _context.Users select a;
string[] args = new string[] { "aa", "bb", "cc" };
foreach (string word in args)
dynamicQuery = dynamicQuery.Where(x => x.Name.Contains(word));
return dynamicQuery.ToList();
Will allow me to create a Linq query with a dynamic list of AND expressions.
But suppose I wanted to do the same, only with a dynamic list of OR expressions?

You don't need to loop at all:
return _context.Users.Where(x => args.Any(word => x.Name.Contains(word)));
EDIT: More generally, you can use:
Func<User, bool> predicate = user => false;
foreach (var item in items)
{
var predicateCopy = predicate;
predicate = user => predicateCopy(user) || someOtherCondition;
}
return query.Where(predicate);
This will end up with quite deep stacks (with one delegate calling another calling another etc). Where the specific situation allows you to use Any, that would usually be a better approach.
I would expect Any to work in most situations where you've got a collection of items to potentially match against... the non-Any approach is approprate for "in some situations, anyone over 18 is fine... in some situations anyone with a last name beginning with "G" is appropriate, etc.

Related

Multiple like in lambda expression

I have a string[] variable which i want to use in the lambda expression to filter data using like clause. I tried using contains, but it works like the 'in' clause, which is not what I need.
Please see the code below which behaves like in clause:
var inventories = ..............AsQuerable();
string[] strArray = <<somevalues>>;
inventories = inventories.Where(c => !strArray.Contains(c.columnName));
Could someone provide me the lambda expression which could filter records using like instead of in using an array.
The only methods LINQ provide for this purpose is .Where(), .StartsWith() or .EndsWith().. Also, there is pretty similar question here How to do SQL Like % in Linq?
Unlike VB.NET, C# has no builtin like-operator. However, you can use the operator from VB.NET easily. What you have to do is to reference the assembly Microsoft.VisualBasic.dll and include a using Microsoft.VisualBasic.CompilerServices; at the top of your file. Then you can do
var inventories = ..............AsQuerable();
string[] strArray = <<somevalues>>;
inventories = inventories.Where(c => !strArray.Any(s => LikeOperator.LikeString(c.columnName, s, CompareMethod.Text)));
I'm not entirely sure what you are trying to do, but I think this is what you are trying to accomplish. If not, please provide some more information and I will update my answer.
To check if a string is equal to a part of another string (which like does), you can use the .Contains method:
bool contains = "Some fancy sentence".Contains("fancy");
This will evaluate to true. Given your example, this would result in the following:
var inventories = ..............AsQuerable();
string[] strArray = <<somevalues>>;
inventories = inventories.Where(inv => !strArray.Any(s => inv.columnName.Contains(s)));
This checks all inventories and removes all inventories where the inventory column name (partially) occurs in any of the strArray values.
It is not pure Lambda expression, but Maybe it can help:
List<SomeObject> inventories = new List<SomeObject>();
// add objects to list...
string[] strArray = <<somevalues>>;
// result will be stored here
List<SomeObject> filtered = new List<SomeObject>();
foreach (var itm in strArray)
{
// LIKE '%SOMEVALUE%'
var match = inventories.Where(x => x.columnName.Contains(itm)).ToList();
// LIKE '%SOMEVALUE'
// var match = inventories.Where(x => x.columnName.StartsWith(itm)).ToList();
// LIKE 'SOMEVALUE%'
// var match = inventories.Where(x => x.columnName.EndsWith(itm)).ToList();
foreach (var m in match)
filtered.Add(m);
}

How to keep initializer list order within Select and/or SelectMany

I hope this is not a duplicate but I wasn't able to find an answer on this.
It either seems to be an undesired behavior or missing knowledge on my part.
I have a list of platform and configuration objects. Both contains a member string CodeName in it.
The list of CodeNames look like this:
dbContext.Platforms.Select(x => x.CodeName) => {"test", "PC", "Nintendo"}
dbContext.Configurations.Select(x => x.CodeName) => {"debug", "release"}
They are obtained from a MySQL database hence the dbContext object.
Here is a simple code that I was to translate in LINQ because 2 foreach are things of the past:
var choiceList = new List<List<string>>();
foreach (Platform platform in dbContext.Platforms.ToList())
{
foreach (Configuration configuration in dbContext.Configurations.ToList())
{
choiceList.Add(new List<string>() { platform.CodeName, configuration.CodeName });
}
}
This code gives my exactly what I want, keeping the platform name first which looks like :
var results = new List<List<string>>() {
{"test", "debug"},
{"test", "release"},
{"PC", "debug"}
{"PC", "release"}
{"Nintendo", "debug"}
{"Nintendo", "release"}};
But if I translate that to this, my list contains item in a different order:
var choiceList = dbContext.Platforms.SelectMany(p => dbContext.Configurations.Select(t => new List<string>() { p.CodeName, t.CodeName })).ToList();
I will end up with this, where the platform name isn't always first, which is not what is desired:
var results = new List<List<string>>() {
{"debug", "test"},
{"release", "test"},
{"debug", "PC"}
{"PC", "release"}
{"debug", "Nintendo"}
{"Nintendo", "release"}};
My question is, is it possible to obtain the desired result using LINQ?
Let me know if I'm not clear or my question lacks certain details.
Thanks
EDIT: So Ivan found the explanation and I modified my code in consequence.
In fact, only the Enumerable in front of the SelectMany needed the .ToList().
I should also have mentioned that I was stuck with the need of a List>.
Thanks everyone for the fast input, this was really appreciated.
When you use
var choiceList = dbContext.Platforms.SelectMany(p => dbContext.Configurations.Select(t => new List<string>() { p.CodeName, t.CodeName })).ToList();
it's really translated to some SQL query where the order of the returned records in not defined as soon as you don't use ORDER BY.
To get the same results as your nested loops, execute and materialize both queries, and then do SelectMany in memory:
var platforms = dbContext.Platforms.ToList();
var configurations = dbContext.Configurations.ToList();
var choiceList = platforms.SelectMany(p => configurations,
(p, c) => new List<string>() { p.CodeName, c.CodeName })
.ToList();
Rather than projecting it out to an array, project it out two a new object with two fields (potentially an anonymous object) and then, if you need it, project that into a two element array after you have retrieved the objects from the database, if you really do need these values in an array.
Try this-
var platforms= dbContext.Platforms.Select(x=>x.CodeName);
var configurations=dbContext.Configurations.Select(x=>x.CodeName);
var mix=platforms.SelectMany(num => configurations, (n, a) => new { n, a });
If you want to learn more in detail- Difference between Select and SelectMany

Concatenation of IEnumerable collections C#

I have 2 results of a Linq query, with which i want to do do some string operations and concatenate.
Result 1 which is the names of enabled checkboxes from group1, obtained by
var selectedCarPosts = grpBox1MCar.Controls.OfType<CheckBox>()
.Where(c => c.Checked).OrderBy(c => c.Name).Select(c => c.Name);
which yields Result 1:
NearMainGate
NearMP5WestGate
Result 2 which is the names of enabled checkboxes from group2, obtainedby
var selectedDtTypeCars = gbDataTypeMCars.Controls.OfType<CheckBox>()
.Where(c => c.Checked).OrderBy(c => c.Name).Select(c => c.Name);
which yields Result2:
WindDir
WindVel
From both results would like to get a concatenated list as follows: (Result3)
C.NearMainGate
C.NearMP5WestGate
C.WindDirNearMainGate
C.WindDirNearMP5WestGate
C.WindVelNearMainGate
C.WindVelNearMP5WestGate
These form columns in a dynamic sql query later.
I have the following code to accomplish this step by step:
var s1 = selectedCarPosts.Select(s => "C." + s); //the first 2 items in Result3
//Now to get the rest, by inserting Result2 string in each of the first 2 items of Result3
IEnumerable<string> selCarPostsArrWithC = new string[]{};
IEnumerable<string> s2 = new string[]{};
foreach (var type in selectedDtTypeCars)
{
selCarPostsArrWithC = s1.Select(s => s.Insert(2, type));//C.WindDirNearMainGate C.WindDirNearMP5WestGate in FIRST iteration and so on
s2 = s2.Concat(selCarPostsArrWithC);// as soon as the SECOND iteration starts, the previous s2 list is overwritten with the subsequent result in selCarPostsArrWithC
}
The problem here is that during code debugging, I noticed, that as soon as I tap F10 key just after the foreach line, before actually reaching the foreach block, the the previous values in s2 is overwritten with the subsequent result in selCarPostsArrWithC already. Explained below
For the first iteration s2 has result.
[0] "C.WindDirNearMainGate"
[1] "C.WindDirNearMP5WestGate"
At the beginning of second iteration before entering inside the foreach block
s2 already resets to new values with WindVel some how:
[0] "C.WindVelNearMainGate"
[1] "C.WindVelNearMP5WestGate"
Please could any one assist what am I doing wrong? How can i accomplish Result3 in bold, for the IEnumerable list?
Enumerable.Select doesn't do anything important when you call it, it merely sets things up so that the requested work will be performed later as desired.
So when you write
selCarPostsArrWithC = s1.Select(s => s.Insert(2, type));
this doesn't call string.Insert yet. string.Insert is only called when you (or your debugger) later starts iterating over selCarPostsArrWithC.
Normally, that doesn't matter, except for performance if you iterate over the enumerable multiple times. However, here, because string.Insert is called later than you expect, the arguments that you pass to it are also evaluated later than you expect. You only have a single type variable, and that variable already holds the next value by the time it gets read.
In general, you can either solve this by creating a new variable per iteration, that captures the value of type as seen during that iteration:
foreach (var type in selectedDtTypeCars)
{
var type_ = type;
selCarPostsArrWithC = s1.Select(s => s.Insert(2, type_));
s2 = s2.Concat(selCarPostsArrWithC);
}
(Nowadays, C# already does this behind the scenes for foreach, but you may need to write it out like this if using an older compiler.)
Or, alternatively, perform all of the evaluations directly inside the loop body:
foreach (var type in selectedDtTypeCars)
{
selCarPostsArrWithC = s1.Select(s => s.Insert(2, type)).ToList();
s2 = s2.Concat(selCarPostsArrWithC);
}
Although in that case, it would be better to just make s2 a List<string>, and call its AddRange method.
Basically you have a list of prefixes, and a list of suffixes. (Note: I skimmed your code, and I couldn't find where you got the C. that was appended to each of the values.)
For every prefix, you need a suffix.
This is the use of SelectMany()
I simplified your code, because you gave quite a bit of code. But here's what I came up with:
var location = new[]
{
"NearMainGate",
"NearMP5WestGate"
};
var modifier = new[]
{
"WindDir",
"WindVel"
};
var lambdaResult = modifier.SelectMany(s => location.Select(l => string.Format("{0}{1}{2}", "C.", s, l)));
var queryResult =
from m in modifier
from l in location
select string.Format("{0}{1}{2}", "C.", m, l);
Note that there's two solutions: a linq query syntax, and linq lambda syntax.
In this situation, I think the query syntax is cleaner and easier to read, but to each their own.
And here's a fiddle with proof of functionality: https://dotnetfiddle.net/Z61RsI

Check array for duplicates, return only items which appear more than once

I have an text document of emails such as
Google12#gmail.com,
MyUSERNAME#me.com,
ME#you.com,
ratonabat#co.co,
iamcool#asd.com,
ratonabat#co.co,
I need to check said document for duplicates and create a unique array from that (so if "ratonabat#co.co" appears 500 times in the new array he'll only appear once.)
Edit:
For an example:
username1#hotmail.com
username2#hotmail.com
username1#hotmail.com
username1#hotmail.com
username1#hotmail.com
username1#hotmail.com
This is my "data" (either in an array or text document, I can handle that)
I want to be able to see if there's a duplicate in that, and move the duplicate ONCE to another array. So the output would be
username1#hotmail.com
You can simply use Linq's Distinct extension method:
var input = new string[] { ... };
var output = input.Distinct().ToArray();
You may also want to consider refactoring your code to use a HashSet<string> instead of a simple array, as it will gracefully handle duplicates.
To get an array containing only those records which are duplicates, it's a little moe complex, but you can still do it with a little Linq:
var output = input.GroupBy(x => x)
.Where(g => g.Skip(1).Any())
.Select(g => g.Key)
.ToArray();
Explanation:
.GroupBy group identical strings together
.Where filter the groups by the following criteria
.Skip(1).Any() return true if there are 2 or more items in the group. This is equivalent to .Count() > 1, but it's slightly more efficient because it stops counting after it finds a second item.
.Select return a set consisting only of a single string (rather than the group)
.ToArray convert the result set to an array.
Here's another solution using a custom extension method:
public static class MyExtensions
{
public static IEnumerable<T> Duplicates<T>(this IEnumerable<T> input)
{
var a = new HashSet<T>();
var b = new HashSet<T>();
foreach(var x in input)
{
if (!a.Add(x) && b.Add(x))
yield return x;
}
}
}
And then you can call this method like this:
var output = input.Duplicates().ToArray();
I haven't benchmarked this, but it should be more efficient than the previous method.
You can use the built in in .Distinct() method, by default the comparisons are case sensitive, if you want to make it case insenstive use the overload that takes a comparer in and use a case insensitive string comparer.
List<string> emailAddresses = GetListOfEmailAddresses();
string[] uniqueEmailAddresses = emailAddresses.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
EDIT: Now I see after you made your clarification you only want to list the duplicates.
string[] duplicateAddresses = emailAddresses.GroupBy(address => address,
(key, rows) => new {Key = key, Count = rows.Count()},
StringComparer.OrdinalIgnoreCase)
.Where(row => row.Count > 1)
.Select(row => row.Key)
.ToArray();
To select emails which occur more then once..
var dupEmails=from emails in File.ReadAllText(path).Split(',').GroupBy(x=>x)
where emails.Count()>1
select emails.Key;

Is there a LINQ equivalent of string.Join(string, string[]) [duplicate]

This question already exists:
What is the LINQ way to implode/join a string array? [duplicate]
Closed 9 years ago.
Is there any way to convert a collection of objects into a single new object using LINQ?
I want to use this within another LINQ to SQL expression.
Why don't you use the string.Join itself?
string.Join("<br/>", collection.Select(e => e.TextProp));
You can use the Aggregate method...
var myResults = (from myString in MyStrings
select myString)
.Aggregate(string.Empty, (results, nextString)
=> string.Format("{0}<br />{1}", results, nextString));
or
var myResults = MyStrings.Aggregate(string.Empty, (results, nextString)
=> string.Format("{0}<br />{1}", results, nextString));
The normal way would be to use one of the aggregation operators (Aggregate, Sum, Average etc), but it entirely depends on the type and what you want to do. What type are you interested in?
EDIT: Okay, so you want to concatenate strings... I don't think there's anything which will do that in LINQ to SQL itself. Options:
Write a stored proc or TVF to do it in SQL
Fetch the individual strings in LINQ to SQL and concatenate back on the client side
Most of the solutions here are fairly inefficient if you have large numbers of values you want to concatonate. Also, they're not all that readable. If you are going to do this sort of thing frequently, then it's worth building your own extension method to do it. The implementation below allows you to do the equivalent of string.Join(", ", arrayOfStrings) where the arrayOfStrings can be an IEnumerable<T>, and separator can be any object at all. It allows you to do something like this:
var names = new [] { "Fred", "Barney", "Wilma", "Betty" };
var list = names
.Where(n => n.Contains("e"))
.Join(", ");
Two things I like about this are:
It's very readable in a LINQ context.
It's fairly efficient because it uses StringBuilder and avoids evaluating the enumeration twice which is important in a database scenario (L2S, L2E, or L2Nh).
public static string Join<TItem,TSep>(
this IEnumerable<TItem> enuml,
TSep separator)
{
if (null == enuml) return string.Empty;
var sb = new StringBuilder();
using (var enumr = enuml.GetEnumerator())
{
if (null != enumr && enumr.MoveNext())
{
sb.Append(enumr.Current);
while (enumr.MoveNext())
{
sb.Append(separator).Append(enumr.Current);
}
}
}
return sb.ToString();
}

Categories