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.
Related
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.)
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.
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);
This is linq-to-sql
I have a lot of different classes all doing the same query, but projecting the results slightly differently. Ideally I'd like to be able to have the query in one place, and have the projection passed into the Select method. It works fine for concrete types:
public void GetResults() {
var junk = db.SiteProducts.Select(Project());
}
public Expression<Func<DbEntities.SiteProduct, string>> Project() {
return p => p.ProductAlert;
}
But when I try to return an anonymous type, it fails
public void GetResults() {
var junk = db.SiteProducts.Select(Project());
}
public Expression<Func<DbEntities.SiteProduct, TResult>> Project<TResult>() {
return p => new { p.ProductAlert };
}
I fully understand why generic type inference is failing in the second case. But is there a trick—short of crafting my own Expressions from the ground up—I'm missing that could get this to work?
This is an intriguing question. I think a DTO can help you out here, but there are limitations and pitfalls to watch out for. Take the following LINQPad Example:
class ProjectDTO
{
public string Name { get; set; }
public static Expression<Func<Project, ProjectDTO>> ToDTO = (e) => new ProjectDTO
{
Name = e.Name
};
public ProjectDTO() {}
public ProjectDTO(Project project)
{
Name = project.Name;
}
}
void Main()
{
Projects.Select(p => p.Name).Dump();
Projects.Select(ProjectDTO.ToDTO).Dump();
Projects.Select(p => new ProjectDTO(p)).Dump();
}
SQL Generated:
SELECT [t0].[Name]
FROM [Project] AS [t0]
GO
SELECT [t0].[Name]
FROM [Project] AS [t0]
GO
SELECT [t0].[ProjectId], [t0].[Name], [t0].[Description], [t0].[DateCreated], [t0].[DateModified], [t0].[DateComplete], [t0].[CreatedBy]
FROM [Project] AS [t0]
As you can see, you cannot use a copy-constructor to assign the properties of the DTO as this forces the entire object to be pulled back from the database.
This also slightly limiting if you wanted to extend the base DTO and add more properties for more specialised views of the data, which means you could end up with multiple Expression's with similar code.
However, I quite like option two, but i'm sure this option is quite likely restricted to single type projections, consider the following example:
var query = from p in Projects
join t in Tasks on p.ProjectId equals t.ProjectId
select ProjectDTO.ToDTO; //Can't be used like this
I don't think you can use the Expression in this type of query-syntax. Generally speaking, I don't think there will be a solution that works across the board. You may have to review your design to see if you can provide less projections, based on some of the properties being very cheap to always include in the query?
Without using the Dynamic LINQ library or building the expression tree manually, I would also like to see if it is possible with LINQ-SQL/LINQ-Entities to create dynamic selects.
If I understand your question correctly you can use this code:
first declare a method for selecting your data like this:
public List<TResult> FindAll<TResult>(Func<Regions, TResult> selector) where TResult : class
{
using (RepositoryDataContext = new DataClasses1DataContext())
{
return RepositoryDataContext.Regions.Select<Regions, TResult>(selector).ToList<TResult>();
}
}
then you can build your select statement like this:
Func<Regions, SelectAllRegion> select = r => new SelectAllRegion
{
RegionID = r.RegionID,
RegionDescription = r.RegionDescription
};
my SelectAllRegion :
public class SelectAllRegion
{
public SelectAllRegion()
{
}
public int RegionID { get; set; }
public string RegionDescription { get; set; }
}
and region is Region table in northwing.I hope this help you
IdeaBlade has a ProjectionSelector class that you can use to abstract your projections. When you need to construct a projection query but you don't know the types involved at compile time, you can create an instance of the ProjectionSelector class and pass in the type information at runtime.
The class, and sample code, can be found here:
Create dynamic "Select", "SelectMany" and "GroupBy" clauses
http://drc.ideablade.com/xwiki/bin/view/Documentation/dynamic-projection
This will not work at compile-time. Using dynamic stuff you can make it work of course.
A simple solution is not to use an anonymous type but a custom-made DTO class. Such a DTO class only takes very few lines and is easy to maintain. Usually this is a good solution.
I have asked this question about using the a Linq method that returns one object (First, Min, Max, etc) from of a generic collection.
I now want to be able to use linq's Except() method and I am not sure how to do it. Perhaps the answer is just in front on me but think I need help.
I have a generic method that fills in missing dates for a corresponding descriptive field. This method is declared as below:
public IEnumerable<T> FillInMissingDates<T>(IEnumerable<T> collection, string datePropertyName, string descriptionPropertyName)
{
Type type = typeof(T);
PropertyInfo dateProperty = type.GetProperty(datePropertyName);
PropertyInfo descriptionProperty = type.GetProperty(descriptionPropertyName);
...
}
What I want to accomplish is this. datePropertyName is the name of the date property I will use to fill in my date gaps (adding default object instances for the dates not already present in the collection). If I were dealing with a non-generic class, I would do something like this:
foreach (string description in descriptions)
{
var missingDates = allDates.Except(originalData.Where(d => d.Description == desc).Select(d => d.TransactionDate).ToList());
...
}
But how can I do the same using the generic method FillInMissingDates with the dateProperty and descriptionProperty properties resolved in runtime?
I think the best way would be to define an interface with all of the properties that you want to use in your method. Have the classes that the method may be used in implement this interface. Then, use a generic method and constrain the generic type to derive from the interface.
This example may not do exactly what you want -- it fills in missing dates for items in the list matching a description, but hopefully it will give you the basic idea.
public interface ITransactable
{
string Description { get; }
DateTime? TransactionDate { get; }
}
public class CompletedTransaction : ITransactable
{
...
}
// note conversion to extension method
public static void FillInMissingDates<T>( this IEnumerable<T> collection,
string match,
DateTime defaultDate )
where T : ITransactable
{
foreach (var trans in collection.Where( t => t.Description = match ))
{
if (!trans.TransactionDate.HasValue)
{
trans.TransactionDate = defaultDate;
}
}
}
You'll need to Cast your enumeration to ITransactable before invoking (at least until C# 4.0 comes out).
var list = new List<CompletedTransaction>();
list.Cast<ITransactable>()
.FillInMissingDates("description",DateTime.MinValue);
Alternatively, you could investigate using Dynamic LINQ from the VS2008 Samples collection. This would allow you to specify the name of a property if it's not consistent between classes. You'd probably still need to use reflection to set the property, however.
You could try this approach:
public IEnumerable<T> FillInMissingDates<T>(IEnumerable<T> collection,
Func<T, DateTime> dateProperty, Func<T, string> descriptionProperty, string desc)
{
return collection.Except(collection
.Where(d => descriptionProperty(d) == desc))
.Select(d => dateProperty(d));
}
This allows you to do things like:
someCollection.FillInMissingDates(o => o.CreatedDate, o => o.Description, "matching");
Note that you don't necessarily need the Except() call, and just have:
.. Where(d => descriptionProperty(d) != desc)
foreach (string description in descriptions)
{
var missingDates = allDates.Except<YourClass>(originalData.Where(d => d.Description == desc).Select(d => d.TransactionDate).ToList());
}
In fact, almost all LINQ extension in C# have a generic possible value. (Except and Except)
If you're going to identify the property to be accessed by a string name, then you don't need to use generics. Their only purpose is static type safety. Just use reflection to access the property, and make the method work on a non-generic IEnumerable.
Getting Except result with multiple properties working with custom data class is not allowed.
You have to use it like this: (given in msdn 101 LINQ Samples)
public void Linq53()
{
List<Product> products = GetProductList();
List<Customer> customers = GetCustomerList();
var productFirstChars =
from p in products
select p.ProductName[0];
var customerFirstChars =
from c in customers
select c.CompanyName[0];
var productOnlyFirstChars = productFirstChars.Except(customerFirstChars);
Console.WriteLine("First letters from Product names, but not from Customer names:");
foreach (var ch in productOnlyFirstChars)
{
Console.WriteLine(ch);
}
}
Having the key, you can handle your data accordingly :)