Dynamic property in Linq query - c#

I have the following Linq expression:
var itemToUpdate = ItemsUpdated.FirstOrDefault(x => x.Id == itemId);
I need to adapt it so that x.Id is a dynamically defined property, for example:
var itemToUpdate = ItemsUpdated.FirstOrDefault(x => x["Id"] == itemId);
I have tried the following already which I thought would be enough:
var itemToUpdate = ItemsUpdated.FirstOrDefault(x => (int)x.GetType().GetProperty("Id").GetValue(x) == itemId);
This runs without any errors, but any code after this statement does not execute therefore I assume something goes wrong with the expression above.

Related

Is there a way to create a filter expression in MongoDB C# drivers (same way as projection allows)?

I can create a projection fiel definition from expression like that:
var projection = Builders<Bar>.Projection.Expression(x => x.Foo)
I want to do exactly the same with the filter:
var filter = Builders<Bar>.Filter.Expression(x => x.Foo == "10")
But I cannot find a way to do this. The operations that I want to perform FindOneAndUpdateAsync supports an expression as it's input directly, but I want to combine a "hardcoded" filter with the one that is coming in as a parameter. This is how I do it now without an expression:
var filter = Filter.Eq(x => x.Id, id); // Here I would like to write Filter.Expression(x => x.Id == id)
if (additionalFilter != null)
{
filter &= additionalFilter;
}
P.S. I would rather avoid combining expressions directly "the C# way" as it is not very easy to understand.
Mongo net driver does not support Filter.Expression and if you want to project any value cast to as ToList
See the below eg:
var filter = Builders<Movie>.Filter.In("cast", new string[] { "Tom Hanks" });
var movies = await _moviesCollection.Find<Movie>(filter)
.Limit(2)
.ToListAsync();
// your test
var projection = Builders<Bar>.Projection.Expression(x => x.Foo);
var projection = Builders<Bar>.Filter.Where(x => x.Foo == "10");

Passing predicate to LINQ where clause

Hey I have following code:
Func<Assoc, bool> predicate = (x) =>
(
!String.IsNullOrWhiteSpace(version) ? x.Version.Contains(version) : x.Version != null
);
var assocs = _context.Assoc
.Where(x => x.Model == model)
.Where(predicate)
;
But it doesn't work. If I try to execute this server gives me Internal Server Exception but if I change this to
var assocs = _context.Assoc
.Where(x => x.Model == model)
.Where(x => x.Version.Contains(version))
;
it works as I expect.
Why is that?
Is it possible to get preview of Linq generated query?
Using LINQKit you can create predicates that will be expanded for you so you can use them in IQueryable expressions, but you don't need that for just excluding version testing.
var assocs = _context.Assoc
.Where(x => x.Model == model);
if (!String.IsNullOrWhiteSpace(version))
assocs = assocs.Where(x.Version.Contains(version));
becuse the EF can translate Contains method to sql (Version LIKE '%#version%'), but no evaluate your "external" function.
if you load the result to memroy (by export method as ToList()) you can do it as linq2object:
var assocs = _context.Assoc.ToList()
.Where(x => x.Model == model)
.Where(predicate);
My code shows and illustrates but it is definitely not recommended, because it is better to question the DB than to work on memory.

Trying to simplify LINQ expression

I'm trying to simplify a LINQ expression but no matter what i try I'm unable to get it to work
var filterProfileIds = filter.Profiles.Select(s => s.ProfileId);
var newList = new List<FileMedia>();
foreach (var item in filterProfileIds)
{
newList.AddRange(query.Where(w => w.Profiles.Select(s => s.ProfileId).Contains(item)));
}
newList.AddRange(query.Where(w => !w.Profiles.Any()));
query = newList.AsQueryable();
query is of type "FileMedia" and has a relation to Profiles.
So what i want is all the results from the query that has the same profiles that filter.profiles has AND i also want all the results from the query that doesnt have any profiles at all.
Try as the below:
var filterProfileIds = filter.Profiles.Select(s => s.ProfileId);
query = query.Where(w =>
!w.Profiles.Any() ||
w.Profiles.Any(i => filterProfileIds.Contains(i.ProfileId))
).ToList();
If I understand correctly the requirement, you could use a combination of Any and All extension methods like this:
query = query.Where(m => !m.Profiles.Any() ||
filterProfileIds.All(id => m.Profiles.Any(p => p.ProfiledId == id)));
This is if you wish to get the items with exact the same profiles as the filter.
If you indeed want to get the item with any profile contained in the filter, then you could use this instead:
query = query.Where(m => !m.Profiles.Any() ||
m.Profiles.Any(p => filterProfileIds.Contains(p.ProfiledId));
Maybe something like this:
query = (from item in filter.Profiles.Select(s => s.ProfileId)
from fileMedia in query
where fileMedia.Profiles.Select(q => q.ProfileId).Contains(item)
select fileMedia).
Concat(query.Where(w => !w.Profiles.Any())).AsQueryable();

Get Method is null off IQueryable (Entity Framework)

I'm trying to pass lambda expressions and a type to my DAL. I have this statement:
(entities).GetType().GetMethod("Where")
"entities" is the Table of entities on the DataContext.
When I run the statement I get a null even though Linq.Table inherits IQueryable.
Anyone have an idea?
Here is the entire method:
public object GetResultSet(Dictionary<Type, Func<object, bool>> values)
{
using (ICSDataContext db = DataContextFactory.CreateDataContext<ICSDataContext>(DataContexts.ICS))
{
foreach (var entry in values)
{
var property = db.GetType().GetProperty(entry.Key.Name + "s");
IQueryable entities = (IQueryable)property.GetValue(db, null);
var whereMethod = (entities).GetType().GetMethod("Where")
.MakeGenericMethod(Type.GetType(entry.Key.AssemblyQualifiedName));
return whereMethod.Invoke(entities, new object[] { entry.Value });
}
}
return null;
}
Thanks
As an alternative you could do something like
db.Set<Type>()
which will return you the DBSet of the appropriate type, with Where accessible without reflection. Also you may want to use Expression> rather than Func, expressions work on queryables where as funcs work on enumerables. If you pass a func into a Where clause it pulls the entire dbset down and processes it in memory.
Typed expressions are also a little easier to work with (intellesence, type checking).
Expression<Func<User,bool>> filter = c=>c.FirstName == "Bob";
As another alternative you can look into System.Linq.Dynamic, ScottGu has a write up on it here. The article and the code are old, but it works with EF 6. It allows things like
.Where("CategoryId=2 and UnitPrice>3")
From answer by LukeH under here:
var where1 = typeof(Queryable).GetMethods()
.Where(x => x.Name == "Where")
.Select(x => new { M = x, P = x.GetParameters() })
.Where(x => x.P.Length == 2
&& x.P[0].ParameterType.IsGenericType
&& x.P[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& x.P[1].ParameterType.IsGenericType
&& x.P[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))
.Select(x => new { x.M, A = x.P[1].ParameterType.GetGenericArguments() })
.Where(x => x.A[0].IsGenericType
&& x.A[0].GetGenericTypeDefinition() == typeof(Func<,>))
.Select(x => new { x.M, A = x.A[0].GetGenericArguments() })
.Where(x => x.A[0].IsGenericParameter
&& x.A[1] == typeof(bool))
.Select(x => x.M)
.SingleOrDefault();
Then this:
var gmi = where1.MakeGenericMethod(typeof(T));

C# predicate list passed to Linq Where clause

I have a long Linq Where clause that I would like to populate with a predicate list.
List<Expression<Func<Note, bool>>> filters = new List<Expression<Func<Note, bool>>>();
filters.Add(p => p.Title != null && p.Title.ToLower().Contains(searchString));
filters.Add(p => p.Notes != null && p.Notes.ToLower().Contains(searchString));
filters.Add(GlobalSearchUser((List < User > users = new List<User>() { p.user1, p.user2, p.user3, p.user4 }), searchString));
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(filters.ToArray()).Take(10).ToList();
However I'm getting this error:
cannot convert from 'System.Linq.Expressions.Expression<System.Func<project.Contracts.DTOs.Note,bool>>[]' to 'System.Func<project.Contracts.DTOs.Note,bool>'
Which is an error on the .where clause. Pulling out the .where compiles just fine.
I think great answer from Hogan can be simplified and shorten a bit by use of Any and All Linq methods.
To get items that fulfill all the conditions:
var resultAll = listOfItems.Where(p => filters.All(f => f(p)));
And to get the items that fulfill any condition:
var resultAny = listOfItems.Where(p => filters.Any(f => f(p)));
There are at least two errors in your code:
List<Expression<Func<Note, bool>>> filters = new List<Expression<Func<Note, bool>>>();
change it to
List<Func<Note, bool>> filters = new List<Func<Note, bool>>();
You don't need Expression trees here. You are using IEnumerable<>, not IQueryable<>
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(filters.ToArray()).Take(10).ToList();
There .Where() accepts a single predicate at a time. You could:
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(x => filters.All(x)).Take(10).ToList();
or various other solutions, like:
var notesEnu = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.AsEnumerable();
foreach (var filter in filters)
{
notesEmu = notesEmu.Where(filter);
}
notes = notesEnu.Take(10).ToList();
Because all the .Where() conditions are implicitly in &&.
You have to loop over your filters and run a test on each one.
You can do it with linq like this to return true if any of your filters are true:
.Where(p => { foreach(f in filters) if (f(p) == true) return(true); return(false)})
or like this to to return true if all of your filters are true:
.Where(p => { foreach(f in filters) if (f(p) == false) return(false); return(true)})
You can't just pass an array of predicates to the where method. You need to either iterate over the array and keep calling Where() for each expression in the array, or find a way to merge them all together into one expression and use that. You'll want to use LinqKit if you go the second route.

Categories

Resources