LINQ query for tag system: Matching any of several tags? - linq

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)");

Related

MVC 3 controller GroupBy View error

Im creating an MVC 3 view from a controller. My model "MyList", contains a large number of records. When I create my model using the following linq statement:
var model = _db.MyList.GroupBy(r => r.myKey);
I'm getting the error: "The model item passed into the dictionary is of type 'System.Collections.GenericlList' 1... but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable' ...
In the view I have the following code in the first line:
#model IEnumerable<MyApp.Models.MyList>
I tried returning
return View(model.ToList());
from the controller, but no joy. What am I missing?
model.ToList() will return a List<T> where T is MyList. Your model type is straight MyList. If the var declaration you have above already returns MyList, just do return View(model).
This one was dogging me all morning. I found the answer to my question in the following post:
Linq Distinct() by name for populate a dropdown list with name and value
I basically return the following:
public static IEnumberable<MyModel> FindTheKeys(this IQueryable<MyModel> myList)
{
return myList.OrderBy(r => r.AreaKey);
}
and then performed the select as indicated in the above post.
var model = _db.MyModel.FindTheKeys().GroupBy(r => r.AreaKey).Select(g => g.First());
GroupBy returns a collection of IGrouping<TKey, TSource>, not MyList (which is strange anyways, you wouldn't have a list of a list.
It doesn't even make much sense to convert a GroupBy into a list anyways. Did you actuall mean OrderBy?

How does this RavenDB linq query work

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.

Linq Order By not working

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.

ASP.NET MVC2 Simple Linq Question

I have the following code:
public ActionResult ViewCategory(string categoryName, string searchCriteria = "Price")
{
// Retrieve Category and its associated Listings from the database
var categoryModel = db.Categories.Include("Listings")
.Single(c => c.Title == categoryName);
var viewModel = new ClassifiedsBrowseViewModel
{
Category = categoryModel,
Listings = categoryModel.Listings.ToList()
};
return View(viewModel);
}
This code returns some listings from a given category.
But I want to re-order these search results based on certain criteria. E.G. Price...
Many Thanks,
J
You want to use OrderBy() or OrderByDescending() depending upon on requirements.
For example ordering it by highest price -
var viewModel = new ClassifiedsBrowseViewModel
{
Category = categoryModel,
Listings = categoryModel.Listings.OrderByDescending(c=>c.Price).ToList()
};
Listings = categoryModel.Listings.OrderBy(x => x.Price).ToList();
Another option that might work, depending on how you present the information: use something like the jquery tablesorter plugin, and sort the results on the client side.
Obviously, this won't work if there are a lot of results, and you're doing paging, but for a single page of results, presented in a table, it works great.
Another way to write the Linq is to use the query language:
Listings = from item in categoryModel.Listings
orderby item.Price
select item;

How do you re-use select statements with Entity Framework?

Given the following query:
var query = from item in context.Users // Users if of type TblUser
select new User() // User is the domain class model
{
ID = item.Username,
Username = item.Username
};
How can I re-use the select part of the statement in other queries? I.e.
var query = from item in context.Jobs // Jobs if of type TblJob
select new Job() // Job is the domain class model
{
ID = item.JobId,
User = ReuseAboveSelectStatement(item.User);
};
I tried just using a mapper method:
public User MapUser(TblUser item)
{
return item == null ? null : new User()
{
ID = item.UserId,
Username = item.Username
};
}
With:
var query = from item in context.Users // Users if of type TblUser
select MapUser(item);
But if I do this, then the framework throws an error such as:
LINQ to Entities does not recognize
the method 'MapUser(TblUser)' method,
and this method cannot be translated
into a store expression.
You can't use regular function calls in a query definition like that. LINQ needs expression trees, it can't analyze compiled functions and magically translate that to SQL. Read this for a more elaborate explanation
The techniques used in the cited article are incorporated in linqkit (factoring out predicates) and might be of help, though I'm not sure you can use the same technique for managing projections, which is what you seem to want.
The more fundamental question you should ask yourself here IMHO is whether you really need this extra mapping layer? It seems like you're implementing something that EF is already perfectly capable of doing for you...
Try making your MapUser method static.

Resources