get data from list of lists with linq - c#

I have a model class of Title-Id:
public Word
{
[key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid WordId {get; set;};
public string WordName {get; set;}
}
this words stored in
public WordsStorage
{
public WordsStorage()
{
CandWords = new HashSet<Word>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid CandWordsModelID { get; set; }
public virtual ICollection<Word> CandWords { get; set; }
}
When I am calling
var aaa = db.UserCWords.AsEnumerable().Select(e => e.CandWords ).Distinct();
var listOfLists = aaa.ToList();
I am getting list of lists of this Words - this is correct.
var aaa = db.UserCWords.AsEnumerable().Select(e => e.CandWordsModelID ).Distinct();
var listOfLists = aaa.ToList();
for ID - that is working OK too
but If I have a my custom Word
Word myCustomWord = new Word();
myCustomWord.Id = Guid.NewGuid();
myCustomWord.WordName = "BadGuy";
how to get all CandWordsModelID, in where this word value contained?
I have tried:
db.UserCWords.AsEnumerable().Where(e=>e.CandWords.Where(s=>s.WordName.Contains(myCustomWord.WordName))).Select(e => e.CandWordsModelID).Distinct()
but get an error error CS0029: Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<prj.Models.Word>' to 'bool'

Try this:
var myWord = ...
db.UserCWords
.Where(e => e.CandWords.Any(w => w.WordName.Contains(myWord.WordName)))
.Select(e => e.Id)
there is no need for calling Distinct since no word store is processed twice.
The issue with you code seems to be that you are trying to filter using the result of a Where which returns a IEnumerable<T>. Now if you take a look at the signature of the Where method you'll see that it takes a IEnumerable<T> and a predicate (Func<T, bool>) and it returns a IEnumerable<T> so when you call the outer Where it expects a predicate and since you are using another Where inside you are giving it a Func<T, IEnumerable<T>> instead of a Func<T, bool>. You need to use Any to achieve what you are doing

Change this expression:
e => e.CandWords.Where( s => s.WordName.Contains( myCustomWord.WordName ) )
to use Any instead of Where:
e => e.CandWords.Any( s => s.WordName.Contains( myCustomWord.WordName ) )
Where will filter CandWords for matching entities where Any will return a boolean of any of the entities in CandWords matches your criteria, which I believe is your goal.

The error is thrown because you basically have Where( ... Where(...) ...) The outer Where expects a Boolean expression, but the inner Where() returns an IEnumerable.
Try changing the inner Where() to use Any(). Any() will return true if it's able to find an object in the collection that satisfies the criteria.
I'd be curious why you need to call AsEnumerable() on db.UserCWords. If UserCWords is IQueryable you would want to call Where and Select before calling AsEnumerable, ToList, etc. Otherwise you're pulling the entire collection from the database and evaluating the LINQ expression on the client (instead of having the LINQ expresion be converted to SQL.)

Related

How to rebuild a lambda expression to start one level deeper?

I created a generic method that accepts a member access expression identifying a grouping key, just as one would pass to IQueryable<T>.GroupBy.
private static IQueryable<ObjectWithRank<T>> IncludeBestRankPerGroup<T,TGroupKey>(this IQueryable<T> q, Expression<Func<T, TGroupKey>> keySelector)
class ObjectWithRank<T> {
public T RankedObject { get; set; }
public int Rank { get; set; }
}
The IncludeBestRankPerGroup method is a variation of my IncludeRank method that just takes an IQueryable<T> and applies a rank to each element by wrapping it in ObjectWithRank<T>, returning an IQueryable<ObjectWithRank<T>>. I then want to group by the keySelector and select the best ranked element per group.
This requires me to convert a lambda expression from form 1 to 2 so I can pass it to IQueryable<ObjectWithRank<T>>.GroupBy:
(T x) => x.GroupingProperty
(ObjectWithRank<T> x) => x.RankedObject.GroupingProperty
Note that I cannot just change the root object type of the keySelector from T to ObjectWithRank<T>, because the ObjectWithRank<T> class is not exposed in the public method that calls IncludeBestRankPerGroup. The user of the API just provides an IQueryable<T>, and receives back an IQueryable<T> with the highest ranking items per group, so they never see that ObjectWithRank<T> is used under the hood.
I managed to perform the conversion with the following code, but it only works for simple member access expressions. For example, it can convert an expression like x => x.GroupingKey to x => x.RankedObject.GroupingKey, but it won't work with a two-level deep member access expression where I'd have to convert something like x => x.SubObject.GroupingKey to x => x.RankedObject.SubObject.GroupingKey.
private static Expression<Func<ObjectWithRank<T>, TGroupKey>> RebuildMemberAccessForRankedObject<T, TGroupBy>(Expression<Func<T, TGroupKey>> keySelector)
{
Expression<Func<ObjectWithRank<T>, T>> objectAccessExpression = x => x.RankedObject;
return Expression.Lambda<Func<ObjectWithRank<T>, TGroupKey>>(
Expression.Property(objectAccessExpression.Body, (keySelector.Body as MemberExpression).Member as PropertyInfo)
, objectAccessExpression.Parameters
);
}
The above seems like a hack where I first create a member access expression that access the T RankedObject property of the ObjectWithRank<T>, then tack on the provided keySelector member access expression. I'm not sure if there's a simple way to get this to work. It seems like Expression.Property only allows drilling down one property at a time, so maybe I need some kind of loop to rebuild the expression from the top, drilling down one property at a time.
There's a similar question here that does have a simple solution, but goes one level deeper on the opposite end of the expression, which isn't what I'm trying to do.
Alter Lambda Expression to go one level deeper
I was able to replace the root of an expression with a recursive lamba that drills down in the member expression until it reaches the parameter expression, replaces the parameter expression with the new root expression at that deepest level, then unwinds the call stack replacing each member expression's Expression with the updated inner expression all the way back to the top, then create's a new lambda with the updated expression and parameter expression set for the new root.
private static Expression<Func<TInNew, TOut>> UpdateExpressionRoot<TOut, TInOld, TInNew>(Expression<Func<TInNew, TInOld>> newRoot, Expression<Func<TInOld, TOut>> memberAccess)
{
Func<MemberExpression, MemberExpression> updateDeepestExpression = null;
updateDeepestExpression = e =>
{
if (e.Expression is MemberExpression)
{
var updatedChild = updateDeepestExpression((MemberExpression)e.Expression);
return e.Update(updatedChild);
}
if (e.Expression is ParameterExpression)
return e.Update(newRoot.Body);
throw new ArgumentException("Member access expression must be composed of nested member access expressions only.", nameof(memberAccess));
};
return Expression.Lambda<Func<TInNew, TOut>>(updateDeepestExpression(memberAccess.Body as MemberExpression), newRoot.Parameters);
}
It can be called like this:
class Car
{
Manufacturer Manufacturer { get; set; }
}
class Manufacturer
{
string ID { get; set; }
}
Expression<Func<Car, string>> groupKeySelector = x => x.Manufacturer.ID;
Expression<Func<ObjectWithRank<Car>, Car>> rankedObjectSelector = x => x.RankedObject;
var rankedGroupKeySelector = UpdateExpressionRoot(rankedObjectSelector, groupKeySelector);
//rankedGroupKeySelector.ToString() == "x.RankedObject.Manufacturer.ID"
//Essentially this replaced ParameterExpression {x} in x.Manufacturer.ID with MemberExpression {x.RankedObject}.

IEnumerable<type> does not contain a definition of 'Contains'

I have 2 classes and 2 IEnumerable lists of those classes:
public class Values
{
public int Value { get; set; }
public DateTime SomeDate { get; set; }
}
public class Holidays
{
public DateTime holiday { get; set; }
}
IEnumerable<Values> values;
IEnumerable<Holidays> holidays;
Next, I am trying to select those 'values' where 'someDate' is not in 'holidays'
var query = values.Where(x=> !holidays.Contains(x.someDate));
However, this gives me the error of IEnumerable<Holidays> does not contain a definition of 'Contains'.
System.Linq is already added in the usings.
I believe this has to do something with the collections, but am not able to figure what.
When you use Contains, the object you're looking for must match the type T of the IEnumerable<T>. Thus, you cannot search IEnumerable<A> for a contained object of type B since there's no implicit way to compare the two.
As mentioned in other answers, use Any and pass in the comparison yourself.
Alternatively, this is also a case where you could use a Select followed by Contains, although this may be less readable in some cases:
var query = values
.Where(x => !holidays
.Select(h => h.holiday)
.Contains(x.someDate));
As an alternative to what everyone else has suggested already:
var holidayDates = new HashSet<DateTime>(holidays.Select(h => h.holiday));
var query = values.Where(x => !holidayDates.Contains(x.someDate));
In particular, if you have a lot of holidays, this change will make the per-value check much more efficient.
Contains is a LINQ extension that takes (in your case) an instance of Holidays and checks if your enumeration contains that instance (or an instance that Equals the given argument).
You should use Any instead:
var query = values.Where(x=> !holidays.Any(h => h.holiday == x.someDate));
You probably want Any():
var query = values.Where(v => !holidays.Any(h => h.holiday == v.someDate));
You can't compare a Holiday object with a DateTime. Use Any extension method instead:
var query = values.Where(x=> !holidays.Any(e=>e.holiday ==x.someDate));
Or you can also use All extension method:
var query = values.Where(x=> holidays.All(e=>e.holiday !=x.someDate));

Reflection Linq-Entity "IN()" function for "contains" searching group of integers

I'm building my own reflection functions for certain types of searches.
The problem is that I want to search a group of IDs within a list of IDs and filter my search/select query to have only these specific objects.
This is the same as using "IN()" in Linq-Entity framework. But I can't use a.objid.
return query.Where(a => ObjectsToFind.Contains(a.objid));
However, "a.objid" is causing errors because I use T Template.
So a is "T a" instead of "MyTable a" so that I can call it's "objid" property.
I know there is a way to do this with parameter expressions. However, I can't figure it out.
Here's what I tried to replace that above line with:
public static IQueryable<T> WhereFunctionContains<T>(this IQueryable<T> query, string contains)
{
var ObjectsToFind = new List<int>(); // I want to search IN() this function that gets filled in here.
ObjectsToFind = FillObjectsToFind(); // just the object id integers I want to filter
var parameter = Expression.Parameter(typeof(T), "type");
var propertyExpression = Expression.Property(parameter, "objid"); // I look for this
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(int) }); // int type
var vars = Expression.Variable(List<int>,); // I can't use "constant", but what do I use here?
var containsExpression = Expression.Call(propertyExpression, method, vars);
return query.Where(Expression.Lambda<Func<T, bool>>(containsExpression, parameter));
}
Replacing "T" with the actual table entity, causes a lot of problems, so I decided to keep the T.
else if (s.ST == "function")
{ // search func
myTable a;
DC2 = MyUtility.WhereFunctionContains(DC2, a => a.objid, s.S);
// s.S is my search query string.
// s.ST means I'm searching for functions (from another associated table)
// I don't understand how a.objid gets called, I wanted to use Template/Reflections.
}
Here's how I call Zev's function.
public static IQueryable<T> WhereFunctionContains<T>(this IQueryable<T> query, Expression<Func<T, int>> converter, string contains)
{
FunctionsClass fc = new FunctionsClass();
var ObjectsToFind = new List<int>();
ObjectsToFind = fc.SearchContainFunction(contains); // I grabbed my list of IDs to search
return query.Where(t => ObjectsToFind.Contains(converter(t)));
}
If I understand you correctly, you have a number of queries on different types:
IQueryable<Person> personsQry = ...
IQueryable<Sale> salesQry = ...
IQueryable<Order> ordersQry = ...
and you have a method that generates a List<int>, called FillObjectsToFind:
public List<int> FillObjectsToFind()
{
//code here
}
You want to be able to limit each of the above queries to only have the id in the returned list. As an added bonus, this should be an extension method, so you can call it like this:
var personsFiltered = personsQry.WhereFunctionContains(...);
var salesFiltered = salesQry.WhereFunctionContains(...);
var ordersFiltered = ordersQry.WhereFunctionContains(...);
The problem is each query is of a separate type, and you would prefer to write one method that covers all of them.
The first part of the solution is to define a generic method:
public static IQueryable<T> WhereFunctionContains<T>(this IQueryable<T> query)
{
//code here
}
but there is still a problem: the only type we know of is type T which is not a real type, but a placeholder for actual types. Since these types could be anything -- string, System.Random, SqlConnection, an ASP.NET Label, a WPF TextBlock -- there is no way of knowing how to compare each object to a List of ints.
The most straightforward solution is to define an interface:
interface IHasObjID
{
int ObjID {get;set;}
}
Then each type should implement this interface:
class Person : IHasObjID
{
int objID;
int ObjID
{
get {return objID;}
set {objID = value;}
}
}
//implement sales and orders similarly
Once that is done, you can define a constraint on the types allowed by the method. Now that the type definitely has an ObjID property, we can query on that:
public static IQueryable<T> WhereFunctionContains<T>(this IQueryable<T> query) where T : IHasObjID
{
var intsToFind = FillObjectsToFind();
return query.Where(t => intsToFind.Contains(t.ObjID));
}
This is what King King was telling you in this comment.
I propose that when calling this function, you also pass in how to get at the integer from the type:
public static IQueryable<T> WhereFunctionContains<T>(this IQueryable<T> query, Expression<Func<T,int>> converter)
{
var intsToFind = FillObjectsToFind();
return query.Where(t => intsToFind.Contains(converter(t)));
}
However, I haven't tested this code, and since we are working with Entity Framework and expressions I suspect there is still an issue: An expression cannot be "called" within an expression.
I wanted to suggest the above, but it doesn't compile with the following error -- 'converter' is a 'variable' but used like a 'method'.
After all that, the solution is straightforward, using Join:
public static IQueryable<T> WhereFunctionContains<T>(this IQueryable<T> query, Expression<Func<T,int>> converter)
{
var ints = new List<int>() { 1, 2, 3, 4, 5 };
return query.Join(ints,converter,i=>i,(t,i) => t);
}
to be called like this:
var filteredPersons = query.WhereFunctionContains(p => p.PersonID);
If this is only used with a single type MyTable:
public static IQueryable<MyTable> WhereFunctionContains(this IQueryable<MyTable> query)
{
var ints = new List<int>() { 1, 2, 3, 4, 5 };
return query.Join(ints, mt=>mt.objid, i=>i, (t,i) => t);
}
Some links from the C# Programming Guide (in order of the answer):
Extension methods
Generics
Generic methods
Generic constraints
Interfaces
LINQ inner joins
Also, see here for a nice overview of LINQ operators, such as Select, Where, Count and ToList.

How use my own function in Linq query?

I want to take some elements by checking them with my custom function.
I have Person table:
public class Person
{
public int Id { get; set; }
public DateTime BirthDay { get; set; }
public string Name { get; set; }
...
}
I should to use my GetAge() and other functions to filter Persons list.
My following code doesnt work:
public List<Person> FilterPersons(int ageFrom, int ageTo...etc..)
{
var all = Database.Persons.AsQueryable();
all = from item in all
where GetAge(item.BirthDay) > ageFrom
select item;
all = from item in all
where GetAge(item.BirthDay) < ageTo
select item;
// other operations
...
}
I think I can write so. In every step to do this:
List<Person> newList = new List<Person>();
foreach (var item in all)
{
var itemAge = Common.GetAge(item.BirthDay);
if (itemAge > AgeFrom)
{
newList.Add(item);
}
}
all = newList.List();
But this is not best way I think, because I should do filter by many criteries. It will work with low speed.
How can I use my functions in Linq query?
Edit:
I showed GetAge() function for example. I have many functions like that. I wanted to know how to use my function.
Well, you can't.
If you want to have criteria used in Where clause of your SQL query, you need to write them directly as a linq.Expression so that entity may parse it and transform it into SQL, not an external function.
Somthing like this works :
DateTime date = DateTime.Now.AddDays(ageFrom);
all = from item in all
where item.BirthDay > date
select item;
Query Expressions are built in to the C# compiler and as such, it only understands the expression that are built in to the compiler.
For example, when you use the where keyword, it converts that to a call to the Where<TSource>(this IQueryable<TSource> source, Func<TSource, bool> predicate) method.
This is true of Linq To Objects and Linq To SQL. What's more, with Linq To SQL, the compiler then has to convert the Query Expression to SQL, which has no way of knowing the definition of your GetAge method.
Or you can use this syntax:
DateTime date = DateTime.Now.AddDays(ageFrom);
all = item.Where(x => x.BirthDay > date).ToList();
Why not use a List<Person>.FindAll() method and pass in a method filter as the predicate?
You would use the method like this.
List<Person> filteredPersons = allPersons.FindAll(FilterPersons);
Below is the a sample method you would use as your filter.
bool FilterPersons(Person p)
{
if(//enter criteria here to determine if you want to select the person)
return true;
else
return false;
}
To do what you want this may be the code you need.
bool FilterPersons(Person p)
{
var itemAge = Common.GetAge(item.BirthDay);
if( itemAge > AgeFrom )
return true;
else
return false;
}
Assuming you can apply filters on the result:
You can apply normal filters ( in linq expressions ) and than apply your functions on the result. Of course, you need to refactor your methods.
Something like this :
var result= Users.Where(s=>s.Name).ToList();
result= MyFilter(result);

Generic List of OrderBy Lambda

I am trying to build a generic query mechanism to access my repository. I wish to use Lambda expressions to filter and sort the query. I am struggling to find a way to pass a list of generic Lambda expressions in, specifically for the order-by, and would appreciate help in doing so.
EDIT: 2 requirements I am trying to meet is, not expose IQueryable beyond the repository, but still be able to carry out some filtering and sorting at database level.
To better illustrate this let me show you the code
public class Query<T>
{
public class OrderBy<T>
{
public Expression<Func<T, **int**>> Clause { set; get; } // Order By clause
public bool Descending = true;
}
public Expression<Func<T, bool>> Where { set; get; } // Where clause
public IList<OrderBy<T>> OrderBys { set; get; } // Where clause
public Query()
{
OrderBys = new List<OrderBy<T>>();
}
}
public IEnumerable<Person> FindBy(Query<Person> query)
{
IQueryable<Person> Temp = GetObjectSet();
if (query.Where != null)
Temp = Temp.Where(query.Where);
foreach (var OrderByThis in query.OrderBys)
{
if (OrderByThis.Descending)
Temp = Temp.OrderByDescending(OrderByThis.Clause);
else
Temp = Temp.OrderBy(OrderByThis.Clause);
}
return Temp.ToList<Person>();
}
This is all very nice, BUT Expression< Func< T, int>> is not generic. I need to be able to do something like:
Query<Person> query = new Query<Person>();
Query<Person>.OrderBy<Person> clause1 = new Query<Person>.OrderBy<Person>();
clause1.Clause = m => m.Username;
Query<Person>.OrderBy<Person> clause2 = new Query<Person>.OrderBy<Person>();
clause2.Clause = m => m.DateOfBirth;
query.OrderBys.Add(clause1);
query.OrderBys.Add(clause2);
i.e. adding a number of different fields of different types.
I imagine there must be a way to store these as generic Lambda functions, and then in the repository convert then to the strongly typed Lambda function it needs.
How can I do this?
As I noted in my answer to your other question, I would discourage this approach in general. It makes more sense just to expose IQueryable<T>/IOrderedQueryable<T>.
That being said, there is a solution along the lines of your intention available in the selected answer to How to pass multiple Expressions to OrderBy for EF? .
It allows you to use a syntax like:
var query = context.Users ... ;
var queryWithOrderBy = ApplyOrderBy(query,
new OrderByExpression<User, string>(expression: u => u.UserName, descending: false), // a string, asc
new OrderByExpression<User, int>(expression: u => u.UserId, descending: true)); // an int, desc
var result = queryWithOrderBy.ToList(); // didn't throw an exception for me
Elaborating on my comment, I don't see why you need to construct your own intermediate query object out of Expressions and then reconstruct Expressions from that intermediate object, when you could just skip that translation altogether.
Given your example query:
repository.FindBy(people => people.OrderBy(p => p.Username).ThenBy(p => p.DateOfBirth));
Take note that you can still build up the queries incrementally, if it is being done based on user selections, for example. The following query is equivalent to the above:
Func<IEnumerable<Person>, IEnumerable<Person>> query = people => people.OrderBy(p => p.Username);
query = query.ThenBy(p => p.DateOfBirth);
I understand that you don't want to expose IQueryable beyond the repository, but you can still use LINQ with a signature such as:
public IEnumerable<Person> FindBy(Func<IEnumerable<Person>, IEnumerable<Person>> query)
{
return query(GetObjectSet()).ToList();
}
Speaking to your actual question, however, you can achieve your OrderBy task by using Expression<Func<T, object>> for the Clause property type, or if that unsettles you, you could constrain it a bit more by using IComparable instead of object, as it is really all you need for ordering, and strings and numeric types all implement it.

Categories

Resources