How to select associate Table from many to many relationship - c#

i have a many to many relationship tables, movie and tag in code first approach in my MVC application. i want the user to be able to select all movies that is associted with tag. for example i want to select all movies that has tag id =2. i need lambda query that will allow me do this.Any help with appreciated.

This is one way:
var moviesWithTag2 = context.Movies
.Where(m => m.Tags.Any(t => t.Id == 2))
.ToList();
This will return an empty collection if there is no tag with Id=2.
Alternative (which will return null if there is no tag with Id=2):
var moviesWithTag2 = context.Tags
.Where(t => t.Id == 2)
.Select(t => t.Movies)
.SingleOrDefault();
If you also want the Tag entity you could just load it including its movies:
var tag2 = context.Tags.Include(t => t.Movies)
.Single(t => t.Id == 2);
tag2.Movies will contain the movies with tag Id=2.

Related

LINQ: Select Post with multiple Tags

I'm trying to find all Posts contains all selected tags.
My current code returning posts containing all selected tags AND posts containing some of selected tags.
Here is my function. I appreciate your help.
Sample Database structure
Tables
[Post]
- Id
- Title
- Body
[PostTag]
- Id
- PostId
- TagName
So [Post] to [PostTag] got One-to-Many relationship with PostId as foreign key.
public static IEnumerable<Post> getPostContainsAllTags(IEnumerable<string> _SelectedTags,
int numPosts)
{
using (MaplePrimesDataContext dc = new MaplePrimesDataContext(_connectionString))
{
var tagPosts = (from p in dc.Posts
join t in dc.PostTags on p.Id equals t.PostId
where p.Status == 1 && _SelectedTags.Contains(t.Name)
orderby p.DateAdded descending
select p).Take(numPosts).ToList();
return tagPosts;
}
}
I would also change the database structure to not duplicate the tag Name in PostTag table
This works and easy to undestand:
var tagPosts = dc.Posts.Where(post => post.Status == 1);
foreach (var selectedTag in _SelectedTags)
{
tagPosts = tagPosts.Where(post => post.PostTags.Any(tag => tag.Name == selectedTag));
}
return tagPosts.OrderByDescending(p => p.DateAdded).Take(numPosts).ToList();
This also works and is quite a bit faster
var selectedTagIds = dc.Tags.Where(tag => _SelectedTags.Contains(tag.Name)).Select(x => x.TagId);
var tagPosts = dc.Posts
.Where(post => post.Status == 1)
.Where(post =>
!(from selectedTag in selectedTagIds
join tag in post.PostTags on selectedTag equals tag.TagId into postTags
from tag in postTags.DefaultIfEmpty()
where tag.TagId == null
select 1).Any());
return tagPosts.OrderByDescending(p => p.DateAdded).Take(numPosts).ToList();
The difference here is that we create a database collection selectedTagIds first and then use it to do a left join (which is very ugly in LINQ -> https://msdn.microsoft.com/en-us/library/bb397895.aspx)
It works because if a post doesn't have a tag, then the left join of that posts tags and selected tags will have a row without a post tag.
dc.Posts.Select
.Join(p=>p.Id, dc.PostTags, (p, pt)=>new{Post = P, PostTag = pt})
.Where(o=>SelectTags.Contains(o.PostTag))
.GroupBy(o=>o.Post, o=>o.PostTag)
.Select(g=>new{Post=>g.Key, Count=>g.Count()})
.Where(o=>o.Count == SelectTags.Count())
.Select(o=>o.Post)
.OrderByDescending(o=>o.DateAdded)
.Take(numPosts)
.Tolist()
Note - i have just typed it here and might not be the optimum way
What iam doing here is that i am selecting
1. Selecting Tags with Post Tags
2. Filtering out all the records With are not in the SelectedTags List
3. Then Grouping by Product to make an Enumerable of objects With Post as their key and (No of Tags) as the value.
4. Then again Filtering out all the records With (Count Of Tags) != (Count of SelectedTags)
If you have another Tag table with all the available tags, you might consider using a simple Count:
var postsIds = (from p in posts
join t in postTags on p.Id equals t.PostId
group p by p.Id into grp
where grp.Count() == tags.Count()
select grp.Key).ToList();

LINQ Include Extension - Drill Down

I am able to use the strongly typed LINQ Extension : .Include
result = (from A in context.Transactions.Include(_ => _.TransactionDetails)
where A.TransactionId == transactionId
select A).SingleOrDefault();
However I cannot go further within TransactionDetails. My TransactionDetails also have a navigation named User however I don't know how to put it. The available options I have inside TransactiomDetails are the regular extensions for Collections (e.g First ; FirstOrDefault,etc).
I was able to do it using the regular string method (That I want to avoid) :
result = (from A in context.Transactions.Include(_ => _.TransactionDetails)
.Include("TransactionDetails.User")
where A.TransactionId == transactionId
select A).SingleOrDefault();
Thanks
Use this:
result = context.Transactions
.Where(t => t.TransactionId == transactionId)
.Include(t => t.TransactionDetails.Select(u => u.User))
.FirstOrDefault();

Why am I getting a Compile time error in this linq query?

I am getting a compile time error when compiling the below code and I can't see why:
Relations are many to many relations
var contacts = groups_to_querry
.SelectMany(x => x.Contacts)
.Where(x => x.ID == Guid.Empty)
.SelectMany(p => p.ContactDetails)
.Where(x => x.ID == Guid.Empty)
.SelectMany(x => x.Contacts); //This line gives me a compile time error
//Error : The Type argumetns for method 'System.Linq.Enumerable.SelectMany<TSource,Tresult>
//(System.Collections.Generic.IEnumerable<TSource>, System.Func<TSource,
//System.Collections.Generic.IEnumerable<TResult>>)' cannot be infrred from the usage.
//Try specifying the type arguments explicitly
The second time you call for .SelectMany(x => x.Contacts), you are currently working with a collection of ContactDetails. It is doubtful that you would be able to use SelectMany on it. You would need to use Select instead.
SelectMany is used when you want to select multiple collections of items and put them into one IEnumerable. Select is used on individual fields. Since you are working with objects of type ContactDetail (which I assume can only have one contact), you would need use Select
EDIT: Here is what you're doing in a nutshell, step by step:
groups_to_querry.SelectMany(x => x.Contacts): From all the groups that I want to query select all of their many contacts. Each group has many contacts, so put them all into a single IEnumerable collection of type Contact
.Where(x => x.ID == Guid.Empty): ...but only those Contacts with an empty ID
.SelectMany(p => p.ContactDetails): Then select all of those Contacts' many ContactDetails. Each Contact has many ContactDetails, so put them all into a single IEnumerable collection of type ContactDetail
.Where(x => x.ID == Guid.Empty): ...but only those ContactDetails with an empty ID
.SelectMany(x => x.Contacts);: Now select each of the ContactDetails' many Contacts. However, since the compiler knows that there is a one-to-many relationship between Contacts and ContactDetails (and not the other way around) that statement is not possible, and thus shows a compile error
I'm interpreting your intended query as "from multiple groups of contacts, select all contacts that have ID=Guid.Empty and also have details that all have ID=Guid.Empty".
The way your code is actually interpreted is "from all contacts that have Guid.Empty, select all details that have Guid.Empty, and from those details select all contacts". The first problem is that you end up selecting from details. This means the final SelectMany should be a Select, because x.Contacts here refers to the many-to-one relationship from details to contacts.
The second problem is that the result will contain duplicates of contacts, because the same contact is included for each details. What you should be doing instead is filtering the contacts directly based on their details collections, like this:
groups_to_query
.SelectMany(g => g.Contacts)
.Where(c => c.ID == Guid.Empty)
.Where(c => c.ContactDetails.All(d => d.ID == Guid.Empty))
Note this would also select contacts that have zero details, which is different behavior from your query, so I'm not sure if it's what you want. You could add another filter for ContactDetails.Any() if not.
Edit: Since you're using Entity Framework, the above probably won't work. You may need to select the details in a subquery and then filter in memory after it executes:
var queryResult =
groups_to_query
.SelectMany(g => g.Contacts)
.Where(c => c.ID == Guid.Empty)
.Select(c => new {
contact = c,
detailIDs = c.ContactDetails.Select(d => d.ID)
}).ToList();
var contacts =
queryResult
.Where(r => r.detailIDs.All(id => id == Guid.Empty))
.Select(r => r.contact);

join 3 tables in linq getting all entries from two tables

I have 3 tables
A project table
A product table
An update table
The product table holds different products from a project, and the update table holds updates made to various products and holds a reference to the user who did it.
Basically what I want is to have a query that returns all products (since products to projects is a many to one relation) ordered by the date they we're last updated by the user who is currently logged in.
This is my current query:
IEnumerable<ProjectProduct> list =
from joined in
(from product in db.GetTable<Product>()
join project in db.GetTable<Project>()
on product.ProjectId equals project.ID
select new { product, project })
join projectupd in db.GetTable<ProjectUpdate>()
on joined.product.ID equals projectupd.ProductID
where projectupd.CreatedBy == ParamUser
orderby projectupd.LastUpdate
select new ProjectProduct(joined.project, joined.product);
However, the result I'm getting is only the entries in the update table, and not all the existing products. I know that the "where" clause makes it only select the updates created by a specific user, so I'm on the right track, but I have tried a couple of things to make the query successful, without luck though.
Does anybody have a suggestion on how to get the desired result?
Here's an answer that's a little verbose, and it uses method-chain syntax, but I do think it does what your looking for:
var products = db.GetTable<Product>();
var projects = db.GetTable<Project>();
var projectUpdates = db.GetTable<ProjectUpdate>();
var latestProjectUpdatesForUser = projectUpdates
.Where(x => x.CreatedBy == paramUser)
.GroupBy(x => x.ProductId)
.Select(g => g.OrderByDescending(x => x.LastUpdate).First());
var list = products
.Join(
projects,
product => product.ProjectId,
project => project.Id,
(product, project) => new
{
Product = product,
Project = project,
Update = latestProjectUpdatesForUser.FirstOrDefault(u => u.ProductId == product.Id)
}
)
.OrderByDescending(x => x.Update != null ? (DateTime?)x.Update.LastUpdate : null)
.ThenBy(x => x.Project.Id)
.ThenBy(x => x.Product.Id)
.Select(x => new ProjectProduct { Project = x.Project, Product = x.Product});
It takes advantage of the fact that DateTime? is sortable and that null values end up last when using OrderByDescending.

LINQ - SELECT DISTINCT with NOT IN

I'm have a SQL statement which I am trying to transform in a LINQ statement...
SELECT DISTINCT mc.*
FROM ManufractorCategories mc
WHERE mc.Active = 'true'
AND mc.Folder = 'false'
AND (mc.Id not in (SELECT Category_id FROM Manufractor_Category
WHERE Manufractor_id = 3));
That's my last, not working LINQ statement
(IQueryable<object>)db.ManufractorCategories
.Where(o => o.Active == active)
.Where(o => o.Folder == folder)
.Select(i => new { i.Id, i.Folder }).Except(db.Manufractor_Categories.Where(t => t.Manufractor_id == id).Select(t => new { t.Category_id })).Distinct();
I've tried the whole Sunday on that, but the Except statement won't work.
Thanks in advances for any help!
The Except method requires two sets of the same type - this means that you would have to select objects of type ManufractorCategory in the nested query as well as in the outer query - then it would select all categories that are in the first one and not in the second one.
An easier alternative is to use the Contains method to check whether the current ID is in a list of IDs that you want to filter. The following should work:
var q =
db.ManufractorCategories
.Where(o => o.Active == active)
.Where(o => o.Folder == folder)
.Select(i => new { i.Id, i.Folder })
.Where(o =>
!db.Manufractor_Categories
.Select(t => t.Manufractor_id)
.Contains(o.Id)
.Distinct();
And a simplified version using query syntax:
var q =
from o in db.ManufractorCategories
where o.Active == active && o.Folder == folder &&
db.Manufractor_Categories
.Select(t => t.Manufractor_id)
.Contains(o.Id)
select new { i.Id, i.Folder };
The Except statement is going to get a list of objects with the Category_id property. However, you're query has a result that contains objects with the Id and Folder properties. The query will most likely be unable to see where these objects are equal, and so, the Except clause won't take effect.

Categories