Search in IQueryable<object> - c#

Is it possible to search in an IQueryable
public static IQueryable<object> SearchAllFields(IQueryable<object> query, string term)
{
query = query.Where(q => q.Property1 == term);
query = query.Where(q => q.Property2 == term);
query = query.Where(q => q.Property3 == term);
return query;
}
Lets say I want to compare the search term to each of the properties that the object might have, without knowing before what properties the object might have.
Edit:
I am attempting to create a generic DataTable solution to display any tabular information that might be necessary (orders, books, customers, etc.)
To test the concept I'm using the ApplicationLogs table in my database. The DataTable looks as follows:
Lets say when typing in that search box I want to search for that value in all the columns that might be displayed. The query that populates the table:
IQueryable<object> query = (from log in db.ApplicationLog
orderby log.LogId descending
select new
{
LogId = log.LogId,
LogDate = log.LogDate.Value,
LogLevel = log.LogLevelId == 1 ? "Information" : log.LogLevelId == 2 ? "Warning" : "Error",
LogSource = log.LogSourceId == 1 ? "Www" : log.LogSourceId == 2 ? "Intranet" : "EmailNotification",
LogText = log.LogText
});
As you can see, this query will determine what the properties of the object will be. The example is taken from the logs table, but it can come from any number of tables. Then, if I want to call the generic search method from the original post:
query = DataTableHelper.SearchAllFields(query, pageRequest.Search);

You can use reflection to search through all the properties of the element, but if you want to return all rows where any property matches then you need to use predicatebuilder to build the applied query instead Where().
This example code will return both instances of Foo where A,B and C are "a". And the instances of Bar where E, F and G are "a". Also added example of anonymous type.
class Program
{
private class Foo
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
private class Bar
{
public string E { get; set; }
public string F { get; set; }
public string G { get; set; }
}
static void Main(string[] args)
{
var list = new List<Foo>
{
new Foo { A = "a", B = "a", C = "a" },
new Foo { A = "a2", B = "b2", C = "c2" },
new Foo { A = "a3", B = "b3", C = "c3" },
};
var list2 = new List<Bar>
{
new Bar { E = "a", F = "a", G = "a" },
new Bar { E = "a2", F = "b2", G = "c2" },
new Bar { E = "a3", F = "b3", G = "c3" },
};
var q1 = Filter(list.AsQueryable(), "a");
var q2 = Filter(list2.AsQueryable(), "a");
foreach (var x in q1)
{
Console.WriteLine(x);
}
foreach (var x in q2)
{
Console.WriteLine(x);
}
var queryable = list.Select(p => new
{
X = p.A,
Y = p.B,
Z = p.C
}).AsQueryable();
var q3 = Filter(queryable, "a");
foreach (var x in q3)
{
Console.WriteLine(x);
}
Console.ReadKey();
}
private static IQueryable<object> Filter(IQueryable<object> list, string value)
{
foreach (var prop in list.ElementType.GetProperties())
{
var prop1 = prop;
list = list.Where(l => Equals(prop1.GetValue(l, null), value));
}
return list;
}
}

Related

Linq: pass GroupBy result to a function

I need to group items in a list and then pass each group to a function for further elaboration.
This is my code:
var list = new List<MyObj>(); // list is created ad populated elsewhere in code...
var query = list.AsEnumerable();
query = query.Where(x => x.MyProp == true).Select(x => x); // query definition is way more complicated
var grp = query.GroupBy(x => new { x.Name, x.Surname }).ToList();
Here grp is of type List<IGrouping<'a, MyObj>>.
I can easily iterate through my items with:
foreach (var g in grp)
{
foreach (var o in g)
{
// here "o" is of type MyObj
}
}
but I don't know how to create a function that receives a group and iterates through its items:
foreach (var g in grp)
{
DoSomethingWithGroup(g);
}
This is because I have an anonymous type (Key) in grp definition.
I tried to replace the Key anonymous type with a custom type:
private class GrpKey
{
public string Name { get; set; }
public string Surname { get; set; }
}
/* ... */
var grp = query.GroupBy(x => new GrpKey { Name = x.Name, Surname = x.Surname }).ToList();
This way grp is of type List<IGrouping<MyGrpKey, MyObj>> instead of List<IGrouping<'a, MyObj>>.
I could then create a function:
private void DoSomethingWithGroup(IGrouping<MyGrpKey, MyObj>) { /* ... */ }
Unfortunately, this way grouping doesn't work anymore: grp now contains as many groups as items in the source list, each one with a single item.
Instead of specifying your method like you did:
private void DoSomethingWithGroup(IGrouping<MyGrpKey, MyObj>)
separate the key and the elements into their on parameters
private void DoSomethingWithGroup<T>(T groupKey, IEnumerable<MyObj> entities)
With this change you can do the following:
//Populate list with some dummy data
var list = new List<MyObj>()
{
new MyObj { Id = 1, MyProp = true, Name = "A", Surname = "B"},
new MyObj { Id = 2, MyProp = false, Name = "A", Surname = "B"},
new MyObj { Id = 3, MyProp = true, Name = "B", Surname = "B"},
new MyObj { Id = 4, MyProp = true, Name = "B", Surname = "B"},
new MyObj { Id = 5, MyProp = true, Name = "C", Surname = "B"},
};
//Perform the grouping
var groups = list
.Where(x => x.MyProp)
.GroupBy(x => new { x.Name, x.Surname })
.ToList();
//Perform some arbitrary action on the group basis
foreach (var group in groups)
{
DoSomethingWithGroup(group.Key, group);
}
If the implementation of the DoSomethignWithGroup looks like this:
void DoSomethingWithGroup<T>(T groupKey, IEnumerable<MyObj> entities)
{
Console.WriteLine(groupKey);
foreach (var entity in entities)
{
Console.WriteLine($"- {entity.Id}");
}
}
then the output will be this:
{ Name = A, Surname = B }
- 1
{ Name = B, Surname = B }
- 3
- 4
{ Name = C, Surname = B }
- 5
DotnetFiddle link
If the key is not needed in your function, you can omit that in the method. A IGrouping<TKey, TElement> is an IEnumerable<TElement> so you could just define it as:
private void DoSomethingWithGroup(IEnumerable<MyObj> items)
{
///...
}

LinQ nested lists and nested selects

Consider these two tables:
ClassID Name
1 C1
2 C2
ClassID List<CourseSession>
1 [Object that has value "A"], [Object that has value "B"]
2 [Object that has value "B"], [Object that has value "C"]
When I join these two tables in Linq, I get:
ID Name List
1 C1 [A, B]
2 C2 [A, B]
Wheras I need to expand them:
ID Name List
1 C1 A
1 C1 B
2 C2 A
2 C2 B
Linq code:
var classes = from row in t.AsEnumerable()
select new
{
ClassID = row.Field<Guid>("ClassID"),
ClassName = row.Field<string>("Name"),
};
var classCourses = from row in classes.AsEnumerable()
select new
{
ID = row.ID,
CourseSessionList = GetAllCoursesByID(row.ID).AsEnumerable()
};
//Attempt to join
var expandedClassCourse = from classRow in classes
join ccRow in classCourses
on classRow.ID equals ccRow.ID
into filteredExpandedClasses
select filteredExpandedClasses;
I'm not sure how to achieve this. Any ideas?
Something like (not sure what your model looks like):
context.CouseSessions.Where(cs => /* condition goes here */)
.Select(cs =>
new
{
Name = cs.Name,
Class = cs.Class.Name
});
or
context.Classes.Where(c => /* condition goes here */)
.SelectMany(c => c.Courses)
.Select(cs =>
new
{
Name = cs.Name,
Class = cs.Class.Name
});
I created two models based on assumption. I hope this helps.
class Info
{
public int Id { get; set; }
public string Name { get; set; }
public List<string> List { get; set; }
}
class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public string s { get; set; }
}
static void Main(string[] args)
{
var infos = new List<Info> { new Info { Id = 1, Name = "c1", List = new List<string> { "A", "B" } }, new Info { Id = 2, Name = "c2", List = new List<string> { "A", "B" } } };
var myClasses = new List<MyClass>();
foreach (var info in infos)
{
myClasses.AddRange(info.List.Select(a => new MyClass { Id = info.Id, Name = info.Name, s = a }));
}
}
(from c in classList
join s in sessionList on c.ClassID equals s.ClassID
select new
{
ID = c.ClassID,
Name = c.Name,
SessionList = s.SessionList
})
.SelectMany(e => e.SessionList.Select(s => new
{
ID = e.ClassID,
Name = e.Name,
Session = s
}))

Group by with multiple columns using lambda

How can I group by with multiple columns using lambda?
I saw examples of how to do it using linq to entities, but I am looking for lambda form.
var query = source.GroupBy(x => new { x.Column1, x.Column2 });
I came up with a mix of defining a class like David's answer, but not requiring a Where class to go with it. It looks something like:
var resultsGroupings = resultsRecords.GroupBy(r => new { r.IdObj1, r.IdObj2, r.IdObj3})
.Select(r => new ResultGrouping {
IdObj1= r.Key.IdObj1,
IdObj2= r.Key.IdObj2,
IdObj3= r.Key.IdObj3,
Results = r.ToArray(),
Count = r.Count()
});
private class ResultGrouping
{
public short IdObj1{ get; set; }
public short IdObj2{ get; set; }
public int IdObj3{ get; set; }
public ResultCsvImport[] Results { get; set; }
public int Count { get; set; }
}
Where resultRecords is my initial list I'm grouping, and its a List<ResultCsvImport>. Note that the idea here to is that, I'm grouping by 3 columns, IdObj1 and IdObj2 and IdObj3
if your table is like this
rowId col1 col2 col3 col4
1 a e 12 2
2 b f 42 5
3 a e 32 2
4 b f 44 5
var grouped = myTable.AsEnumerable().GroupBy(r=> new {pp1 = r.Field<int>("col1"), pp2 = r.Field<int>("col2")});
Further to aduchis answer above - if you then need to filter based on those group by keys, you can define a class to wrap the many keys.
return customers.GroupBy(a => new CustomerGroupingKey(a.Country, a.Gender))
.Where(a => a.Key.Country == "Ireland" && a.Key.Gender == "M")
.SelectMany(a => a)
.ToList();
Where CustomerGroupingKey takes the group keys:
private class CustomerGroupingKey
{
public CustomerGroupingKey(string country, string gender)
{
Country = country;
Gender = gender;
}
public string Country { get; }
public string Gender { get; }
}
class Element
{
public string Company;
public string TypeOfInvestment;
public decimal Worth;
}
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>()
{
new Element { Company = "JPMORGAN CHASE",TypeOfInvestment = "Stocks", Worth = 96983 },
new Element { Company = "AMER TOWER CORP",TypeOfInvestment = "Securities", Worth = 17141 },
new Element { Company = "ORACLE CORP",TypeOfInvestment = "Assets", Worth = 59372 },
new Element { Company = "PEPSICO INC",TypeOfInvestment = "Assets", Worth = 26516 },
new Element { Company = "PROCTER & GAMBL",TypeOfInvestment = "Stocks", Worth = 387050 },
new Element { Company = "QUASLCOMM INC",TypeOfInvestment = "Bonds", Worth = 196811 },
new Element { Company = "UTD TECHS CORP",TypeOfInvestment = "Bonds", Worth = 257429 },
new Element { Company = "WELLS FARGO-NEW",TypeOfInvestment = "Bank Account", Worth = 106600 },
new Element { Company = "FEDEX CORP",TypeOfInvestment = "Stocks", Worth = 103955 },
new Element { Company = "CVS CAREMARK CP",TypeOfInvestment = "Securities", Worth = 171048 },
};
//Group by on multiple column in LINQ (Query Method)
var query = from e in elements
group e by new{e.TypeOfInvestment,e.Company} into eg
select new {eg.Key.TypeOfInvestment, eg.Key.Company, Points = eg.Sum(rl => rl.Worth)};
foreach (var item in query)
{
Console.WriteLine(item.TypeOfInvestment.PadRight(20) + " " + item.Points.ToString());
}
//Group by on multiple column in LINQ (Lambda Method)
var CompanyDetails =elements.GroupBy(s => new { s.Company, s.TypeOfInvestment})
.Select(g =>
new
{
company = g.Key.Company,
TypeOfInvestment = g.Key.TypeOfInvestment,
Balance = g.Sum(x => Math.Round(Convert.ToDecimal(x.Worth), 2)),
}
);
foreach (var item in CompanyDetails)
{
Console.WriteLine(item.TypeOfInvestment.PadRight(20) + " " + item.Balance.ToString());
}
Console.ReadLine();
}
}

Linq: Converting flat structure to hierarchical

What is the easiest and somewhat efficient way to convert a flat structure:
object[][] rawData = new object[][]
{
{ "A1", "B1", "C1" },
{ "A1", "B1", "C2" },
{ "A2", "B2", "C3" },
{ "A2", "B2", "C4" }
// .. more
};
into a hierarchical structure:
class X
{
public X ()
{
Cs = new List<string>();
}
public string A { get; set; }
public string B { get; set; }
public List<string> Cs { get; private set; }
}
the result should look like this
// pseudo code which describes structure:
result =
{
new X() { A = "A1", B = "B1", Cs = { "C1", "C2" } },
new X() { A = "A2", B = "B2", Cs = { "C3", "C4" } }
}
Preferably using Linq extension methods. Target class X could be changed (eg. a public setter for the List), only if not possible / useful as it is now.
for this particular case:
.GroupBy( x => new { a = x[0], b = x[1] } )
.Select( x => new { A = x.Key.a, B = x.Key.b, C = x.Select( c => c[2] ) })
Something like this should work if the depth of your hierarchy is limited (as in your example where you have only three levels A, B and C). I simplified your X a little bit:
class X {
public string A { get; set; }
public string B { get; set; }
public List<string> Cs { get; set; }
}
Then you can use nested GroupBy as many times as you need (depending on the depth of the hierarchy). It would be also relatively easy to rewrite this into a recursive method (that would work for arbitrarily deep hierarchies):
// Group by 'A'
rawData.GroupBy(aels => aels[0]).Select(a =>
// Group by 'B'
a.GroupBy(bels => bels[1]).Select(b =>
// Generate result of type 'X' for the current grouping
new X { A = a.Key, B = b.Key,
// Take the third element
Cs = b.Select(c => c[2]).ToList() }));
This is more explicit than the other solutions here, but maybe it will be more readable as it is more straightforward encoding of the idea...
With X members being strings and Cs being a private set, and rawData being an array of arrays of objects, I would add a constructor to X public X(string a, string b, List<string> cs) and then perform this code
var query = from row in rawData
group row by new { A = row[0], B = row[1] } into rowgroup
select new X((string)rowgroup.Key.A, (string)rowgroup.Key.B, rowgroup.Select(r => (string)r[2]).ToList());
This is on the following raw data
object[][] rawData = new object[][]
{
new object[] { "A1", "B1", "C1" },
new object[] { "A1", "B1", "C2" },
new object[] { "A2", "B2", "C3" },
new object[] { "A2", "B2", "C4" }
// .. more
};
I wanted to see if I could write this without anonymous instances. It's not too bad:
IEnumerable<X> myList =
from raw0 in rawData
group raw0 by raw0[0] into g0
let g1s =
(
from raw1 in g0
group raw1 by raw1[1]
)
from g1 in g1s
select new X()
{
A = g0.Key,
B = g1.Key,
C = g1.Select(raw2 => raw2[2]).ToList()
}

Using FindAll on a List<List<T>> type

Assuming
public class MyClass
{
public int ID {get; set; }
public string Name {get; set; }
}
and
List<MyClass> classList = //populate with MyClass instances of various IDs
I can do
List<MyClass> result = classList.FindAll(class => class.ID == 123);
and that will give me a list of just classes with ID = 123. Works great, looks elegant.
Now, if I had
List<List<MyClass>> listOfClassLists = //populate with Lists of MyClass instances
How do I get a filtered list where the lists themselves are filtered. I tried
List<List<MyClass>> result = listOfClassLists.FindAll
(list => list.FindAll(class => class.ID == 123).Count > 0);
it looks elegant, but doesn't work. It only includes Lists of classes where at least one class has an ID of 123, but it includes ALL MyClass instances in that list, not just the ones that match.
I ended up having to do
List<List<MyClass>> result = Results(listOfClassLists, 123);
private List<List<MyClass>> Results(List<List<MyClass>> myListOfLists, int id)
{
List<List<MyClass>> results = new List<List<MyClass>>();
foreach (List<MyClass> myClassList in myListOfLists)
{
List<MyClass> subList = myClassList.FindAll(myClass => myClass.ID == id);
if (subList.Count > 0)
results.Add(subList);
}
return results;
}
which gets the job done, but isn't that elegant. Just looking for better ways to do a FindAll on a List of Lists.
Ken
listOfClasses.SelectMany(x=>x).FindAll( /* yadda */)
Sorry about that, FindAll is a method of List<T>.
This
var result = from x in listOfClasses from y in x where SomeCondition(y) select y;
or
var result = listOfClasses.SelectMany(x=>x).Where(x=>SomeCondition(x));
To keep a list of lists, you could do something like this example:
MyClass a = new MyClass() { ID = 123, Name = "Apple" };
MyClass b = new MyClass() { ID = 456, Name = "Banana" };
MyClass c = new MyClass() { ID = 789, Name = "Cherry" };
MyClass d = new MyClass() { ID = 123, Name = "Alpha" };
MyClass e = new MyClass() { ID = 456, Name = "Bravo" };
List<List<MyClass>> lists = new List<List<MyClass>>()
{
new List<MyClass>() { a, b, c },
new List<MyClass>() { d, e },
new List<MyClass>() { b, c, e}
};
var query = lists
.Select(list => list.Where(item => item.ID == 123).ToList())
.Where(list => list.Count > 0).ToList();
query would be List<List<MyClass>> holding lists of MyClass objects that passed the test. At first glance, it looks out of order with the Where extension coming after the Select, but the transformation of the inner lists needs to occur first, and that's what's happening in the Select extension. Then it is filtered by the Where.
I would probably go with this
List<List<string>> stuff = new List<List<string>>();
List<List<string>> results = new List<List<string>>();
stuff.ForEach(list=> {var result = list.FindAll(i => i == "fun").ToList();
if (result.Count > 0) results.Add(result);
});
List<string> flatResult = new List<string>();
stuff.ForEach(List => flatResult.AddRange(List.FindAll(i => i == "fun")));
That way you can go with a jagged array or flatten it out.. But the Linq way works well too :-).
While producing a flat List<MyClass> will answer your need most of the time, the exact answer to your question is:
var result = (from list in ListOfClassLists
let listWithTheId=
(
(from myClass in list
where myClass.ID == id
select myClass)
.ToList()
)
where listWithTheId.Count > 0
select listWithTheId
).ToList();
This code snippet was taken from my Proof of Concept:
using System.Collections.Generic;
using System.Linq;
namespace ListOfListSelectionSpike
{
public class ListSpikeClass
{
public List<List<MyClass>> ListOfClassLists { get; set; }
private List<MyClass> list1, list2, list3;
public ListSpikeClass()
{
var myClassWithId123 = new MyClass("123");
var myClassWithIs345 = new MyClass("456");
list1 = new List<MyClass> { myClassWithId123, myClassWithIs345 };
list2 = new List<MyClass> { myClassWithId123, myClassWithIs345, myClassWithId123 };
list3 = new List<MyClass> { myClassWithIs345, myClassWithIs345 };
ListOfClassLists = new List<List<MyClass>> { list1, list2, list3 };
}
public List<List<MyClass>> GetListOfListsById(string id)
{
var result = (from list in ListOfClassLists
let listWithTheId =
((from myClass in list
where myClass.ID == id
select myClass)
.ToList())
where listWithTheId.Count > 0
select listWithTheId)
.ToList();
return result;
}
}
public class MyClass
{
public MyClass(string id)
{
ID = id;
Name = "My ID=" + id;
}
public string ID { get; set; }
public string Name { get; set; }
}
}

Categories

Resources