Linq IEqualityComparer<string> Ignore Case [duplicate] - linq

This question already has answers here:
Case insensitive 'Contains(string)'
(29 answers)
Closed 2 years ago.
I am sorting a list of elements:
var matchEle = listOfElements.Where(e => e.Properties().Any(p => p.Name.Contains("Key", Asking for IEqualityComparer))).First();
I am used to just going straight to a StringComparer, OrdinalIgnoreCase or CurrentCultureIgnoreCase, however when calling Contains() in this context, it is asking for an IEqualityComparer. I imagine because of the data structure/level. I saw an example of how to set up an IEqualityComparer such as
strEqualityComparer = new IEqualityComparer();
and defining the class for strEqualityComparer but I am not sure beyond that. Can someone help me get my linq statement to work with an ignore case?
Update:
Just so I'm clear here is an example of the data structure:
listOfElements = [element1, element2, etc..]
element1.Properties = ["Prop1", "Key1", "Prop2", "Key2", etc.]
I need to extract the elements which pass the filter if any of its properties has a value containing the keyword, in this case "Key" therefore it cannot be .Equals or IndexOf.

Update as per comment
Search string inside another string:
var matchEle = listOfElements
.Where(e => e.Properties().Any(p => p.Name.IndexOf("Key", System.StringComparison.OrdinalIgnoreCase) >= 0))
.First();
Old solutions
You have two options, that depends on Name type:
1 - Without IEqualityComparer, and if Name in Properties is a string. replace Contains by Equals like :
var matchEle = listOfElements
.Where(e => e.Properties().Any(p => p.Name.Equals("Key", StringComparison.OrdinalIgnoreCase)))
.First();
2 - With IEqualityComparer, and if Name in Properties is a list of string:
2.1 : Create a custom comparer, like:
public class StringIEqualityComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return x.Equals(y, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(string obj)
{
return obj.GetHashCode();
}
}
2.2 : change little your query to :
var matchEle = listOfElements
.Where(e => e.Properties().Any(p => p.Name.Contains("Key", new StringIEqualityComparer())))
.First();
I hope this helps you.

Related

using linq to find if a text field contains any string in a list

im running this in asp.net core v3.1
my question is similar to this question:
How to use Linq to check if a list of strings contains any string in a list
with the specific question relating to the first answer such that
filterTags = ["abc", "cd", "efg"]
var results = db.People
.Where(p => filterTags.Any(tag => p.Tags.Contains(tag)));
so basically saying
give me results from the db of all People
who's Tags field contains any of the filterTags
where Tags = a big text field populated by a bunch of space-delimited tags
This seems straightforward (esp since this has been written before)
but i get an error back
System.InvalidOperationException: The LINQ expression 'DbSet
.Where(p => __filterTags_0
.Any(tag => p.Tags.Contains(tag)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync()
does anyone know what this means or what im doing wrong?
This is not possible with pure EF LINQ. You have to create helper which transforms your search list in Expression Tree.
public static class QueryExtensions
{
private static MethodInfo _containsMethodInfo = typeof(string).GetMethod("Contains")!;
public static IQueryable<T> FilterUsingContains<T>(this IQueryable<T> query, Expression<Func<T, string>> prop, IList<string> items)
{
if (items.Count == 0)
return query.Where(e => 1 == 2);
var param = prop.Parameters[0];
var predicate = items.Select(i =>
(Expression)Expression.Call(prop.Body, _containsMethodInfo, Expression.Constant(i, typeof(string))))
.Aggregate(Expression.OrElse);
var lambda = Expression.Lambda<Func<T, bool>>(predicate, param);
return query.Where(lambda);
}
}
Then you can use this extension in your queries
filterTags = ["abc", "cd", "efg"]
var results = db.People
.Where(p => p.Tags.AsQueryable().FilterUsingContains(t => t, filterTags).Any());
Here is a workaround for you:
using System.Linq;
string[] filterTags = {"abc", "cd", "efg"};
var results = db.People.Where(p => filterTags.Contains(p.Tags)).ToList();

LINQ GroupBy on single property

I am just not understanding the LINQ non-query syntax for GroupBy.
I have a collection of objects that I want to group by a single property. In this case Name
{ Id="1", Name="Bob", Age="23" }
{ Id="2", Name="Sally", Age="41" }
{ Id="3", Name="Bob", Age="73" }
{ Id="4", Name="Bob", Age="34" }
I would like to end up with a collection of all the unique names
{ Name="Bob" }
{ Name="Sally" }
Based on some examples I looked at I thought this would be the way to do it
var uniqueNameCollection = Persons.GroupBy(x => x.Name).Select(y => y.Key).ToList();
But I ended up with a collection with one item. So I though maybe I was over complicating things with the projection. I tried this
var uniqueNameCollection = Persons.GroupBy(x => x.Name).ToList();
Same result. I ended up with a single item in the collection. What am I doing wrong here? I am just looking to GroupBy the Name property.
var names = Persons.Select(p => p.Name).Distinct().ToList()
If you just want names
LINQ's GroupBy doesn't work the same way that SQL's GROUP BY does.
GroupBy takes a sequence and a function to find the field to group by as parameters, and return a sequence of IGroupings that each have a Key that is the field value that was grouped by and sequence of elements in that group.
IEnumerable<IGrouping<TSource>> GroupBy<TSource, TKey>(
IEnumerable<TSource> sequence,
Func<TSource, TKey> keySelector)
{ ... }
So if you start with a list like this:
class Person
{
public string Name;
}
var people = new List<Person> {
new Person { Name = "Adam" },
new Person { Name = "Eve" }
}
Grouping by name will look like this
IEnumerable<IGrouping<Person>> groups = people.GroupBy(person => person.Name);
You could then select the key from each group like this:
IEnumerable<string> names = groups.Select(group => group.Key);
names will be distinct because if there were multiple people with the same name, they would have been in the same group and there would only be one group with that name.
For what you need, it would probably be more efficient to just select the names and then use Distinct
var names = people.Select(p => p.Name).Distinct();
var uniqueNameCollection = Persons.GroupBy(x => x.Name).Select(y => y.Key).ToList();
Appears valid to me. .net Fiddle showing proper expected outcome: https://dotnetfiddle.net/2hqOvt
Using your data I ran the following code statement
var uniqueNameCollection = people.GroupBy(x => x.Name).Select(y => y.Key).ToList();
The return results were List
Bob
Sally
With 2 items in the List
run the following statement and your count should be 2.
people.GroupBy(x => x.Name).Select(y => y.Key).ToList().Count();
Works for me, download a nugget MoreLinq
using MoreLinq
var distinctitems = list.DistinctBy( u => u.Name);

Another how do I use LINQ to Enumerate Dictionaries

Thank you to all the folks who answered and contributed to my last question. I have run into another interesting incarnation of a LINQ question... so like before...
I have the following
const String A_CONVERSATION = "AD Channels";
const String NOT_REPUTABLE = "AD Resolution";
const String DO_NOT_KNOW = "Capture Input";
private enum Properties
{ MyHow, Thats, It }
List<String> MyList = new List<string>
{
A_CONVERSATION,
NOT_REPUTABLE,
DO_NOT_KNOW
}
private Dictionary <Properties, String> PropertyToString;
private Dictionary <String, Properies> StringToProperty;
How can I use LINQ to populate each of the dictionaries so that I can use the following? Is there a one line LINQ statement that would populate each?
Properties MyResult1 = StringToProperty[A_CONVERSATION];
String MySResult2 = PropertyToString[Properties.It];
I specifically would like to use the actaull property to index in the second case.
Providing this is what you want...
In two statements (one could be merged I guess via some lookup somehow - but I didn't think it was that relevant - not everything has to be in one line:)
var properties = ((Properties[])Enum.GetValues(typeof(Properties))).ToList();
var propertyToString = properties.Zip(MyList, (p, s) => new { Prop = p, Text = s }).ToDictionary(x => x.Prop, x => x.Text);
var stringToProperty = properties.Zip(MyList, (p, s) => new { Prop = p, Text = s }).ToDictionary(x => x.Text, x => x.Prop);
I deliberately separated the makings of the enum list - for clarity - you can move that into one line if you'd like.

Complex foreach loop possible to shorten to linq?

I have a cluttery piece of code that I would like to shorten using Linq. It's about the part in the foreach() loop that performs an additional grouping on the result set and builds a nested Dictionary.
Is this possible using a shorter Linq syntax?
var q = from entity in this.Context.Entities
join text in this.Context.Texts on new { ObjectType = 1, ObjectId = entity.EntityId} equals new { ObjectType = text.ObjectType, ObjectId = text.ObjectId}
into texts
select new {entity, texts};
foreach (var result in q)
{
//Can this grouping be performed in the LINQ query above?
var grouped = from tx in result.texts
group tx by tx.Language
into langGroup
select new
{
langGroup.Key,
langGroup
};
//End grouping
var byLanguage = grouped.ToDictionary(x => x.Key, x => x.langGroup.ToDictionary(y => y.PropertyName, y => y.Text));
result.f.Apply(x => x.Texts = byLanguage);
}
return q.Select(x => x.entity);
Sideinfo:
What basically happens is that "texts" for every language and for every property for a certain objecttype (in this case hardcoded 1) are selected and grouped by language. A dictionary of dictionaries is created for every language and then for every property.
Entities have a property called Texts (the dictionary of dictionaries). Apply is a custom extension method which looks like this:
public static T Apply<T>(this T subject, Action<T> action)
{
action(subject);
return subject;
}
isn't this far simpler?
foreach(var entity in Context.Entities)
{
// Create the result dictionary.
entity.Texts = new Dictionary<Language,Dictionary<PropertyName,Text>>();
// loop through each text we want to classify
foreach(var text in Context.Texts.Where(t => t.ObjectType == 1
&& t.ObjectId == entity.ObjectId))
{
var language = text.Language;
var property = text.PropertyName;
// Create the sub-level dictionary, if required
if (!entity.Texts.ContainsKey(language))
entity.Texts[language] = new Dictionary<PropertyName,Text>();
entity.Texts[language][property] = text;
}
}
Sometimes good old foreach loops do the job much better.
Language, PropertyName and Text have no type in your code, so I named my types after the names...

Dynamic LINQ with Data Objects [duplicate]

This question already has answers here:
How do I apply OrderBy on an IQueryable using a string column name within a generic extension method?
(8 answers)
Closed 2 years ago.
So I have been searching for a simple example to hopefully a simple problem.
I have a simple object a List of ListTest (List)
public class ListTest{
{
public string perName { get; set; }
public string perSex { get; set; }
public ListTest(string pName, string pSex)
{
this.perSex = pSex;
this.perName = pName;
}
}
I have loaded it with some data:
List<ListTest> tryIt = new List<ListTest>();
tryIt.Add(new ListTest("Karen", "F"));
tryIt.Add(new ListTest("Kate", "F"));
tryIt.Add(new ListTest("Glen", "M"));
tryIt.Add(new ListTest("Tiger", "M"));
tryIt.Add(new ListTest("Clementine", "F"));
tryIt.Add(new ListTest("Magnolia", "F"));
Now I want to query it using a Lambda Expression:
var things = tryIt
.Where(sex => (sex.perSex == "F"))
.OrderBy(sex => sex.perName);
But I want to do it dynamically, just in case I want to change my where to "perName".
I am able to create the Lambda Expression your Expressions, but I can't figure out how to take it across the goal line and actually assign it to a where clause and execute it.
IQueryable<ListTest> iq = tryIt.AsQueryable();
ParameterExpression pe = Expression.Parameter(typeof(ListTest), "Person");
Expression paEx = Expression.Property(pe, "perSex");
Expression right = Expression.Constant("F");
Expression eqEx = Expression.Equal(paEx, right);
Expression lE = Expression.Lambda<Func<ListTest, bool>>(eqEx, pe);
This should be a simple 4 or 5 line solution, but I can't find an easily decipherable solution example.
Should I use a MethodCallExpression or something down those lines?
Thanks,
You can try something like this:
IEnumerable<ListTest> things = tryIt;
if (someBooleanLogic)
things = things.Where(l => l.perSex == "F");
else
things = things.Where(l => l.perName == "Tiger");
things = things.OrderBy(sex => sex.perName);
List<ListTest> filteredAndSorted = things.ToList();
Edit:
Or, there's also the popular LINQ Dynamic Query Library, where you can form your query pretty much however you want (dynamically, of course).

Resources