How can the following be accomplished using LINQ
SELECT r.BrandID
FROM dbo.Items AS r
JOIN Brands AS d ON r.BrandID = d.BrandID
WHERE CategoryID IN (SELECT CategoryID
FROM dbo.Categories
WHERE Name = 'Bread - Bakery')
Code for Brand class:
public class Brand
{
public int BrandID { get; set; }
[DisplayName("Brand Name")]
public string Name { get; set; }
public virtual List<Category> Categories { get; set; }
public virtual List<Item> Items { get; set; }
}
Code for Item class:
public class Item
{
[Key]
public int ItemID { get; set; }
public virtual Category Category { get; set; }
public virtual Brand Brand { get; set; }
public int CategoryID { get; set; }
public int BrandID { get; set; }
}
code for Category class:
public class Category
{
[Key]
public int CategoryID { get; set; }
[DisplayName("Category Name")]
public virtual string Name { get; set; }
public virtual List<Brand> Brands { get; set; }
public virtual List<Item> Items { get; set; }
}
dbContext.Items
.Where(x => x.Category.Name.Equals("Bread - Bakery"))
.Select(x => x.BrandID);
I am not sure why you need to use below join. It seems that it is not needed (unless intentionally inner joined with brands to remove non-matching records from items)
JOIN Brands AS d ON r.BrandID = d.BrandID
Hm, pity you didn't write your requirements, now I have to guess what they are from your SQL code.
So you have a database with Brands, Items and Categories. Every Item has a Category, every Category can be used by zero or more Items: a one-to-many relation
Every Item is of a certain Brand, Every Brand can have zero or more items: also a straightforward one-to-many relation.
Finally every Brand has zero or more Categories, every Category has zero or more Brands: many-to-many
Now you take your collection of Items, you only want to keep those Items that have a Category with a Name that equals Bread - Bakery. From the remaining items you want all their BrandIds.
The requirement would be: "Give me the BrandIds of all Items that have a Category with a Name that equals 'Bread - Bakery`.
If you use entity framework, it is usually easier if you use the virtual ICollection instead of doing the join yourself. Entity framework knows the relations between the tables and will compose the proper joins for it.
var result = myDbContext.Items // from the collection of Items
.Where(item => item.Category.Name == "Bread - Bakery") // keep only those with a Category
// that has a Name equal to ...
.Select(item.BrandId); // from the remaining items select the BrandId
If you really want, and you can convince your project leader that entity framework can't be trusted to do the proper joins you can do the join yourself:
// get the sequence of categories that I'm interested in:
var breadBakeryCategories = myDbContext.Categories
.Where(category => category.Name == "Bread - Bakery");
// join the items with these categories
// and select the BrandIds
var requestedBrandIds= myDbContext.Items
.Join(breadBakeryCategories,
item => item.CategoryId, // from every Item take the CategoryId,
category => category.CategoryId, // from every Category take the CategoryId
(item, category) => item.BrandId); // when they match, select the BrandId
TODO: consider concatenating this into one big ugly LINQ statement.
Remark 1
You do realize that your result might have the same BrandIds several times, don't you?
If you don't want that, start with the Brands:
var result = myDbContext.Brands
.Where(brand => brand.Items.Select(item => item.Category.Name)
.Where(name => name == "Bread - Bakery")
.Any())
.Select(brand => brand.brandId);
In words: from the collection of Brands, keep only those Brands that have at least one Category with a name equal to "Bread - Bakery". From the remaining Brands select the BrandId.
** Remark 2 **
Why are your one-to-many Lists instead of ICollections? Are you sure that brand.Categories[4] has a proper meaning?
var result = myDbContext.Brands
.Where(brand => brand.Category[4].Name == "Bread - Bakeries");
Your compiler won't complain, but you'll get runtime errors.
Consider using virtual ICollection<...> for your one-to-many and many-to-many relations. This way you'll have exactly the functionality you expect with a database table, and your compiler will complain if you try to use functionality that can't be translated into SQL
Related
I want to pull data from multiple tables using LINQ in my .NET Core application. Here's an example:
public class Customer {
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public HashSet<Transaction> Transactions { get; set; }
}
public class Transaction {
public Guid Id { get; set; }
public decimal Amount { get; set; }
public DateTime Created { get; set; }
public Guid CustomerId { get; set; }
public Customer Customer { get; set; }
}
These have a one-to-many relation in my solution. One customer has many transactions and one transaction has one customer. If I wanted to grab the 10 latest transactions and 10 lastest customers in one LINQ query, how would I do that? I've read that .Union() should be able to do it, but it won't work for me. Example:
var ids = _context
.Customers
.OrderByDescending(x => x.Created)
.Take(10)
.Select(x => x.Id)
.Union(_context
.Transactions
.OrderByDescending(x => x.Created)
.Take(10)
.Select(x => x.CustomerId)
)
.ToList();
This gives me two lists of type Guid, but they contain the same elements. Not sure if it's just me who understands this wrong, but it seems a bit weird. I am happy as long as it asks the database once.
You wrote:
I wanted to grab the 10 latest transactions and 10 latest customers in one LINQ query
It is a bit unclear what you want. I doubt that you want one sequence with a mix of Customers and Transactions. I guess that you want the 10 newest Customers, each with their last 10 Transactions?
I wonder why you would deviate from the entity framework code first conventions. If your class Customer represents a row in your database, then surely it doesn't have a HashSet<Transaction>?
A one-to-many of a Customer with his Transactions should be modeled as follows:
class Customer
{
public int Id {get; set;}
... // other properties
// every Customer has zero or more Transactions (one-to-many)
public virtual ICollection<Transaction> Transactions {get; set;}
}
class Transaction
{
public int Id {get; set;}
... // other properties
// every Transaction belongs to exactly one Customer, using foreign key
public int CustomerId {get; set;}
public virtual Customer Customer {get; set;}
}
public MyDbContext : DbContext
{
public DbSet<Customer> Customers {get; set;}
public DbSet<Transaction> Transactions {get; set;}
}
This is all that entity framework needs to know to detect the tables you want to create, to detect your one-to-many relationship, and to detect the primary keys and foreign keys. Only if you want different names of tables or columns, you'll need attributes and/or fluent API
The major differences between my classes and yours, is that the one-to-many relation is represented by virtual properties. The HashSet is an ICollection. After all, your Transactions table is a collection of rows, not a HashSet
In entity framework the columns of your tables are represented by non-virtual properties; the virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
Quite a lot of people tend to (group-)join tables, when they are using entity framework. However, life is much easier if you use the virtual properties
Back to your question
I want (some properties of) the 10 newest Customers, each with (several properties of) their 10 latest Transactions
var query = dbContext.Customers // from the collection of Customer
.OrderByDescending(customer => customer.Created) // order this by descending Creation date
.Select(customer => new // from every Customer select the
{ // following properties
// select only the properties you actually plan to use
Id = Customer.Id,
Created = Customer.Created,
Name = Customer.Name,
...
LatestTransactions = customer.Transactions // Order the customer's collection
.OrderBy(transaction => transaction.Created) // of Transactions
.Select(transaction => new // and select the properties
{
// again: select only the properties you plan to use
Id = transaction.Id,
Created = transaction.Created,
...
// not needed you know it equals Customer.Id
// CustomerId = transaction.CustomerId,
})
.Take(10) // take only the first 10 Transactions
.ToList(),
})
.Take(10); // take only the first 10 Customers
Entity framework knows the one-to-many relationship and recognizes that a group-join is needed for this.
One of the slower parts of your query is the transfer of the selected data from the DBMS to your local process. Hence it is wise to limit the selected data to the data you actually plan to use. If Customer with Id 4 has 1000 Transactions, it would be a waste to transfer the foreign key for every Transaction, because you know it has value 4.
If you really want to do the join yourself:
var query = dbContext.Customers // GroupJoin customers and Transactions
.GroupJoin(dbContext.Transactions,
customer => customer.Id, // from each Customer take the primary key
transaction => transaction.CustomerId, // from each Transaction take the foreign key
(customer, transactions) => new // take the customer with his matching transactions
{ // to make a new:
Id = customer.Id,
Created = customer.Created,
...
LatestTransactions = transactions
.OrderBy(transaction => transaction.Created)
.Select(transaction => new
{
Id = transaction.Id,
Created = transaction.Created,
...
})
.Take(10)
.ToList(),
})
.Take(10);
Try following. I models you database _context as a class just so I could test the syntax. Remember that one customer may map to more than one transaction. You may want to use GroupBy ID so you get 10 different customers.
class Program
{
static void Main(string[] args)
{
Context _context = new Context();
var ids = (from c in _context.customers
join t in _context.transactions on c.Id equals t.CustomerId
select new { c = c, t = t})
.OrderByDescending(x => x.c.Created)
.Take(10)
.ToList();
}
}
public class Context
{
public List<Customer> customers { get; set; }
public List<Transaction> transactions { get; set; }
}
public class Customer
{
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public HashSet<Transaction> Transactions { get; set; }
}
public class Transaction
{
public Guid Id { get; set; }
public decimal Amount { get; set; }
public DateTime Created { get; set; }
public Guid CustomerId { get; set; }
public Customer Customer { get; set; }
}
You may want to try this instead :
var ids = (from c in _context.customers
join t in _context.transactions on c.Id equals t.CustomerId
select new { c = c, t = t})
.OrderByDescending(x => x.c.Created)
.GroupBy(x => x.c.Id)
.SelectMany(x => x.Take(10))
.ToList();
Eliminating the Join will speed up results. You always can get the customer info in another query.
var transactions = _context.transactions
.OrderByDescending(x => x.Created)
.GroupBy(x => x.CustomerId)
.Select(x => x.Take(10))
.ToList();
try this:
var customers = customerService.GetAll().OrderByDescending(c => c.Created).Take(10).ToList().AsQueryable();
var transactions = transactionService.GetAll().OrderByDescending(t => t.Created).Take(10).ToList().AsQueryable();
transactions = transactions.Where(t => customers.Any(c => c.CustomerId == t.Id));
I have the following model:
public Class Category{
public int Id {get;set;}
public string Name {get;set;}
public ICollection<SubCategory> SubCategories {get;set;}
}
public Class SubCategory{
public int Id {get;set;}
public string Name {get;set;}
public int CategoryId { get; set; }
public virtual Category Category{ get; set; }
public ICollection<Ticket> Tickets { get; set; }
}
public class Ticket {
public Ticket();
public int Id { get; set; }
public virtual SubCategory SubCategory{ get; set; }
public int SubCategoryId{ get; set; }
}
I want to get data groupBy Category and get the count of tickets in each subcategory using this query:
Entities
.Include(h => h.SubCategories )
.ThenInclude(s => s.Tickets)
.GroupBy(s => s.Id)
.Select(t => new Cata {
Name = t.FirstOrDefault().Name,
Children = GetChildern(t.FirstOrDefault().SubCategories )
});
public List<SubCat> GetChildern(IEnumerable<SubCategories> subs)
{
var output = new List<SubCat>();
foreach (var sub in subs) {
var subcat = new SubCat();
subcat.Name = sub.Name;
if (sub.Tickets != null) {
subcat.Size = sub.Tickets.Count;
}
output.Add(subcat);
}
return output;
}
With the Query above the ticket is always zero for all, but tickets exists.
I don't see why you need to do a group by if you start your query in Categories
var result= Entities
.Include(h => h.TicketSubCategories)
.ThenInclude(s => s.Tickets)
.Select(t => new Cata {
Name = t.Name,
Children= t.TicketSubCategories
.Select(ts=>new SubCat{
Name=ts.Name,
Count=ts.Tickets.Count()})
};
Agreed with #Ivan about he commented above, here you don't need to use a custom method, using it you will force the projection of your query to be executed on the client side and not on the server (your DB)
So each Category has zero or more SubCategories, and each SubCategory has zero or more Tickets. Each Ticket belongs to exactly one SubCategory and each SubCategory belongs to exactly one Category
And you want a query, that results in groups of SubCategories that have the same Category. You want some (or all) properties of each SubCategory, but above all, you want the number of Tickets each SubCategory has.
All elements in every group of SubCategories belong to the same Category. You also want some (if not all) properties of this Category.
The solution is to group all SubCategories into groups of same Category (for efficiency use CategoryId). Then use a Select to get the properties you want.
var result = SubCategories
// group them into groups with same CategoryId
.GroupBy(subCategory => subCategory.CategoryId
// from every group take the properties you want:
.Select(group => new
{
// All SubCategories in one group belong to the same Category.
// For efficiency, take only the Category properties you plan to use,
CommonCategory = group.Key.Select(category => new
{
// take the category properties you want to use
}
// The group has a lot of SubCategories.
// For each subcategory select only the properties you want to use
SubCategories = group.Select(subCategory => new
{
// one of the properties you want is the number of Tickets of this SubCategory:
TicketCount = subCategory.Tickets.Count(),
// for efficiency: select only SubCategory properties you plan to use:
Property1 = subCategory.Property1,
Property2 = subCategory.Property2,
...
}),
});
So the result is a sequence of objects. Each object has two Properties:
SubCategories: a sequence of some properties of all SubCategories that belong to the same Category.
CommonCategory. Several properties of the Category that all SubCategories belong to.
The SubCategories is a sequence. Each element of the sequence is an object with several properties:
TicketCount: the number of tickets in the SubCategory
other properties: several other properties of the SubCategory
From this it is easy to construct the code to GetChildren
I'm not sure how to write LINQ query. I have these models:
class Category
{
ICollection<Thread> Threads {get;set;}
ICollection<Category> SubCategories {get;set;}
}
class Thread
{
Category Category {get;set;}
//Some Stuff
}
So, there could be categories linked like -
Category1
Category2
Category3
Category4
Category5
Category6
I want find all threads linked to Category2 and it SubCategories(3, 4, 5).
I thought about just take Category1 form db, and using C# recursive function build List of threads i need, but i feel it's bad idea.
Any ideas or links would be great. Thank you!
There code, but there is Topics(in Threads), i didnt mention it couse it's not rly matter(at least i think so)
public ActionResult ShowCategoryTopics(int id)
{
var category = db.Categories.Where(x => x.Id == id).FirstOrDefault();
var topics = GetTopics(category);
return View();
}
public List<Topic> GetTopics(Category category)
{
List<Topic> topics = new List<Topic>();
if (!category.IsDeleted && !category.IsHidden)
return null;
foreach (Thread thread in category.Threads)
{
topics.AddRange(thread.Topics.Where(x => !x.IsDeleted).ToList());
}
foreach(Category childCategory in category.SubCategories)
{
topics.AddRange(GetTopics(childCategory));
}
return topics;
}
While EF can load joined records lazily and transparently, it can't load recursive joined records cause it's too complicate.
So, first of all, remove the Category.Threads navigation property:
public class Category
{
public int Id { get; set; }
public int? ParentId { get; set; }
// you can remove the attribute
[ForeignKey(nameof(ParentId))]
public virtual Category Parent { get; set; }
public string Title { get; set; }
public virtual ICollection<Category> SubCategories { get; set; } = new HashSet<Category>();
}
public class Thread
{
public int Id { get; set; }
public int CategoryId { get; set; }
// you can remove the attribute
[ForeignKey(nameof(Category))]
public Category Category { get; set; }
public string Title { get; set; }
}
Now you can use Common Table Expressions to recursive query and Database.SqlQuery<TElement> method to load the result of the query.
This is the SQL query to get all Threads corresponded to the specified #CategoryId and all its subcategories:
WITH RecursiveCategories(Id, ParentId, Title)
AS
(
SELECT Id, ParentId
FROM dbo.Categories AS c1
WHERE Id = #CategoryId
UNION ALL
SELECT Id, ParentId
FROM dbo.Categories AS c2
INNER JOIN c1 ON c2.ParentId = c1.Id
)
SELECT th.*
FROM dbo.Threads AS th
WHERE th.CategoryId IN (SELECT Id FROM RecursiveCategories)
The method to load threads of specified category recursively:
public IEnumerable<Thread> GetAllRecursivelyByCategoryId(int categoryId)
{
var query = #"WITH RecursiveCategories(Id, ParentId, Title)
AS
(
SELECT Id, ParentId
FROM dbo.Categories AS c1
WHERE Id = #CategoryId
UNION ALL
SELECT Id, ParentId
FROM dbo.Categories AS c2
INNER JOIN c1 ON c2.ParentId = c1.Id
)
SELECT th.*
FROM dbo.Threads AS th
WHERE th.CategoryId IN (SELECT Id FROM RecursiveCategories)";
var parameter = new SqlParameter("CategoryId", categoryId);
return _dbContext.Database
.SqlQuery<Thread>(query, parameter)
.AsEnumerable();
}
This method runs the recursive query and maps the result to enumerable of threads. Here is only one request to the SQL server, and the response contains only necessary threads.
The way to do this all in database would be to use a recursive Common Table Expression (CTE) to extract all the category hierarchy. However this is a bit difficult to implement using Linq without resorting to direct SQL.
As you state there will only be about 100 or so categories it may me simpler to do the category extraction in the code rather than database.
I'm assuming you have the foreign key columns as wells as the navigation properties.
First a Helper function, converts a list of categories to an enumerable of nested ids;
static IEnumerable<int> GetCategoryIds(IList<Category> categories, int? targetId) {
if (!targetId.HasValue) {
yield break;
}
yield return targetId;
foreach (var id in categories.Where(x => x.ParentId==targetId).SelectMany(x => GetCategoryIds(x.Id))) {
yield return id;
}
}
Now your query
var ids = GetCategoryIds(db.Categories.ToList(), 2).ToList();
var threads = db.Threads.Where(x => ids.Contains(x.CategoryId));
my EF Poco classes structure as below and what I try to achieve is to get all CategoryProducts Including Products and ProductName but only ProductNames having languageid=1
I dont want to filter Root objects. I need not all productnames are loaded but only productname with languageid=1
I dont know how to achieve this. for example, I tried query below
var products = db.CategoryProduct.Include("Product.ProductName").
Where(p=>p.Product.ProductName.Any(a=>a.LanguageId==1)).ToList();
But this one filters all categoryProducts which have ProductName with languageid=1. this is not what I want because all products have names in 5 different languages. I just dont want to load eagerly for each product 5 times but only 1 time for languageid=1
public partial class CategoryProduct
{
[Key]
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
public partial class Product
{
public virtual ICollection<ProductName> ProductName { get; set; }
}
public partial class ProductName
{
public int ProductId { get; set; }
public int LanguageId { get; set; }
public string Name { get; set; }
public virtual Product Product { get; set; }
}
I'm afraid that using eager loading you can't filter the related entities unless you project your query in an anonymous type or a DTO:
var products = db.CategoryProduct.Include(c=>c.Product.ProductName)
.Select(c=> new CategoryProductDTO()
{
//...
ProductNames= c.Product.ProductName.Where(a=>a.LanguageId==1)
})
.ToList();
If you don't want to project your query and you want to load specific related entities, then I suggest you to use Explicit Loading:
var catproduct = db.CategoryProduct.Include(c=>c.Product).FirstOrDefault();// This is just an example, select the category product that you need to load the related entities
context.Entry(catproduct.Product)
.Collection(b => b.ProductName)
.Query()
.Where(pn => pn.LanguageId==1)
.Load();
But IMHO the first variant is the way to go
this is not easily doable, but something like the following may do it:
from cp in db.CategoryProduct.Include(x => x.Product)
from pn in cp.Product.ProductName.Where(x => x.LanguageId == 1).DefaultIfEmpty()
select new {
Cat = cp,
Name1 = pn.Name
}
then you have you product in Cat.Product, and the name in Name1.
The basic idea is to set a LEFT JOIN on ProductName.
Suppose the following mapped classes:
public class Item
{
public virtual int Id { get; set; }
public virtual IEnumerable<History> Histories { get; set; }
}
public class History
{
public virtual int Id { get; set; }
public virtual Item Item { get; set; }
public virtual DateTime Date { get; set; }
public virtual HistoryType HistoryType { get; set; }
}
public enum HistoryType
{
A = 1,
B = 2
}
Now I want to be able to fetch all items ordered by their latest History Date, where History is of HistoryType = A. Not all items have history so I guess a left join is needed.
What I need is a query for Fluent NHibernate but it would also be nice to see a correct SQL query for this.
Standard SQL Query for your case will look like this. Yes you need a Left Join to get all items.
all items ordered by their latest History Date, where History is of HistoryType = A. even for items that has not hisotry
SELECT i.id, h.id, h.datetime, h.historytype
FROM ITEMS i
LEFT JOIN HISTORY h
ON i.id = h.itemid
WHERE h.HistoryType = 'A'
ORDER BY h.Datetime DESC
If you choose to show a user-defined value istead of null. E.g. when an item has no history, history table reocrds returned will be null. Thus a function like Colasce could help you to add syntatic sugar to your query :)
Please apply the correct syntax (e.g. whether to use backtics/inverted commas for String/varchar columns values) for Fluent NHibernate.