I was looking at the source code for RacoonBlog trying to find a way in RavenDB to query on a collection contained in a document. I did read about indexes and Map / Reduce and failed to find my answer.
In the PostsController there is an ActionResult called Tag that takes a string parameter and contains the following linq query.
var posts = RavenSession.Query<Post>()
.Include(x => x.AuthorId)
.Statistics(out stats)
.WhereIsPublicPost()
.Where(post => post.TagsAsSlugs.Any(postTag => postTag == slug))
.OrderByDescending(post => post.PublishAt)
.Paging(CurrentPage, DefaultPage, PageSize)
.ToList();
The Where extension method calls TagsAsSlugs and performs an Any, TagsAsSlugs looks like this.
public IEnumerable<string> TagsAsSlugs
{
get
{
if (Tags == null)
yield break;
foreach (var tag in Tags)
{
yield return SlugConverter.TitleToSlug(tag);
}
}
}
So since the TagsAsSlugs property loops over the collection of tags does the query require that all posts are returned so that each post can have its Tags collection iterated over?
I doubt this is the case since Oren's blog is so fast.
Jackson,
No, that is NOT how it works. We are doing the work during indexing (the TagsAsSlugs is actually computed on save time), and then we save TagsAsSlugs into the index.
We query the index for tags that exists there.
In short, we don't do any computation, certainly not on the client side.
Related
I have a DbSet<Items> collection.
The primary key is a Guid. I don't want to order by this primary key. I want to order by an editable decimal property named "Order".
The code I have is very simple, and it works great until the user puts a "$top" parameter into the request:
public class ItemsController : ApiController
{
protected DbContext ctx = // ...
// GET api/documents
[EnableQuery()]
public IQueryable<Item> Get()
{
return ctx.Items.OrderBy(o => o.Order).AsQueryable();
}
When the user puts "$top" into the query string, the order gets all messed up (it presumably forces the ordering to be done by the primary key, for consistent paging results -- however, in my situation, this is having the opposite effect, it's preventing me from having consistent paging results).
I've tried moving .AsQueryable() to be earlier in the query (before the .OrderBy(...) clause), I've tried it without the .AsQueryable(), I've tried it with two AsQueryables, etc.
There are going to be a lot of items in this table, so it needs to be done via an IQueryable (enumerating all of the items on the web server via IEnumerable is not an option here).
The only thing that has worked so far is passing in "$orderby=Order" from the client, but I don't want to force that (seems like it will get forgotten easily).
1.) Is there anything I can do to make ordering by my Order property the default behavior here?
2.) Or failing that, is there anyway to trick WebApi / OData into thinking that a custom "$orderby=Order" clause was specified?
To override default sort order, you need to set property EnsureStableOrdering of EnableQueryAttribute to false, like describe here:
A true value indicates the original query should be modified when
necessary to guarantee a stable sort order. A false value indicates
the sort order can be considered stable without modifying the query.
Query providers that ensure a stable sort order should set this value
to false. The default value is true.
So in your code, changes the action attribute like this:
// GET api/documents
[EnableQuery(EnsureStableOrdering = false)]
public IQueryable<Item> Get()
{
return ctx.Items.OrderBy(o => o.Order).AsQueryable();
}
You can manually invoke the odata in your controller. This should create the proper sorted IQueryable and then apply the $top and any other odata like $filter and $skip. Now you don't have to return an IQueryable which was causing the problem because the actual query was being executed later in the pipeline.
public class ItemsController : ApiController
{
protected DbContext ctx = // ...
public IEnumerable<Item> Get(ODataQueryOptions<Item> odata)
{
var collection = ctx.Items.OrderBy(o => o.Order);
if (odata == null)
{
//return a default max size of 100
return collection.Take(100).ToList();
}
var results = odata.ApplyTo(collection.AsQueryable()) as List<Item>;
//still provide a max incase the $top wasn't specified.
//you could check the odata to see if $top is there or not.
return results.Take(100);
}
}
More information can be found in the WebApi documentation.
i have the following action method:-
[AcceptVerbs(HttpVerbs.Post)]
public PartialViewResult Search(string q, int classid)
{
var users = r.searchusers(q, classid);
// code does here..............
which calls the following model repository method:-
public IQueryable<User> searchusers(string q, int id)
{
return from u in entities1.Users
where (!u.Users_Classes.Any(c => c.ClassID == id) && (u.UserID.Contains(q))
select u;
}
now if i change the IQueryable to IEnumerable as follow , will there be any changes on how the query will be executed in this case ?:-
public IEnumerable<User> searchusers(string q, int id)
{
return from u in entities1.Users
where (!u.Users_Classes.Any(c => c.ClassID == id) && (u.UserID.Contains(q))
select u;
}
Yes, in my testing once you cast to IEnumerable, that determines the query SQL. Any additional query composition you do after that will be done in memory after the query is executed.
So suppose you have a base query that loads a list of users and returns an IEnumerable. Then before you actually run through that list (thereby executing the query), you also add a .Where(i=>i.username='bob'). In that case, it will execute the whole select, and then apply a LINQ-to-Objects in memory filter for the "where username='bob'" part, which is probably not what you want, instead you want the whole thing to be run as part of the SQL statement.
So yes, always use IQueryable whenever you can so that your fully composed are run at once.
Yes it will change the query, you want to use the IQuerable for anything that uses a remote datasource.
In your case it will force linq to execute the query, where as IQuerable would wait until someone else to execute the query. IQuerable allows them, if they desire, to append more conditions to push down to the database for execution.
I generally enforce IEnumerable at the layer boundary, places where I dont want people modifying the queries that the system is going to generate.
The Linq query "order by" is not working and I've followed all the suggestions found on your site and other sites. Any assistance would be appreciated.
[WebGet]
public IQueryable<vw_providercharge_providers> GetChargeProviders(int submitted)
{
var results = (from p in this.CurrentDataSource.vw_providercharge_providers
where p.submitted == submitted
orderby p.fullname
select p);
return results;
}
Thanks for your input!
Yes, this is a WebGet method for a WCF data service. I get a 400 error if I don't return an IQueryable type, so I modified your suggestion a little. Unfortunately, it still seems to disregard any order-by.
[WebGet]
public IQueryable<vw_providercharge_providers> GetChargeProviders(int submitted)
{
var results = (from p in this.CurrentDataSource.vw_providercharge_providers
where p.submitted == submitted
orderby p.fullname
select p).ToArray();
results.OrderBy(p => p.patientname);
return results;
}
I notice you return an IQueryable<T> - are you calling any LINQ methods on the result before you enumerate it?
Not all LINQ methods preserve order. Most commonly, calling Distinct() after you do the ordering will destroy the order.
Since your method is a marked with a WebGet attribute, I'm assuming that you are calling this method from a Web endpoint, therefore you may need to collapse the collection prior to send it through internet.
Try:
[WebGet]
public vw_providercharge_providers[] GetChargeProviders(int submitted)
{
var results = (from p in this.CurrentDataSource.vw_providercharge_providers
where p.submitted == submitted
orderby p.fullname
select p).ToArray();
return results;
}
This way you have the guarantee that the GetChargeProviders method returns and array instead of an linq expression.
Regards,
I found the cause of the issue.
I had not set the "fullname" column as an Entity Key for the "vw_providercharge_providers" data model entity. Only the identity column was set as an Entity Key. I didn't realize that was a requirement to use it in an order by clause.
Thanks again for your input.
I am using dynamic linq to make a generic class for processing a generic JqGrid from MVC all works fine (searching, pagination etc) except for sorting on code properties. Sorting works fine when I am hitting the DB to sort the data, but as soon as it is a property I have made the sorting does not work eg
public partial class tblStockOrder
{
public string approved
{
get
{
return approved_id == null ? "" : "Approved";
}
}
}
I am running the following Dynamic Linq
items = items
.OrderBy(string.Format("{0} {1}", sidx, sord))
.Skip(pageIndex * pageSize)
.Take(pageSize);
Where sidx etc are strings passed in by jquery.
So basically what is the best solution for handling a case where some properties will be from the db while others will be code properties (not sure of the correct naming). I can handle all this in code using reflection but would obviously like the DB to handle as much of the searching / sorting as possible without pulling in thousands of records and sorting through them in code using reflection.
Computed class will of course not work, as you're trying to create record which is part in memory, part in database.
You can, however, compute the same on database by specifying the function in linq query, example:
items = items
.OrderBy(x=> x.approved_id != null )
.Skip(pageIndex * pageSize)
.Take(pageSize);
I am just getting started with LINQ. I am creating an Entity Framework app that uses the canonical Post and Tag model. A Post contains an ID, Text, and Tags, and a Tag contains an ID, a Name, and Posts.
A previous thread on StackOverflow showed me how to query for a Post that matches all Tag objects (A and B and C) in a search list. But how would I query for a Post that matches any Tag (A or B or C) in the list? Thanks for your help.
Stumbled over the answer right after I posted this question. PredicateBuilder to the rescue!
Here's my code, which uses PredicateBuilder. It is set up as an extension method:
public static IQueryable<Note> WhereContainsAnyTags(this IQueryable<Note> notes, IEnumerable<Tag> searchTags)
{
// Initialize
var predicate = PredicateBuilder.False<Note>();
// Select Notes that contain any search Tags
foreach (var searchTag in searchTags)
{
var tag = searchTag;
predicate = predicate.Or(note => note.Tags.Any(t => t.Id == tag.Id));
}
// Set return value
return notes.AsExpandable().Where(predicate);
}
And here is how I call the code:
searchResults = m_ViewModel.ObjectContext.Notes.WhereContainsAnyTags(m_ViewModel.SearchTags);
Not sure if this would work or not, but worth a try anyway I guess if you are already using WhereIn.
var posts = context.Tags.WhereIn(tag => tag.Name, acceptableValues)
.SelectMany(t => t.Posts);
The WhereIn should give you all the tags that are part of the name, and the SelectMany should give you all the posts containing those tags.
You could aslo do it like this with Entity SQL
var post = ctx.Posts.Where("it.Tags.Id IN (1,2,3)");