I'm experimenting with linq and generics. For now, I just implemented a GetAll method which returns all records of the given type.
class BaseBL<T> where T : class
{
public IList<T> GetAll()
{
using (TestObjectContext entities = new TestObjectContext(...))
{
var result = from obj in entities.CreateObjectSet<T>() select obj;
return result.ToList();
}
}
}
This works fine. Next, I would like to precompile the query:
class BaseBL<T> where T : class
{
private readonly Func<ObjectContext, IQueryable<T>> cqGetAll =
CompiledQuery.Compile<ObjectContext, IQueryable<T>>(
(ctx) => from obj in ctx.CreateObjectSet<T>() select obj);
public IList<T> GetAll()
{
using (TestObjectContext entities = new TestObjectContext(...))
{
var result = cqGetAll.Invoke(entities);
return result.ToList();
}
}
}
Here, i get the following:
base {System.Exception} = {"LINQ to Entities does not recognize the method
'System.Data.Objects.ObjectSet`1[admin_model.TestEntity] CreateObjectSet[TestEntity]()'
method, and this method cannot be translated into a store expression."}
What is the problem with this? I guess the problem is with the result of the execution of the precompiled query, but I am unable to fanthom why.
I had this exception when I used methods inside the LINQ query that are not part of the entity model. The problem is that the precompiled query can't invoke the CreateObjectSet for the type TestEntity because the precompiled query is not part of the context that is used to invoke it.
Related
I'm trying to create a Repository for an Object Page.
I would like to have non-generic LINQ queries but can't seem to get them working,
I am only able to create them if they are generic and in a helper class, and please let me know if it is recommended to have this type of function in a Helper Class, let me know the reasoning if so.
public class PageRepository
{
public PageViewModel GetPageById(string oid)
{
using (var db = new AppDbContext())
{
Page page = db.Pages
//This is what I cannot get working
.WhereStatusType(StatusType.Published);
return GetPageView(page);
}
}
//I would like the below to work
//Tried generic (below), also non-generic <Page> but doesn't work
internal static IQueryable<T> WhereStatusType<T>(this IQueryable<T> query, StatusType statusType = StatusType.Published)
where T : Page
{
return query.Where(p => p.StatusType == statusType);
}
}
//Below is working, but don't want it in a helper
public static class PageHelper
{
internal static IQueryable<T> WhereStatusType<T>(this IQueryable<T> query, StatusType statusType = StatusType.Published)
where T : Page
{
return query.Where(p => p.StatusType == statusType);
}
}
When I attempt to make the IQueryable non-generic, I receive the following errors:
public class PageRepository has an issue:
Extension method must be defined in a non-generic static class
Also:
'IQueryable' does not contain a definition for 'WhereStatusType' and no extension method 'WhereStatusType' accepting a first argument of type 'IQueryable' could be found (are you missing a using directive or an assembly reference?)
I'm sure it is a simple solution, thank you for the help. Also, if I am approaching this incorrectly, any advice would be greatly appreciated.
Edit:
While FirstOfDefault() is suggested for the above code, I am also looking to use the function when returning List<PageViewModel> (I may have over-simplified the code/example), for example:
public List<PageViewModel> GetPages()
{
using (var context = new AppDbContext())
{
List<Page> pages = new List<Page>();
pages = context.Pages.AsNoTracking()
.WhereStatusType(StatusType.Published)
.ToList();
if (pages != null)
{
List<PageViewModel> pageViewModels = new List<PageViewModel>();
foreach (Page p in pages)
pageViewModels.Add(GetPageView(p));
return pageViewModels;
}
else
return null;
}
}
The error message tells you exactly what's going on here. If you want an extension method -- a method whose first parameter uses the this keyword, like your example -- then it needs to be defined in a non-generic static class, like your PageHelper.
If you don't like that approach, you could define a non-extension method, something like this:
public class PageRepository
{
public PageViewModel GetPageById(string oid)
{
using (var db = new AppDbContext())
{
Page page = GetPagesWithStatus(db.Pages, StatusType.Published);
return GetPageView(page);
}
}
internal IQueryable<Page> GetPagesWithStatus(IQueryable<Page> query, StatusType statusType)
{
return query.Where(p => p.StatusType == statusType);
}
}
As #asherber mentioned, your LINQ query isn't right. You are comparing p.Alias to p.Alias
Also, it looks like you want a FirstOrDefault() in there.
I wrote a method that extends System.Linq class. My ContainsAnyWord method allows me to search all words in a string against a string instead of comparing one string to another.
Here is the method that I wrote to extend Linq
public static class LinqExtension
{
public static bool ContainsAnyWord(this string value, string searchFor)
{
if (!string.IsNullOrWhiteSpace(value) && !string.IsNullOrWhiteSpace(searchFor))
{
value = value.ToLower();
IEnumerable<string> tags = StopwordTool.GetPossibleTags(searchFor);
return tags.Contains(value);
}
return false;
}
}
This extension method works great on an IEnumerable object. But when I want to use it on IQueryable object I get the following error
LINQ to Entities does not recognize the method 'Boolean
ContainsAnyWord(System.String, System.String)' method, and this method
cannot be translated into a store expression.
The following example works great because I am working with a list.
using(var conn = new AppContext())
{
var allUsers = conn.Users.GetAll().ToList();
var foundUsers = allUsers.Where
(
user =>
user.FirstName.ContainsAnyWord("Some Full Name Goes Here")
|| user.LastName.ContainsAnyWord("Some Full Name Goes Here")
).ToList();
}
But the following example does not work because I am working with IQueryable which gives me the error listed above.
using(var conn = new AppContext())
{
var allUsers = conn.Users.Where
(
user =>
user.FirstName.ContainsAnyWord("Some Full Name Goes Here")
|| user.LastName.ContainsAnyWord("Some Full Name Goes Here")
).ToList();
}
How can I fix this issue and make this method available for IQueryable object?
UPDATED
Based on the feedback I got below, I tried to implement this using IQueryable like so
public static IQueryable<T> ContainsAnyWord<T>(this IQueryable<T> query, string searchFor)
{
if (!string.IsNullOrWhiteSpace(searchFor))
{
IEnumerable<string> tags = StopwordTool.GetPossibleTags(searchFor);
return query.Where(item => tags.Contains(item));
}
return query;
}
But this is also giving me an error
You have to keep in mind that this has to be translated to SQL at the end.
Obviously ContainsAnyWord cannot be translated to SQL...
So ,save your names in a List/Array and try
user => yourlist.Contains( user.FirstName)
EF will translate this to a WHERE ..IN
There is no need for a method but if for some reason you want it
internal static class MyClass2
{
public static IQueryable<T> ContainsAnyWord<T>(this IQueryable<T> value, string searchFor) where T: User
{
var names = searchFor.Split(' ').ToList();
return value.Where(u => names.Contains(u.DisplayName));
}
}
You can use it like
var result=conn.Users.ContainsAnyWord("abc def ght");
Linq to Entities converts Methods into database expressions. You can not implement an extension to System.String and expect it to be translated into a T-SQL expression, wich is what internally Entity Framework does.
My suggestion is trying to work around using pre-defined Linq methods. Try using Contains method.
You can't call methods inside the query expression the expression parser provided by the Linq to Entites does not know how to handle. The only thing you can really do is write a extension method that takes in a IQueryable<T> and build up the expression internally inside the function instead of calling Where(
This below code is totally untested and is something I came up with off the top of my head. But you would need to do something like this
public static IQueryable<T> WhereContainsAnyWord<T>(this IQueryable<T> #this, Expression<Func<T, string>> selector, string searchFor)
{
var tags = StopwordTool.GetPossibleTags(searchFor); //You might need a .ToArray() here.
var selectedItems = #this.GroupBy(selector);
var filteredItems = selectedItems.Where(selectedItem => tags.Contains(selectedItem.Key.ToLower()));
var result = filteredItems.SelectMany(x => x);
return result;
}
used like
using(var conn = new AppContext())
{
var allUsers = conn.Users.WhereContainsAnyWord(user => user.FirstName, "Some Full Name Goes Here").ToList();
}
Here is a simpiler non generic version
public static IQueryable<User> WhereContainsAnyWord(this IQueryable<User> #this, string searchFor)
{
var tags = StopwordTool.GetPossibleTags(searchFor); //You might need a .ToArray() here.
return #this.Where(user => tags.Contains(user.DisplayName.ToLower()));
}
used like
using(var conn = new AppContext())
{
var allUsers = conn.Users.WhereContainsAnyWord("Some Full Name Goes Here").ToList();
}
This question already has answers here:
C# Pass Lambda Expression as Method Parameter
(4 answers)
Closed 9 years ago.
I'm writing a layered ASP.Net Application which consist of Bussiness layer, Repository layer, service Layer... . In repository layer I'm using EntityFramework as ORM. In service layer, I want to pass a query in lambda form (which includes OrderBy or OrderByDescending ,take, skip,...) to repository layer and run the query on an DbSet and return result entities.
In Simple words : (How I can do something like the following mock code in asp.net c#)
public class Repository
{
public List<Book> findby(var query)
{
var dbcontext = DataContextFactory.GetDataContext();
//The following line should do this : dbcontext.Books.Where(B=>B.New==true && B.Id>99).OrderBy(B=>B.Date).ThenBy(B=>B.Id).Skip(2).Take(10);
List<Book> matchedBooks = RunQueryOnBooks(dbcontext.Books,query);
return matchedBooks;
}
}
public class Service
{
public List<Book> getTopNewBooks(Repository _repository)
{
var query = Where(B=>B.New==true && B.Id>99).OrderBy(B=>B.Date).ThenBy(B=>B.Id).Skip(2).Take(10);
List<Book> matchedBooks = _repository.findby(query);
return matchedBooks;
}
}
So the question is:
which type I should use instead of var for query (If there is any and is possible)
how I execute the query on dbcontext.Books ?
Please give a good and easy example like mine and more references. thanks in advance.
I think you probably want simply a Func<IQueryable<Book>, IQueryable<Book>>:
void RunQueryOnBooks(DbSet<Book> set, Func<IQueryable<Book>, IQueryable<Book>> query)
{
return query(set.AsQueryable());
}
The query input could then be a lambda:
Repository.FindBy(set => set.Where(B => B.new == true ... ));
LINQ uses Lambda Expressions, which can be represented as Funcs. As long as you build a Func that returns the appropriate type and takes the appropriate parameter type, you'll be all set.
var myFunc = new Func<IEnumerable<Book>, IEnumerable<Book>>(book => book.Where(i => true /* rest of your code here */));
var test = Enumerable.Empty<Book>();
var result = myFunc(test);
If you think about it, what you're saying is that you want to take some collection of books as input and give back some filtered/ordered collection of books back. Thus, the appropriate type for query would be Func<IEnumerable<Books>, IEnumerable<Books>.
With that in mind, I'd probably make the query a function (getTopNewBooks) instead of a lambda, but both will work. Either way, you can pass the delegate method to the findby function as something like:
public class Application
{
public static void Main()
{
var repo = new Repository();
foreach (var topBook in repo.findby(Service.getTopNewBooks))
{
// This is a matching top book
}
}
}
public class Repository
{
public List<Book> findby(Func<IEnumerable<Book>,IEnumerable<Book>> query)
{
var dbcontext = DataContextFactory.GetDataContext();
List<Book> matchedBooks = query(dbcontext.Books).ToList();
return matchedBooks;
}
}
public class Service
{
public static IEnumerable<Book> getTopNewBooks(IEnumerable<Book> input)
{
return input.Where(B=>B.New==true && B.Id>99).OrderBy(B=>B.Date).ThenBy(B=>B.Id).Skip(2).Take(10);
}
}
If you hover over the "var" in Visual Studio, it will tell you the inferred type. You can use that to show what type your query ends up being.
In my Custom ObjectContext class I have my entity collections exposed as IObjectSet so they can be unit-tested. I have run into a problem when I use this ObjectContext in a compiled query and call the "Include" extension method (From Julie Lerman's blog http://thedatafarm.com/blog/data-access/agile-entity-framework-4-repository-part-5-iobjectset/) and in her book Programming Entity Framework 2nd edition on pages 722-723.
Here is the code:
Query:
public class CommunityPostsBySlugQuery : QueryBase<IEnumerable<CommunityPost>>
{
private static readonly Expression<Func<Database, string, IEnumerable<CommunityPost>>> expression = (database, slug) => database.CommunityPosts.Include("Comments").Where(x => x.Site.Slug == slug).OrderByDescending(x => x.DatePosted);
private static readonly Func<Database, string, IEnumerable<CommunityPost>> plainQuery = expression.Compile();
private static readonly Func<Database, string, IEnumerable<CommunityPost>> compiledQuery = CompiledQuery.Compile(expression);
private readonly string _slug;
public CommunityPostsBySlugQuery(bool useCompiled, string slug): base(useCompiled)
{
_slug = slug;
}
public override IEnumerable<CommunityPost> Execute(Database database)
{
return base.UseCompiled ? compiledQuery(database, _slug) : plainQuery(database, _slug);
}
}
Extension
public static class ObjectQueryExtension
{
public static IQueryable<T> Include<T>(this IQueryable<T> source, string path)
{
var objectQuery = source as ObjectQuery<T>;
return objectQuery == null ? source : objectQuery.Include(path);
}
}
LINQ to Entities does not recognize the method 'System.Linq.IQueryable1[MyPocoObject] Include[MyIncludedPocoObject](System.Linq.IQueryable1[MyPocoObject], System.String)' method, and this method cannot be translated into a store expression.
If I use this same query on ObjectSet collections rather than IObjectSet it works fine. If I simply run this query without precompiling it works fine. What am I missing here?
I really don't know but have asked if someone on the EF team can answer it.
Response by EF Team:
This is a known issue with CTP4, Include is an instance method on ObjectSet but when your set is typed as IObjectSet you are actually using an extension method on IQueryable that is included in CTP4. This extension method doesn't work with compiled queries but we will try and support this in the next release.
I'm using T4 for generating repositories for LINQ to Entities entities.
The repository contains (amongst other things) a List method suitable for paging. The documentation for Supported and Unsupported Methods does not mention it, but you can't "call" Skip on a unordered IQueryable. It will raise the following exception:
System.NotSupportedException: The method 'Skip' is only supported for
sorted input in LINQ to Entities. The method 'OrderBy' must be called before
the method 'Skip'..
I solved it by allowing to define a default sorting via a partial method. But I'm having problems checking if the expression tree indeed contains an OrderBy.
I've reduced the problem to as less code as possible:
public partial class Repository
{
partial void ProvideDefaultSorting(ref IQueryable<Category> currentQuery);
public IQueryable<Category> List(int startIndex, int count)
{
IQueryable<Category> query = List();
ProvideDefaultSorting(ref query);
if (!IsSorted(query))
{
query = query.OrderBy(c => c.CategoryID);
}
return query.Skip(startIndex).Take(count);
}
public IQueryable<Category> List(string sortExpression, int startIndex, int count)
{
return List(sortExpression).Skip(startIndex).Take(count);
}
public IQueryable<Category> List(string sortExpression)
{
return AddSortingToTheExpressionTree(List(), sortExpression);
}
public IQueryable<Category> List()
{
NorthwindEntities ent = new NorthwindEntities();
return ent.Categories;
}
private Boolean IsSorted(IQueryable<Category> query)
{
return query is IOrderedQueryable<Category>;
}
}
public partial class Repository
{
partial void ProvideDefaultSorting(ref IQueryable<Category> currentQuery)
{
currentQuery = currentQuery.Where(c => c.CategoryName.Contains(" ")); // no sorting..
}
}
This is not my real implementation!
But my question is, how could I implement the IsSorted method? The problem is that LINQ to Entities query's are always of the type ObjectQuery, which implements IOrderedQueryable.
So how should I make sure an OrderBy method is present in the expression tree? Is the only option to parse the tree?
Update
I've added two other overloads to make clear that it's not about how to add sorting support to the repository, but how to check if the ProvideDefaultSorting partial method has indeed added an OrderBy to the expression tree.
The problem is, the first partial class is generate by a template and the implementation of the second part of the partial class is made by a team member at another time. You can compare it with the way the .NET Entity Framework generates the EntityContext, it allows extension points for other developers. So I want to try to make it robust and not crash when the ProvideDefaultSorting is not implemented correctly.
So maybe the question is more, how can I confirm that the ProvideDefaultSorting did indeed add sorting to the expression tree.
Update 2
The new question was answered, and accepted, I think I should change the title to match the question more. Or should I leave the current title because it will lead people with the same problem to this solution?
Paging depends on Ordering in a strong way. Why not tightly couple the operations? Here's one way to do that:
Support objects
public interface IOrderByExpression<T>
{
ApplyOrdering(ref IQueryable<T> query);
}
public class OrderByExpression<T, U> : IOrderByExpression<T>
{
public IQueryable<T> ApplyOrderBy(ref IQueryable<T> query)
{
query = query.OrderBy(exp);
}
//TODO OrderByDescending, ThenBy, ThenByDescending methods.
private Expression<Func<T, U>> exp = null;
//TODO bool descending?
public OrderByExpression (Expression<Func<T, U>> myExpression)
{
exp = myExpression;
}
}
The method under discussion:
public IQueryable<Category> List(int startIndex, int count, IOrderByExpression<Category> ordering)
{
NorthwindEntities ent = new NorthwindEntities();
IQueryable<Category> query = ent.Categories;
if (ordering == null)
{
ordering = new OrderByExpression<Category, int>(c => c.CategoryID)
}
ordering.ApplyOrdering(ref query);
return query.Skip(startIndex).Take(count);
}
Some time later, calling the method:
var query = List(20, 20, new OrderByExpression<Category, string>(c => c.CategoryName));
You can address this in the return type of ProvideDefaultSorting. This code does not build:
public IOrderedQueryable<int> GetOrderedQueryable()
{
IQueryable<int> myInts = new List<int>() { 3, 4, 1, 2 }.AsQueryable<int>();
return myInts.Where(i => i == 2);
}
This code builds, but is insidious and the coder gets what they deserve.
public IOrderedQueryable<int> GetOrderedQueryable()
{
IQueryable<int> myInts = new List<int>() { 3, 4, 1, 2 }.AsQueryable<int>();
return myInts.Where(i => i == 2) as IOrderedQueryable<int>;
}
Same story with ref (this does not build):
public void GetOrderedQueryable(ref IOrderedQueryable<int> query)
{
query = query.Where(i => i == 2);
}
I'm afraid it's a bit harder than that. You see, the Entity Framework will, in certain circumstances, silently ignore an OrderBy. So it isn't enough to just look for an OrderBy in the expression tree. The OrderBy has to be in the "right" place, and the definition of the "right" place is an implementation detail of the Entity Framework.
As you may have guessed by now, I'm in the same place as you are; I'm using the entity repository pattern and doing a Take/Skip on the presentation layer. The solution I have used, which is perhaps not ideal, but good enough for what I'm doing, is to not do any ordering until the last possible moment, to ensure that the OrderBy is always the last thing into the expression tree. So any action which is going to do a Take/Skip (directly or indirectly) inserts an OrderBy first. The code is structured such that this can only happen once.
Thanks to David B I've got a the following solution. (I had to add detection for the situation where the partial method was not executed or just returned it's parameter).
public partial class Repository
{
partial void ProvideDefaultSorting(ref IOrderedQueryable<Category> currentQuery);
public IQueryable<Category> List(int startIndex, int count)
{
NorthwindEntities ent = new NorthwindEntities();
IOrderedQueryable<Category> query = ent.CategorySet;
var oldQuery = query;
ProvideDefaultSorting(ref query);
if (oldQuery.Equals(query)) // the partial method did nothing with the query, or just didn't exist
{
query = query.OrderBy(c => c.CategoryID);
}
return query.Skip(startIndex).Take(count);
}
// the rest..
}
public partial class Repository
{
partial void ProvideDefaultSorting(ref IOrderedQueryable<Category> currentQuery)
{
currentQuery = currentQuery.Where(c => c.CategoryName.Contains(" ")).OrderBy(c => c.CategoryName); // compile time forced sotring
}
}
It ensures at compile time that if the partial method is implemented, it should at least keep it an IOrderdQueryable.
And when the partial method is not implemented or just returns its parameter, the query will not be changed, and that will use the fallback sort.
ProvideDefaultSorting(ref query);
if (!IsSorted(query))
{
query = query.OrderBy(c => c.CategoryID);
}
Change to:
//apply a default ordering
query = query.OrderBy(c => c.CategoryID);
//add to the ordering
ProvideDefaultSorting(ref query);
It's not a perfect solution.
It doesn't solve the "filter in the ordering function" problem you've stated. It does solve "I forgot to implement ordering" or "I choose not to order".
I tested this solution in LinqToSql:
public void OrderManyTimes()
{
DataClasses1DataContext myDC = new DataClasses1DataContext();
var query = myDC.Customers.OrderBy(c => c.Field3);
query = query.OrderBy(c => c.Field2);
query = query.OrderBy(c => c.Field1);
Console.WriteLine(myDC.GetCommand(query).CommandText);
}
Generates (note the reverse order of orderings):
SELECT Field1, Field2, Field3
FROM [dbo].[Customers] AS [t0]
ORDER BY [t0].[Field1], [t0].[Field2], [t0].[Field3]
I have implemented a solution that sorts whatever collection by its primary key as the default sort order is not specified. Perhaps that will work for you.
See http://johnkaster.wordpress.com/2011/05/19/a-bug-fix-for-system-linq-dynamic-and-a-solution-for-the-entity-framework-4-skip-problem/ for the discussion and the general-purpose code. (And an incidental bug fix for Dynamic LINQ.)