IQueryable where clause - c#

Was difficult for me to find a fitting title for this post. But I have the following:
IArticleRepository articleRepo = unitOfWork.ArticleRepository;
List<Article> articles = new List<Article>(
articleRepo.GetAll()
.Where(a => a.Title == searchTerm)
//.Where(a => a.Categories.Contains(Category.))
.OrderByDescending(a => a.CreatedDate));
So some explanation: An article has , among other things, a Title and a CreateDate, and filtering through those is easy. But an article also has categories associated with it. So an article has an array property of type Category. Type Category has a property called CategoryId of type int.
So in my code where it's commented out, I'm trying to select an article, which has a category associated with it, who's CategoryId is equal to.. say 4.
But I'm finding it quite difficult to express this in my C# syntax. I'm also new to C# so that's not helping either.

You don't need to write two Where clauses; just add another condition to your first Where. The second condition should use Any function to search for the categories you're looking for.
IArticleRepository articleRepo = unitOfWork.ArticleRepository;
List<Article> articles = new List<Article>(
articleRepo.GetAll()
.Where(a => a.Title == searchTerm &&
a.Categories.Any(c => c.CategoryID == 4))
.OrderByDescending(a => a.CreatedDate));
For multiple categories, suppose you have your CategoryIDs in an int[] or List<int> named MyCatIDsList. They you can change the categories clause in the above query to this:
a.Categories.Any(c => MyCatIDsList.Contains(c.CategoryID))

There is an alternative syntax when using LINQ queries, which is more like SQL. The code above is correct, but you may find this version more concise:
int categoryId = 4
IArticleRepository articleRepo = unitOfWork.ArticleRepository;
var articlesQuery = from article in articleRepo.GetAll()
from category in article.Categories
where category.CategoryId == categoryId
where article.Title == searchTerm
orderby article.CreatedDate descending
select article
List<Article> articles = articlesQuery.ToList();
Or its more common to do these all together in one step:
int categoryId = 4
List<Article> articles = (
from article in articleRepo.GetAll()
from category in article.Categories
where category.CategoryId == categoryId
where article.Title == searchTerm
orderby article.CreatedDate descending
select article
).ToList()

You don't need to create a new list and you can use several where expressions in one Where clause. Can you try the following code:
List<Article> articles = articleRepo.GetAll()
.Where(a => a.Title == searchTerm && a.Categories.Contains(Category)).OrderByDescending(a => a.CreatedDate)).ToList();

Related

Linq Join issue in C#

I am so very new to c# and am learning as I go...
I have two tables, Blog and BlogCategories. The Blog table has an id that references the category in BlogCategories.
I am having an issue with the syntax on the join:
var categories = new List<BlogCategory>();
if (model.BlogCategoryId.HasValue)
{
var query =
from category in categories
join blog in model on category.Id equals model.BlogCategoryId
select new { BlogCategory = category.Name };
}
The issue is that it doesn't like the join and get :
Error CS1941 The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'
Any help would be greatly appreciated... PHP is not a big boy language, this is.
Quick table structure
BlogPosts table:
Id,
BlogCategory,
Title,
Content
BlogCategories:
Id,
CategoryName
EDIT:
Solution seems like it would work but error somewhere:
var blogPosts = PopulateBlogPosts();
if (model.BlogCategoryId.HasValue)
{
var blogPostCategories = PopulateBlogCategories();
blogPosts = blogPostCategories.Where(c => c.Id == model.BlogCategoryId).Single();
}
Assuming categories is an IEnumerable<BlogCategory>.
var category = categories.Single(c => c.Id == model.BlogCategoryId);
This Single extension method asserts you're only expecting one object to match, and it allows you to filter. It will throw an exception if there is anything other than one category that meets the filter criteria. It's a shorter form of this:
var category = categories.Where(c => c.Id == model.BlogCategoryId).Single();

LINQ many-to-many relationship, how to write a correct WHERE clause?

I use many-to-many relationship for my tables.
There is a query:
var query = from post in context.Posts
from tag in post.Tags where tag.TagId == 10
select post;
Ok, it works fine. I get posts having the tag specified by id.
I have a collection of tag ids. And i want to get posts having every tag in my collection.
I try the following way:
var tagIds = new int[]{1, 3, 7, 23, 56};
var query = from post in context.Posts
from tag in post.Tags where tagIds.Contains( tag.TagId )
select post;
It doesn't work. The query returns all posts having ANY one of the specified tags.
I want to get a clause like this but dynamicaly for any count of tags in the collection:
post.Tags.Whare(x => x.TagId = 1 && x.TagId = 3 && x.TagId = 7 && ... )
You shouldn’t project each post’s tags in the outer query; rather, you need to use an inner query which performs the check for the outer filter. (In SQL, we used to call it a correlated subquery.)
var query =
from post in context.Posts
where post.Tags.All(tag => tagIds.Contains(tag.TagId))
select post;
Alternate syntax:
var query =
context.Posts.Where(post =>
post.Tags.All(tag =>
tagIds.Contains(tag.TagId)));
Edit: Correcting per Slauma’s clarification. The version below returns posts which contain, at least, all the tags in the tagIds collection.
var query =
from post in context.Posts
where tagIds.All(requiredId => post.Tags.Any(tag => tag.TagId == requiredId))
select post;
Alternate syntax:
var query =
context.Posts.Where(post =>
tagIds.All(requiredId =>
post.Tags.Any(tag =>
tag.TagId == requiredId)));
Edit2: Corrected above per Slauma. Also including another alternative making full use of query syntax below:
// Project posts from context for which
// no Ids from tagIds are not matched
// by any tags from post
var query =
from post in context.Posts
where
(
// Project Ids from tagIds that are
// not matched by any tags from post
from requiredId in tagIds
where
(
// Project tags from post that match requiredId
from tag in post.Tags
where tag.TagId == requiredId
select tag
).Any() == false
select requiredId
).Any() == false
select post;
I’ve used .Any() == false to simulate the NOT EXISTS operator in Transact-SQL.
This is actually pretty easy to do:
var tags = context.Posts.Where(post => post.Tags.All(tag => tagIds.Contains(tag)));
Another option is to intersect the two lists if you want the collection of tags to ONLY contain the set you specify and no others:
var query = from post in context.Posts
let tags = post.Tags.Select(x => x.Id).ToList()
where tags.Intersect(tagIds).Count() == tags.Length
select post;
Try it with Any.
var query = from post in context.Posts
from tag in post.Tags where tagIds.Any(t => t == tag.TagId )
select post;

LINQ: Problem using DB with relations

I don't know how can I return posts to my view where their tags are equal to those who are passed to controller action.
I think there is some clever and easy way of doing this but I am very new to LINQ and SQL.
Code
// id = tag name, not it's id
public ActionResult Tag(string id)
{
// I get all the PostTags where PostTags.Tag.Name = id
var postTags = _db.PostTags.Where(x => x.Tag.Name == id);
// And what I do now?
}
Using joins in relational data is easier to grasp as a novice using query syntax instead of extension methods. The following is possible with extension methods (like .Join(...), etc), but this is closer to the SQL you might already be used to.
var postTags = from t in _db.Tags
join pt in _db.PostTags on t.ID equals pt.TagID
join p in _db.Posts on pt.PostID equals p.ID
where t.Name == id
select p;
Well to select them you could do something similar to..
var posts = _db.Posts.Where(post => post.PostTags.Any(postTag => postTag.Tag.Name == id));
This will just select all Posts where any of the related PostTags has a Tag with the name passed.

Crazy Query need some feedback

var query =context.Categories.Include("ChildHierarchy")
.Where(c =>
context.CategoryHierarchy.Where(ch => ch.ParentCategoryID == ch.ParentCategoryID)
.Select(ch => ch.ChildCategoryID).Contains(c.CategoryID));
Questions:
I need to include some data from another Navigation Propery (".Include("otherprop")")
Is it possible to do a select new after all of this?
Thanks
The title to your question intrigued me with the words "Crazy Query", and yes, you're right, it is a bit crazy.
You have a .Where(...) clause with the following predicate:
ch => ch.ParentCategoryID == ch.ParentCategoryID
Now that's going to always be true. So I guess that you're trying to do something else. I'll have a crack at what that might be at the end of my answer.
I then did some cleaning up of your query to get a better idea of what you're doing. This is what it now looks like:
var query =
context
.Categories
.Where(c => context
.CategoryHierarchy
.Select(ch => ch.ChildCategoryID)
.Contains(c.CategoryID));
So rather than use nested queries I would suggest something like this might be better in terms of readability and possibly performance:
var query =
from c in context.Categories
join h in context.CategoryHierarchy
on c.CategoryID equals h.ChildCategoryID into ghs
where ghs.Any()
select c;
This gives the same results as your query so hopefully this is helpful.
I do get the impression that you're trying to do a query where you want to return each Category along with any child categories it may have. If that's the case here are the queries you need:
var lookup =
(from c in context.Categories
join h in context.CategoryHierarchy
on c.CategoryID equals h.ChildCategoryID
select new { ParentCategoryID = h.ParentCategoryID, Category = c, }
).ToLookup(x => x.ParentCategoryID, x => x.Category);
var query =
from c in context.Categories
select new { Category = c, Children = lookup[c.CategoryID], };
The lookup query first makes a join on categories and the category hierarchies to return all children categories and their associated ParentCategoryID and then it creates a lookup from ParentCategoryID to a list of associated Category children.
The query now just has to select all categories and perform a lookup on the CategoryID to get the children.
The advantage of using the .ToLookup(...) approach is that it easily allows you to include categories that don't have children. Unlike using a Dictionary<,> the lookup does not throw an exception when you use a key that it hasn't got a value for - instead it returns an empty list.
Now, you can add back in the .Include(...) calls too.
var lookup =
(from c in context.Categories
.Include("ChildHierarchy")
.Include("otherprop")
join h in context.CategoryHierarchy
on c.CategoryID equals h.ChildCategoryID
select new { ParentCategoryID = h.ParentCategoryID, Category = c, }
).ToLookup(x => x.ParentCategoryID, x => x.Category);
var query =
from c in context.Categories
.Include("ChildHierarchy")
.Include("otherprop")
select new { Category = c, Children = lookup[c.CategoryID], };
Is that what you're after?
1) Then add it - context.Categories.Include("ChildHierarchy").Include("OtherCollection");
2) Absolutely, yes
var query = context.Categories
.Include("ChildHierarchy")
.Include("OtherProp")
.Where(c => context.CategoryHierarchy.Where(ch => ch.ParentCategoryID == ch.ParentCategoryID)
.Select(ch => ch.ChildCategoryID).Contains(c.CategoryID))
.Select(c => new { c.A, c.B, c.etc });

How to cast a Linq Dynamic Query result as a custom class?

Normally, I do this:
var a = from p in db.Products
where p.ProductType == "Tee Shirt"
group p by p.ProductColor into g
select new Category {
PropertyType = g.Key,
Count = g.Count() }
But I have code like this:
var a = Products
.Where("ProductType == #0", "Tee Shirt")
.GroupBy("ProductColor", "it")
.Select("new ( Key, it.Count() as int )");
What syntax could I alter to produce identical results, i.e., how do I do a projection of Category from the second Linq statement?
I know in both that g and it are the same and represent the entire table record, and that I am pulling the entire record in just to do a count. I need to fix that too. Edit: Marcelo Cantos pointed out that Linq is smart enough to not pull unnecessary data. Thanks!
Why would you have to do it at all? Since you still have all of the information after the GroupBy call, you can easily do this:
var a = Products
.Where("ProductType == #0", "Tee Shirt")
.GroupBy("ProductColor", "it")
.Select(c => new Category {
PropertyType = g.Key, Count = g.Count()
});
The type of Products should still flow through and be accessible and the regular groupings/filtering shouldn't mutate the type that is flowing through the extension methods.

Categories

Resources