How to use .Where with lambda / IQueryable - c#

Edit: Code works fine, it was an other bug.
I had comment out the //department.IdAgency = reader.GetByte(2); line, in the created departmentList. When I removed the // then the IQueryable<string> with .Where works fine. Sorry for the inconvenience!
static List<Department> CreateDepartmentList(IDataReader reader)
{
List<Department> departmentList = new List<Department>();
Department department = null;
while (reader.Read())
{
department = new Department();
department.Id = reader.GetByte(0);
department.Name = reader.GetString(1);
//department.IdAgency = reader.GetByte(2);
if (!reader.IsDBNull(3))
{ department.IdTalkGroup = reader.GetInt16(3); }
departmentList.Add(department);
}
return departmentList;
}
Original question:
I have an IQueryable<string> query, that works. But how do I use .Where?
IQueryable<string> query = departmentList.AsQueryable()
.OrderBy(x => x.Name)
.Select(x => x.Name);
I have tried this, but it does not work:
IQueryable<string> query = departmentList.AsQueryable()
.OrderBy(x => x.Name)
.Where(x => x.IdAgency == idAgencySelected[0])
.Select(x => x.Name);

All the .Where() call does is apply a filtering method to each element on the list, thus returning a new IEnumerable.
So, for some IQueryable<string>...
IEnumerable<string> results = SomeStringList.Where(s => s.Contains("Department"));
...You would get a list of strings that contain the word department.
In other words, by passing it some boolean condition that can be applied to a member of the queryable collection, you get a subset of the original collection.
The reason your second block of code does not work, is because you're calling a method or property that does not belong to string. You may want to consider querying against the more complex type, if it has identifier data, and then take the names of the elements and add them to some list instead.

Related

How to write not in clause in lambda expression where not in contains value from another model list

I'm trying to write lambda expression to fetch data where Id is present in other model list. Below query returns the UserId which will be used further in anotherexpression as key to filter out the data.
var _result = authenticationStatus.accountDetails
.GroupBy(m => m.User_Id)
.Select(m =>new AccountDetails {
User_Id= m.Key,
IsReg = m.Sum(ta => Convert.ToInt32(ta.IsRegistered))
})
.Where(m => m.IsReg > 0)
.ToList();
Now _result will have userId property which will be further used as key to filter data for following query, but whenever I'm trying to use User_id to filter the result I'm getting an error at compile time -"cannot convert from string to AccountDetail"
var authenticated = authenticationStatus.accountDetails
.Where(x=>result.Contains(x.User_Id))
.ToList();
Note -AccountDetail is a model, below is model representation
public class AccountDetails
{
public string UserId {get;set;}
public int IsReg {get;set;}
}
You need to compare the User_Id to the property values of the objects in the list, not to the objects in the list.
var authenticated = authenticationStatus.accountDetails
.Where(x => result.Any(y => y.UserId == x.User_Id))
.ToList();
If result contains a List of AccountDetails, then Contains is looking for an AccountDetails object and not a key. You could use the .Any linq statement instead. Something like
var authenticated = authenticationStatus.accountDetails
.Where(x=>result.Any(ad = > string.Equals(ad.UserId, x.User_id))
.ToList();
Or create a Dictionary or a HashSet, not a list out of your initial query

Linq Groupby Not Grouping in c#

Below i have a snippet of code which outputs a list of Appointments based on clients, some clients can have more than one appointment but the latest one is the one that needs to be outputted for said client
the output is not grouping at all and for some reason i cannot figure why the heck not
foreach (ClientRecord client in clients)
{
List<ReturnRecord> records = db.Appointments
.AsNoTracking()
.Include(rec => rec.Property)
.Include(rec => rec.Property.Address)
.Include(rec => rec.AppointmentType)
.ToList()
.Where(rec => rec.ClientID == client.ID)
.Select(rec => new ReturnRecord
{
ClientName = $"{client.FirstNames} {client.Surnames}",
PropertyAddress = $"{rec.Property.Address.FormattedAddress}",
AppStatus = $"{rec.AppointmentStatus.Name}",
StockStatus = $"{rec.Property.Stocks.FirstOrDefault().StockStatus.Name}",
LastUpdated = rec.LastUpdated
})
.ToList();
returnList.AddRange(records);
}
returnList.GroupBy(rec => rec.PropertyAddress);
return Ok(returnList);
here is an attachment of the screen grab of the output
You need to assign result of GroupBy() to variable:
returnList = returnList.GroupBy(rec => rec.PropertyAddress).ToList();
Make sure to actually use the new IEnumerable that the .GroupBy() Method returned.
If you want to return a List you need to use a workaround:
Get the IEnumerable<IGrouping<int, ReturnRecord>> from the .GroupBy()
Use .SelectMany() to select all elements and save them into an IEnumerable
Now you can convert your IEnumerable into a List with .List()
Example:
// Longer Alternative
IEnumerable<IGrouping<int, ReturnRecord>> groups = resultList
.GroupBy((rec => rec.PropertyAddress);
IEnumerable<ReturnRecord> result = groups.SelectMany(group => group);
List<ReturnRecord> listResult = result.ToList();
return Ok(listResult);
// Shorter Alternative
IEnumerable<IGrouping<int, ReturnRecord>> groups = resultList
.GroupBy((rec => rec.PropertyAddress);
IEnumerable<ReturnRecord> result = groups.SelectMany(group => group);
return Ok(result.ToList());

Group By struct list on multiple columns in C#

I am having a struct as
public struct structMailJob
{
public string ID;
public string MailID;
public int ResendCount;
public int PageCount;
}
and a list as
List<structMailJob> myStructList = new List<structMailJob>();
I have loaded data in myStructList from database and want myStructList data in a new list after grouping by MailID and ResendCount.
I am trying as:
List<structMailJob> newStructList = new List<structMailJob>();
newStructList = myStructList.GroupBy(u => u.MailID, u=>u.ResendCount)
.Select(grp => new { myStructList = grp.ToList() })
.ToList();
but unable to do that as getting error message - cant implicitly convert generic list to structMailJob.
I think that you are looking for is the following:
var newStructList = myStructList.GroupBy(smj => new { smj.MailID, smj.ResendCount })
.Select(grp => new
{
MailID = grp.Key.MailID,
ResendCount = grp.Key.ResendCount
MailJobs = grp.Select(x=>new
{
x.ID,
x.PageCount
}).ToList()
})
.ToList();
Note that we changed the GroupBy clause to the following one:
GroupBy(smj => new { smj.MailID, smj.ResendCount })
Doing so, the key on which the groups would be created would be consisted of both MailID and ResendCount. By the way the former GroupBy clause isn't correct.
Then having done the grouping, we project each group to an object with three properties, MailID and ResendCout, which are the components of the key and list of anonymous type object with two properties, ID and PageCount, which we gave it the name MailJobs.
Last but not least you will notice that I didn't mention the following
List<structMailJob> newStructList = new List<structMailJob>();
I just used the var and declared the newStructList. I don't think that you stated in your post makes sense. How do we expect to get a list of the same objects after grouping them? So I assumed that you might want is the above.
However, I thought you might want also something like this and you didn't want to refer to Grouping.
myStructList = myStructList.OrderBy(smj => smj.MailID)
.ThenBy(smj => smj.ResendCount)
.ToList();
Linq Query is completely incorrect, following are the important points:
myStructList.GroupBy(u => u.MailID, u=>u.ResendCount) // Incorrect grouping
myStructList.GroupBy(u => new {u.MailID, u.ResendCount }) // Correct grouping, which will do by two columns MailID and ResendCount, last one was only doing by MailID and was using ResendCount for result projection
Now the result is of type IEnumerable<IGrouping<AnonymousType,structMailJob>>, so when you do something like Select, it will end up creating Concatenated List of type IEnumerable<List<structMailJob>> (Removed the assignment to myStructList inside the Select, as that was not correct):
.Select(grp => grp.ToList())
Correct code would require you to flatten using SelectMany as follows:
newStructList = myStructList.GroupBy(u => new {u.MailID, u.ResendCount})
.SelectMany(grp => grp.ToList()).ToList();
Assign it to newStructList, but this code has little use, since literally newStructList is exactly same as myStructList post flattening, ideally you shall be able to use the grouping, so that you can get a subset and thus the correct result, however that depends on your business logic
I don't know if I got your question right but it seems to me you missed the 'Group by' signature.
List<structMailJob> myStructList = new List<structMailJob>();
List<structMailJob> newStructList = new List<structMailJob>();
newStructList = myStructList
// .GroupBy(/*Key Selector */u => u.MailID, /*Element Selector*/u=>u.ResendCount)
.GroupBy(u => new { u.MailID, u.ResendCount }) // broup by MailID, ResendCount
// Note no Element Selector , the 'STRUCT' is 'SELECTED'
.Select(grp => {
// NOte: Key == Anonymous {MailID, ResendCount }
return grp;
})
// otherwise you get a IEnumerable<IEnumerable<T>> instead of IEnumerable<T> because you grouped it
.SelectMany(x=>x)
.ToList();
If Mrinal Kamboj's answer is what you are looking for, then you could use the following as an alternative:
var orderedList = myStructList.OrderBy(x => x.MailID).ThenBy(x => x.ResendCount);

Create Multiple Objects Single LINQ EF Method

List<MyObject> objects = await item.tables.ToAsyncEnumerable()
.Where(p => p.field1 == value)
.Select(p => new MyObject(p.field1,p.field2))
.ToList();
^ I have something like that, but what i'm wondering, is there anyway way to add a second object creation, in the same select? eg. new MyObject(p.field3,p.field4) ? and add it to the same list? order does not matter.
I know could do this with multiple calls to database or splitting up lists into sections, but is there way to do this in single line?
You could create it as a tuple.
List<Tuple<MyObject1, MyObject2>> = query.Select(x => Tuple.Create(
new MyObject1
{
// fields
},
new MyObject2
{
//fields
}))
.ToList();
From my testing in Linqpad, it seems that this will only hit the database once.
Alternatively, you could just select all the fields you know you'll need from the database to create both:
var myList = query.Select(x => new { FieldA = x.FieldA, FieldB = x.FieldB }).ToList(); //hits db once
var object1s = myList.Select(x => new MyObject1(x.FieldA));
var object2s = myList.Select(x => new MyObject1(x.FieldB));
var bothLists = object1s.Concat(object2s).ToList();
What you'd want to do is use the SelectMany method in linq. Which will select all the items from an array. The array can be created anonymously as seen below.
List<MyObject> objects = await item.tables.ToAsyncEnumerable()
.Where(p => p.field1 == value)
.SelectMany(p => new []{new MyObject(p.field1,p.field2), new MyObject(p.field3,p.field4)})
.ToList();
Hope that solves you problem!
If you use query syntax instead of method chaining, you can use the let operator to accomplish this. Note that the SQL generated may not be exactly performant as this article shows, but it should work for you if you're after a subquery.
You could try creating an array of objects and then flattening with SelectMany:
List<MyObject> objects = await item.tables.ToAsyncEnumerable()
.Where(p => p.field1 == value)
.Select(p => new [] {
new MyObject(p.field1,p.field2),
new MyObject(p.field3,p.field4)
})
.SelectMany(g => g)
.ToList();
But I suspect you'll have problems getting EF to translate that to a query.

Running the same linq query on multiple IQueryable in parallel?

Situation: I have a List<IQueryable<MyDataStructure>>. I want to run a single linq query on each of them, in parallel, and then join the results.
Question: How to create a linq query which I can pass as a parameter?
Example code:
Here's some simplified code. First, I have the collection of IQueryable<string>:
public List<IQueryable<string>> GetQueries()
{
var set1 = (new List<string> { "hello", "hey" }).AsQueryable();
var set2 = (new List<string> { "cat", "dog", "house" }).AsQueryable();
var set3 = (new List<string> { "cat", "dog", "house" }).AsQueryable();
var set4 = (new List<string> { "hello", "hey" }).AsQueryable();
var sets = new List<IQueryable<string>> { set1, set2, set3, set4 };
return sets;
}
I would like to find all the words which start with letter 'h'. With a single IQueryable<string> this is easy:
query.Where(x => x.StartsWith("h")).ToList()
But I want to run the same query against all the IQueryable<string> objects in parallel and then combine the results. Here's one way to do it:
var result = new ConcurrentBag<string>();
Parallel.ForEach(queries, query =>
{
var partOfResult = query.Where(x => x.StartsWith("h")).ToList();
foreach (var word in partOfResult)
{
result.Add(word);
}
});
Console.WriteLine(result.Count);
But I want this to be a more generic solution. So that I could define the linq operation separately and pass it as a parameter to a method. Something like this:
var query = Where(x => x.FirstName.StartsWith("d") && x.IsRemoved == false)
.Select(x => x.FirstName)
.OrderBy(x => x.FirstName);
var queries = GetQueries();
var result = Run(queries, query);
But I'm at loss on how to do this. Any ideas?
So the first thing that you wanted was a way of taking a sequence of queries, executing all of them, and then getting the flattened list of results. That's simple enough:
public static IEnumerable<T> Foo<T>(IEnumerable<IQueryable<T>> queries)
{
return queries.AsParallel()
.Select(query => query.ToList())
.SelectMany(results => results);
}
For each query we execute it (call ToList on it) and it's done in parallel, thanks to AsParallel, and then the results are flattened into a single sequence through SelectMany.
The other thing that you wanted to do was to add a number of query operations to each query in a sequence of queries. This doesn't need to be parallelized (thanks to deferred execution, the calls to Where, OrderBy, etc. take almost no time) and can just be done through Select:
var queries = GetQueries().Select(query =>
query.Where(x => x.FirstName.StartsWith("d")
&& !x.IsRemoved)
.Select(x => x.FirstName)
.OrderBy(x => x.FirstName));
var results = Foo(queries);
Personally I don't really see a need to combine these two methods. You can make a method that does both, but they're really rather separate concepts so I don't see a need for it. If you do want them combined though, here it is:
public static IEnumerable<TResult> Bar<TSource, TResult>(
IEnumerable<IQueryable<TSource>> queries,
Func<IQueryable<TSource>, IQueryable<TResult>> selector)
{
return queries.Select(selector)
.AsParallel()
.Select(query => query.ToList())
.SelectMany(results => results);
}
Feel free to make either Foo or Bar extension methods if you want. Also, you really better rename them to something better if you're going to use them.
First - given your current implementation, there is no reason to use IQueryable<T> - you could just use IEnumerable<T>.
You could then write a method which takes an IEnumerable<IEnumerable<T>> and a Func<IEnumerable<T>, IEnumerable<U>>, to build a result:
IEnumerable<IEnumerable<U>> QueryMultiple<T,U>(IEnumerable<IEnumerable<T>> inputs, Func<IEnumerable<T>,IEnumerable<U>> mapping)
{
return inputs.AsParallel().Select(i => mapping(i));
}
You could then use this as:
void Run()
{
IEnumerable<IEnumerable<YourType>> inputs = GetYourObjects();
Func<IEnumerable<YourType>, IEnumerable<YourType>> query = i =>
i.Where(x => x.FirstName.StartsWith("d") && x.IsRemoved == false)
.Select(x => x.FirstName)
.OrderBy(x => x.FirstName);
var results = QueryMultiple(inputs, query);
}

Categories

Resources