Build dynamic Lambda Expression for comparing undefined number of values - c#

In short what I want do accomplish is to load Tasks from a project in SharePoint Project Server using CSOM.
var projects = ctx.LoadQuery(ctx.Projects
.Where(p => p.Id == projGuid)
.Include(
p => p.Id, p => p.Name,
p => p.Tasks
.Where(t => t.Id == taskGuid)
.Include(t => t.Name))
);
ctx.ExecuteQuery();
My Problem is with this part .Where(t => t.Id == taskGuid). It works how it should if I only want to load 1 Task but would not work if I want to load more then one. Sure I could write it like that .Where(t => t.Id == taskGuid1 || t.Id == taskGuid2 || ... )
But that wouldn't be dynamic.
What I tried was to use an array and the look if the array GuidArray.Contains(p.Id)
But I get an error if I try to use .Contains() inside the Where() expression.
ClientRequestException: The 'Contains' member cannot be used in the expression.
So I was thinking if it is possible to somehow create the lambda expression based on the number of tasks I want to load.

I regards to creation of lambda, you create the dynamic or condition you are looking for like so
public static class ExpressionExt
{
public static IQueryable<T> Where<T,TKey>(this IQueryable<T> data, string prop,params TKey[] guids)
{
var param = Expression.Parameter(typeof(T));
var exp = guids.Select(g => Expression.Equal(Expression.Property(param, prop), Expression.Constant(g))).Aggregate((a, e) => a != null ? Expression.Or(e, a) : e);
var lambda = Expression.Lambda<Func<T, bool>>(exp, param);
return data.Where(lambda);
}
}
And use it like Where(nameof(A.Id), guids) this is what I usually do when the IQueryable only supports or and not contains. There might a contains implementation there so you might want to check the documentation.

Related

Linq Lambda Where clause with in a where clause

I am trying to build a lambda expression with serval where clauses with in each other.
Items, webProperties and profiles are all lists. I am trying to find the one that contains a profile which is 3 level lists down. Actually all I am really trying to do is validate that it does exist.
var x = AccountSummeriesResponse.items.Where(wp => wp.webProperties.Where(p => p.profiles.Where(a => a.id == profile ))).FirstOrDefault();
I am getting the following error.
Cannot implicitly convert type
'System.Collections.Generic.IEnumerable'
to 'bool'
The probelm is Enumerable.Where returns IEnumarable<T> but the predicate of Where expects a boolen. You can use Any instead:-
var x = AccountSummeriesResponse.items
.Where(wp => wp.webProperties.Any(p => p.profiles.Any(a => a.id == profile )))
.FirstOrDefault();
Also, you can replace the Where with FirstOrDefault like this:-
var x = AccountSummeriesResponse.items
.FirstOrDefault(wp => wp.webProperties.Any(p => p.profiles
.Any(a => a.id == profile )));
That's because delegate(Predicate) inside the where clause needs to return bool and you are returning IEnumerable(Where(p => p.profiles.Where(a => a.id == profile ))) so reporting a compilation error.
Instead make a use of Any extension method if you are looking for whether exist in the collection kind of thing..
var x = AccountSummeriesResponse.items.Where(wp => wp.webProperties.Any(p => p.profiles.Any(a => a.id == profile ))).FirstOrDefault();

How to modify the argument of the GroupBy() method?

This is my a linq query that filter tasks by range name then group them by language.
var transTasks = taskData
.Where(t => t.RangeName == rName)
.GroupBy(t => t.CultureID)
.Select(g => new { language = g.Key, tasks = g });
Now I'd like to use the same query for a different grouping. Instead of grouping by CultureID, I'd like to group by TaskOrder.
So I've create variables like this
Func<QuoteTaskInfo, bool> predicate = null
if(tasktype == "translation")
{
groupPredicate = t => t.CultureID;
{
else
{
groupPredicate = t => t.TaskOrder;
}
I'm getting the following error: "Cannot convert expression ... because some return type are not convertible to the delegate return type".
Any help on how to write a delegate that would return a bool?
The same goes with Select(). If the criteria is task order, then the key for the select should be TaskOrder instead of language.
Thanks for helping

What is the best way to dynamically add to a where clause in a nhibernate query in C#?

I have a some C# code that is querying a database using nhibernate that looks like this:
public void Query()
{
IEnumerable<Project> list = session.Query<Project>()
.Where(p => !p.IsDeleted)
.FetchMany(r => r.UnfilteredProjectApplications)
.ThenFetch(r => r.Application)
.ToList()
}
I now have a number of user driver filters so, based on the parameters passed in, i want to add to the where clause. So something like this:
public void Query(string name)
{
if (!String.IsNullOrEmpty(name)
{
IEnumerable<Project> list = session.Query<Project>()
.Where(p => !p.IsDeleted && p.Name == name)
.FetchMany(r => r.UnfilteredProjectApplications)
.ThenFetch(r => r.Application)
.ToList()
}
}
else
{
IEnumerable<Project> list = session.Query<Project>()
.Where(p => !p.IsDeleted)
.FetchMany(r => r.UnfilteredProjectApplications)
.ThenFetch(r => r.Application)
.ToList()
}
the user can select one or many filters. As you can imagine, this code above would get ridiculously complicated given the large number of combinations. Is there an elegant way to append a where clause here with additional blocks of logic. Some might be simple such as
p.Name == name
but others might be more complicated like:
p.ProjectApplications.Select(r => r.Application).Any(s => applicationIds.Contains(s.Id)))
and as I said, there may be zero or many different filters . .
UPDATE:
i have seen in other cases, people suggesting building up the where clause like
query = query.where (r=>r.name = "XYZ");
query = query.where (r=>r.Age > 10);
query = query.where (r=>r.Gender = "Male");
but that does NOT seem to work with nhibernate so what started was a generic lambda question is now a specific question to nhibernate
You can use the PredicateBuilder<T> to create the expression and apply it on your query, for sample:
public void Query(string name)
{
Expression<Func<Project, bool>> filter = PredicateBuilder.True<Project>();
filter = filter.And(p => !p.IsDeleted);
if (!string.IsNullOrEmpty(name)
filter = filter.And(p => p.Name == name);
IEnumerable<Project> list = session.Query<Project>()
.Where(filter)
.FetchMany(r => r.UnfilteredProjectApplications)
.ThenFetch(r => r.Application)
.ToList();
}
With PredicateBuilder you can create the expression you need, adding conditions using And(), Or(), Not() methods.
If you are looking something like this:
public IList<Bestellung> GetAll(Expression<Func<Order, bool>> restriction)
{
ISession session = SessionService.GetSession();
IList<Order> bestellungen = session.Query<Order>()
.Where(restriction).ToList();
return bestellungen;
}
Read this.

LINQ Filter List inside another LINQ Query

Ok, so I have a model that looks like this:
public int idA
public int idB
public int total
public virtual TableA TableA { get; set; }
public virtual TableB TableB { get; set; }
The models for Table A and B are similar to each other, they both tie to this with something like
public virtual List<Association> Assocation { get; set; }
I am now trying to query this, and it is working but I want to be able to filter the results when idB equals a certain integer, for example:
var results = db.TableA
.Where(t => t.idA == id)
.Where(t => t.Association.Where(m => m.idB == 1));
This returns the following exceptions:
Cannot implicitly convert to 'bool'
Cannot convert lambda expression to delegate type 'System.Func' because some of the return types in the block are not implicitly convertible to the delegate return type
Thank you so much for your help!
Update
So I have implemented the following:
var results = db.TableA
.Where(t => t.idA == id)
.Where(t => t.Association.Any(m => m.idB == 1));
Since this association table uses a compound primary key there should only be 1 result returned. There are about 200 results that match the given t.idA == id and that is what I am getting back. It is not only returning the 1 result.
Just for thoroughness here is the query being created, I omitted the fields themselves to simplify it some:
SELECT ... fields here ...
WHERE ([Extent1].[id] = #p__linq__0) AND (#p__linq__0 IS NOT NULL)
AND ( EXISTS (SELECT ... fields here ....
WHERE ([Extent1].[id] = [Extent2].[idA]) AND (1 = [Extent2].[idB])
)
)
Update 2
So the problem with .Any() is it will return the entire collection IF it contains a value that matches 1, all I was wanting was it to return the value that matches 1. So because of this, the only thing I could think of to do was to take the extra 118 rows and then filter the list returned. Luckily upon profiling, this hasn't impacted the SQL server as I initially expected so it was not necessary to do pre-optimization. However, if anyone does know how to filter a list within the initial SQL query using LINQ I would still love to know as I'm sure I could use this in the future where the impact on the database may be more severe, thus the optimization warranted.
The '.Where' lambda function needs to return a boolean value. Currently, you are returning the results of a '.Where' against another recordset. Instead, you probably intended something like this:
var results = db.TableA
.Where(t => t.idA == id)
.Where(t => t.Association.Any(m => m.idB == 1));
The '.Any' function will return true if 't.Association' contains any records that match the condition.
I think what you want to do is use the Any method. Something like this should work:
var results = db.TableA
.Where(t => t.idA == id)
.Where(t => t.Association.Any(m => m.idB == 1));
This will return any Table with the idA equal to id and at least on Association with idB equal to 1.
Or if you prefer query syntax:
var results =
from a in TableA
where a.idA == id && a.Association.Any(m => m.idB == 1)
select a;
This should work:
var results = db.TableA
.Where(t => t.idA == id)
.Select(t => new {
Item = t
, AssociatedItem = t.Association.SingleOrDefault(m => m.idB == 1)
})
.Where(p => p.Associated != null)
.ToList();
This code produces a list of objects of anonymous type with two fields - the Item field with the item, and the AssociatedItem containing its associated item that has idB of 1.
Note that the above code assumes that there is at most one associated item with idB of 1; otherwise, SingleOrDefault would fail.

How can I set LINQ SelectMany projection via Func parameter?

I have a function which returns a list of property values from a collection:
public static List<string> GetSpeakerList()
{
var Videos = QueryVideos(HttpContext.Current);
return Videos.Where(v => v.Type == "exampleType"
.SelectMany(v => v.SpeakerName)
.Distinct()
.OrderBy(s => s)
.ToList();
}
I'd like to have a generic version which will let me determine which field I'd like projected - say instead of SpeakerName I'd like to allow selecting Video.Length or Video.Type.
I understand that SelectMany takes a Func, so what's the best way to make the Func configurable to allow passing it as a parameter into this function?
Add the function as a parameter to the method.
public static List<string> GetVideosAttribute( Func<Video,string> selector )
{
var Videos = QueryVideos(HttpContext.Current);
return Videos.Where(v => v.Type == "exampleType"
.Select( selector )
.Distinct()
.OrderBy(s => s)
.ToList();
}
var speakers = GetVideosAttribute( v => v->SpeakerName );
var topics = GetVideosAttribute( v => v->Topic );

Categories