Grouping 4 Tables using Linq - c#

I have the following requirements:
One rating can have zero or many RatingPictures
One Rating can have zero or many Comments
One Comment belongs to one User
This is what I have so far:
from rating in Ratings
where rating.LocationID == 1007
join ratingpicture in RatingPictures
on rating.ID equals ratingpicture.RatingID into j3
from ratingpicture in j3.DefaultIfEmpty()
join comment in Comments
on rating.ID equals comment.RatingID into j1
from comment in j1.DefaultIfEmpty()
join user in Users
on comment.UserID equals user.ID into j2
from user in j2.DefaultIfEmpty()
group new { ratingpicture, comment, user } by rating into g
select new { rating = g.Key, ratingpicture= g.Key, comment = g.ToList() }

If you model your entity classes like this:
public class Comment
{
public int CommentId { get; set; }
public int RatingId { get; set; }
public virtual Rating Rating { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
}
public class User
{
public int UserId { get; set; }
}
public class Rating
{
public Rating()
{
RatingPictures = new HashSet<RatingPicture>();
Comments = new HashSet<Comment>();
}
public int RatingId { get; set; }
public virtual ICollection<RatingPicture> RatingPictures { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public int LocationID { get; set; }
}
public class RatingPicture
{
public int RatingPictureId { get; set; }
public int RatingId { get; set; }
public virtual Rating Rating { get; set; }
}
Then your query would be as simple as this:
var query = context.Ratings.Where(r => r.LocationID == 1007)
.Include(r => r.RatingPictures)
.Include(r => r.Comments.Select(c => c.User));
var result = query.ToList();
Under the hood, this query will be translated into a join query. But this is the beauty of ORMs, we get to work at a more abstract level.
Take a look at this reference for more information about relationships and navigation properties in Entity Framework.

Related

EF Core Many to Many Querying to join 3 tables

I have the following Code:
public class ProductTbl
{
public override int Id { get; set; }
public string ProductName { get; set; }
public List<ProductManufacturer> ProductManufacturer { get; set; } //M2M
}
public class Manufacturer_LKP
{
public override int Id { get; set; }
public string ManufacturerName { get; set; }
public List<ProductManufacturer> ProductManufacturer { get; set; } //M2M
}
public class ProductManufacturer
{
public ProductTbl Product { get; set; }
public int ProductID { get; set; }
public Manufacturer_LKP Manufacturer { get; set; }
public int ManufacturerID { get; set; }
}
public class SupplierTbl
{
public int SupplierID { get; set; }
public string SupplierName { get; set; }
}
public class ProductSuppliertbl
{
public int Id { get; set; }
public ProductTbl Product { get; set; }
public int ProductID { get; set; }
public SuppilerTbl Supplier { get; set; }
public int SupplierID { get; set; }
}
*I need to write Linq query to join all 3 tables (Product,Manufacture,ProductManufacturer) to get ProductName and ManufatureName together in one DB trip
*When I do the following I missed the Manufacture object (Manufacture=Null)
DbSet<ProductTbl>()
.Where(a => a.Id == 5)
.AsNoTracking()
.Include(a => a.ProductType)
.Include(a => a.ProductManufacturer)
Above Linq Just joint Product table with ProductManufacture Table So I cannot Get "ManufactureName"
So Is there is any way to join the 3 tables to get ManufactureName beside the ProductName in one DB trip?
Projection is your friend when trying to load related data. The issue with many-to-many is that you are saying a product has many manufacturers, while at the same time it has many suppliers
The Product would need a reference to the ProductSuppliers for that product to easily manage the many suppliers requirement.
var productData = context.Products
.Select(p => new
{
p.ProductName,
ManufacturerNames = p.ProductManufacturers.Select(pm => pm.Manufacturer.ManufacturerName).ToList(),
SupplierNames = x.ProductSuppliers.Select(ps => ps.Supplier.SupplierName).ToList()
}).ToList();
This gives you a list of products, with each product's associated manufacturer names and supplier names. With that data you can format output how you see fit.
If you want the entities themselves, then the missing bit is ThenInclude:
var products = context.Products
.Include(p => p.ProductManufacturers)
.ThenInclude(pm => pm.Manufacturer)
.Include(p => p.ProductSuppliers)
.ThenInclude(ps => ps.Supplier)
.AsNoTracking()
.ToList();
This would load the entire entity graph.
If you don't want or cannot put a ProductSuppliers collection in product then you can build the query entirely from the ProductSupplier, but it's a bit messier.
If you are using EF Core 5 and your joining entities (ProductManufacturer/ProductSupplier) are just simply the FK references to their respective entities, then you can do away with the joining entity and let EF manage it behind the scenes. Product would just contain a collection of Manufacturers and a collection of Suppliers. These can be configured still with a HasMany..WithMany, but makes queries a lot cleaner to look at without the intermediate entities.
I.e.
var productData = context.Products
.Select(p => new
{
p.ProductName,
ManufacturerNames = p.Manufacturers.Select(m => m.ManufacturerName).ToList(),
SupplierNames = x.Suppliers.Select(s => ps.SupplierName).ToList()
}).ToList();
and
var products = context.Products
.Include(p => p.Manufacturers)
.Include(p => p.Suppliers)
.AsNoTracking()
.ToList();
... respectively. Intermediate joining entities are only needed if there are additional properties you want to access in the joining entity. (I.e. CreatedBy/At, etc.)
try this
var list = context.ProductManufactures
.Select(i => new
{
ProductName = i.Product.ProductName,
ManufacturerName = i.Manufacturer.ManufacturerName,
SupplierNames = i.Product.ProductSuppliers.Select(s => s.SupplierName).ToList()
}).ToList();
or you can try this too
var productData = context.Products
.Select(i => new
{
ProductName= i.ProductName,
ManufacturerNames = i.ProductManufacturers.Select(m => m.Manufacturer.ManufacturerName),
SupplierNames = i.ProductSuppliers.Select(s => s.Supplier.SupSupplierName)
}).ToList();
but before this you have to fix some navigation properties
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
public List<ProductManufacturer> ProductManufacturers { get; set; }
public List<ProductSupplier> ProductSuppliers { get; set; }
}
public class Supplier
{
public int SupplierID { get; set; }
public string SupplierName { get; set; }
public List<ProductSupplier> ProductSuppliers { get; set; }
}
public class ProductSupplier
{
public int Id { get; set; }
public Product Product { get; set; }
public int ProductID { get; set; }
public Supplier Supplier { get; set; }
public int SupplierID { get; set; }
}
public class Manufacturer_LKP
{
public int Id { get; set; }
public string ManufacturerName { get; set; }
public List<ProductManufacturer> ProductManufacturer { get; set; }
}
public class ProductManufacturer
{
public Product Product { get; set; }
public int ProductID { get; set; }
public Manufacturer_LKP Manufacturer { get; set; }
public int ManufacturerID { get; set; }
}

Having trouble to write lambda expression for One to Many Relationship in Linq

I am trying to extract list of Categories with the corresponding Tickets for a specific userId using Linq Lambda expression.
Category:
public class Category
{
public int Id { get; set; }
public string CategoryName { get; set; }
public ICollection<Ticket> Tickets { get; set; }
}
Ticket:
public class Ticket
{
public int Id { get; set; }
public string Title { get; set; }
public User User { get; set; }
public Category Category { get; set; }
}
User:
public class User : IdentityUser
{
public ICollection<Ticket> Tickets { get; set; }
}
This is what I tried so far, but it is only returns Category if it has any ticket.
I want all Categories with Tickets (for a specific userId)
var query = from cate in _context.Categories.Include(c => c.Tickets)
join tickets in _context.Tickets
on cate.Id equals tickets.Category.Id
where tickets.User.Id.Equals(id)
select (cate);
DatabaseContext.Categories
.Include(c => c.Tickets)
.Where(c => c.Tickets.Any(t => t.User.Id.Equals(id)));
This may help you and I recommend you to learn more about linq operators first.

Sequence contains no elements on query

Query:
var result = await this.Context.ShopProducts
.Include(prd => prd.Category)
.ThenInclude(cat => cat.Culture)
.Include(prd => prd.InfoItems)
.SingleOrDefaultAsync(prd => prd.Id.Equals(id) && prd.CategoryId.Equals(culture));
Edit: Updated the entities and query to reflect the new design and added a sql query
Entities:
Product:
[Table("ShopProduct")]
public class Product : ShopBase
{
public bool Active { get; set; } = true;
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public ICollection<ProductInfo> InfoItems { get; set; } = new HashSet<ProductInfo>();
}
ProductInfo:
[Table("ShopProductInfo")]
public class ProductInfo : ShopBase
{
public int ProductId { get; set; }
public int CultureId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Sum { get; set; }
public ICollection<GraphicItem> GraphicItems { get; set; }
}
What I want is to only select the ProductInfo objects with CultureId that equals the Category CultureId property. When selecting I provide the product Id and Category Id.
I want to replicate something like this sql query:
DECLARE #prdId INT,
#catId INT
SET #prdId = 1
SET #catId = 1
SELECT prd.*,
info.*,
cat.*
FROM ShopProduct prd,
ShopProductInfo info,
ShopCategory cat
WHERE prd.Id = #prdId
AND prd.CategoryId = cat.Id
AND cat.Id = #catId
AND cat.CultureId = info.CultureId
this error mean, linq query return some value otherwise give exception error

Join 3 One to Many Tables in Entity Framework

i have 2 tables that each one has a one-to-many relation to the table between and the table between has ids of 2 other tables
dbo.Posts dbo.Posts_Categories dbo.Categories
-ID -ID -ID
-Title -PostID -Name
-CategoryID
result i expect is :
Title = post1 Categories = web,mobile,desktop
Title = post2 Categories = app,game
...
i know how to query this in sql using Stuff function and For Xml Path but i have no idea how do i do this in entity framework!
any suggestion or book for how to do works in this way might help!
Edit: EF classes added:
public class Post : ReportingBase {
public Post() { }
[Required, MaxLength(500)]
public string Title { get; set; }
[Required, MaxLength(500)]
public string Address { get; set; }
[Required]
public string Body { get; set; }
[Required, MaxLength(500)]
public string Tags { get; set; }
[Required]
public int Visit { get; set; }
public virtual ICollection<Post_Category> Posts_Categories { get; set; }
public virtual ICollection<Post_AttachedFile> Posts_AttachedFiles { get; set; }
[ForeignKey("Image")]
public virtual int? ImageID { get; set; }
public virtual Image Image { get; set; }
}
public class Post_Category {
public Post_Category() { }
[Key, Column(Order = 0)]
public int PostID { get; set; }
[Key, Column(Order = 1)]
public int CategoryID { get; set; }
public virtual Post Post { get; set; }
public virtual Category Category { get; set; }
}
public class Category : EntityBase {
public Category() { }
[Required, MaxLength(50)]
public string Name { get; set; }
[Required, MaxLength(150)]
public string Address { get; set; }
public int? ParentID { get; set; }
public virtual ICollection<Post_Category> Posts_Categories { get; set; }
}
thank you in advance
Edit : According to #IvanStoev answer i did following :
List<P> p = context.Posts.Select(post => new {
Title = post.Title,
Categories = post.Posts_Categories.Select(pc => pc.Category.Name).ToList()
}).ToList();
and created a class called P :
public class P {
public string Title { get; set; }
public List<string> Categories { get; set; }
}
but it doesn't work correctly and the problem is how to return the result.
In EF it's even easier than in SQL thanks to the concept of so called navigation properties. All you need to know is a basic LINQ query syntax and just follow them (navigate) to get the data needed. For instance:
var result = db.Posts
.Select(post => new
{
Title = post.Title,
Categories = post.Posts_Categories
.Select(pc => pc.Category.Name)
.ToList()
})
.ToList();
The result is a list of anonymous type having string Title property and List<string> Categories property containing the related category names.
You can use Linqpad (software) to get familiarize with the Linq query it builds lambda expression for you by connecting to the database and provides output too to cross verify.
The below one is the lambda expression for joining the tables you have mentioned.
p - Post
pc - post_categories
c - categories
Code:
Posts.Join(Post_Categories, p => p.ID, pc => pc.ID, ( p, pc) => new { p = p, pc = pc})
.Join(Categories, pcc => pcc.pc.CategoryID, c => c.ID, ( pcc, c) => new { pcc = pcc, c = c})
.Select(p.Title)
.Select(c.Name)
You should be using .Include() for any join in EF Core.
I've come up with this simple example: one person can have many dogs.
public class Person
{
public Guid Id { get; set; }
public ICollection<Dog> Dogs { get; set; } // One Person can have many Dogs
}
public class Dogs
{
public Guid Id { get; set; }
public Guid PersonId { get; set; }
}
Generate the migrations after creating the models. Not going over how to do that in this answer.
Here's how you use .Include() to join upon the two different tables:
public class PersonRepository : RepositoryBase
{
public IEnumerable<Person> FetchPeopleWithManyDogs()
{
return DatabaseContext.Person
.Include(x => x.Dogs)
.Where(x => x.Dogs.Count() > 1).ToList();
}
}

How to get specific columns with one to many relationship

I have 3 tables , one to many relationship.
I need to get only specific columns with SelectMany method.
I need to get only Categories.CategoryName and Comments.CommentDate of the selected News object.
Here is my code
News news = db.News.Include(w => w.Categories)
.Include(w => w.Comments).SingleOrDefault(n => n.NewsId == Id);
Here are my Entities:
News Entity:
public partial class News
{
public News()
{
this.Categories = new HashSet<Category>();
this.Comments = new HashSet<Comment>();
}
public int NewsId { get; set; }
public string NewsTitle { get; set; }
public string NewsBody { get; set; }
public System.DateTime NewsDate { get; set; }
public string NewsImagePath { get; set; }
public virtual ICollection<Category> Categories { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
Category Entity:
public partial class Category
{
public Category()
{
this.News = new HashSet<News>();
}
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<News> News { get; set; }
}
Comment Entity:
public partial class Comment
{
public Comment()
{
this.News = new HashSet<News>();
}
public int CommentId { get; set; }
public string CommentBody { get; set; }
public Nullable<System.DateTime> CommentDate { get; set; }
public virtual ICollection<News> News { get; set; }
}
This LINQ query should take care of it:
var query =
from news in db.News
where news.Id == Id
let categoryNames =
from category in news.Categories
select category.Name
let commentDates =
from comment in news.Comments
select comment.CommentDate
select new {
CategoryNames = categoryNames.ToList(),
CommentDates = commentDates.ToList()
};
That query is not using SelectMany, but that wouldn't help you, since then you wouldn't be able to group your categories and comments by news items. Since categories and comments are not directly connected, you'd need two SelectManys and then you'd need to cross join the results. That would obviously not be what you want.
Maybe try using the following?
var categoryNames = news.Categories.Select(c=>c.CategoryName);
var commentDates = news.Comments.Select(c=>c.CommentDate);
Note that SelectMany is used to flatten lists.For example, lets say you have collection of news matching certain search criteria, and then you use SelectMany to collect all the Categories/Comments of these news set, in a flat list.

Categories

Resources