I'm fairly new to Entity Framework, my tables relationship looks a bit like this
public class Customer {
public int Id { get; set; }
public string Name { get; set; }
public List<Product> Products { get; set; }
}
public class Product {
public int Id { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
I would like to make a query on the Customer table and include only the last Product created MAX(Id)
Normal SQL query would look like this
SELECT *
FROM Customer
INNER JOIN Product ON Customer.Id = Product.CustomerId
WHERE Product.Id = (SELECT MAX(Id) FROM Product WHERE CustomerId = Customers.Id)
My current EF query look like this but it return all the products...
List<Customer> customers = _context.Customers
.Include(c => c.Products)
.ToList();
I tried something like this which gives me the right results, but EF makes a bunch of query and very quickly I see this seems like wrong way to go at it
List<Customer> customers = _context.Customers
.Select(c => new Customer() {
Id = c.Id,
Name = c.Name,
c.Products = c.Products.Where(d => d.Id == c.Products.Max(max => max.Id)).ToList()
}).ToList();
I would like some suggestion, or if there's a different way to make this works.
It looks like below query can be written in a different way
SELECT *
FROM Customer
INNER JOIN Product ON Customer.Id = Product.CustomerId
WHERE Product.Id = (SELECT MAX(Id) FROM Product WHERE CustomerId = Customers.Id)
This can be written as
SELECT TOP 1 *
FROM Customer
INNER JOIN Product ON Customer.Id = Product.CustomerId
Order by Product.Id desc
Assuming customer name is required,above query can be written in LINQ or using EF as below
var customers = _context.Customers.Join(_context.Products, cu => cu.id,
p => p.CustomerId, (cu,p) => new { cu,p})
.Select( c => new { prodId = c.p.Id,customername = c.cu.Name })
.OrderByDescending( c => c.prodId).Take(1);
If you have configured navigation property 1-n I would recommend you to use:
var customers = _context.Customers
.SelectMany(c => c.Products, (c, p) => new { c, p })
.Select(b => new { prodId = b.p.Id, customername = b.c.Name })
.OrderByDescending(c => c.prodId).Take(1);
Much more clearer to me and looks better with multiple nested joins.
Related
How can I get the list of products for specific project without including project? Something similar to
select p.Id, p.Name
from product p
inner join ProjectProduct j on p.Id = j.ProductId and j.ProjectId = #Id
The closest solution that I have is
var products = await (from proj in context.Project select proj)
.Where(proj => proj.Id == 1)
.Select(proj => proj.Product)
.ToListAsync();
but this returns a List<List<Product>>
in addition I need to be able to add the order by and pagination to that
I know that I can do it by raw sql like this
var param1 = new SqlParameter("#Id", id);
var query = #"select * from product p
inner join ProjectProduct j
on p.Id = j.ProductId and j.ProjectId = #Id ";
var queryable = context.product.FromSqlRaw(query, param1).AsQueryable();
await HttpContext.AddPaginationToHeader(queryable);
var products = await queryable.OrderBy(x => x.Name).Paginate(paginationDTO).ToListAsync();
But I was hoping to do it by linq to entity
public class Project
{
public int Id { get; set; }
public string Name { get; set; }
public List<Product> Product { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public List<Project> Project { get; set; }
}
public class ProjectProduct
{
public int ProjectId { get; set; }
public int ProductId { get; set; }
}
Thanks to Harald Coppoolse's response on another post
and for those that have the same problem
here are the steps
1- we define our query as queryable
var queryable = context.Project
.Where(proj => proj.Id == id)
.SelectMany(p => p.Product).AsQueryable();
2- Now we can pass the query to SQL server like this
var products = await queryable.OrderBy(x =>
x.Name).Skip(...).Take(...).ToListAsync();
for Skip and Take we can create a generic Extension for IQueryable
and add a method to that with return of
return queryable
.Skip((paginationDTO.Page - 1) * paginationDTO.PageSize)
.Take(paginationDTO.PageSize);
and of course our paginationDTO is nothing more than a POCO class with 2 properties of page and pageSize
try this:
var products = await context.Project
.Where(proj => proj.Id == 1)
.Select( proj=>proj.Products)
.FirstOrDefaultAsync();
if you want to order product after this you can do this common way
products= products.OrderBy(p=>p.Name).ToList();
it can be put in one line but EF sometimes generates a very ridiculous Sql script, so better to do this separately.
Hi good day I am new with Entity Framework. I just wanna to know if there is a way I could improve my implementation. Here are the codes.
public async Task<List<Record>> GetRecordsByBatchId(string batchId, string source)
{
List<string> idList = new List<string>();
//[1] Get all parent ID from table 1 with a filter of source and batchId
var parentIds= await _context.Set<FirstTable>()
.Where(a => a.IsActive
&& a.BatchId.Equals(batchId)
&& a.Source.Equals(source)).Select(b => b.ParentId).ToListAsync();
if (parentIds.Count() == 0)
{
return new List<Record>();
}
//[2] Query idNumber of each parentId from [1] to SecondTable
List<long> idNumber = await _context.Set<SecondTable>()
.Where(a => parentIds.Contains(a.Id))
.Select(b => b.IdNumber).ToListAsync();
//[3] Query Record/s that contains idNumber from previous query [2]. it is possible that 1 or
//more records has same idNumber
List<Risk> recordByIdNumber = await _context.Set<SecondTable>()
.Where(a => idNumber.Contains(a.IdNumber)).ToListAsync();
//[4] In this part I just want to group the records in [3] by Id number and sort each group
//by its endorsementNumber in descending order and return the record with highest endorsement
//number for each group
return (from record in recordByIdNumber
group record by record.IdNumber into g
orderby g.Key
select g.OrderByDescending(risk =>risk.EndorsementNumber).FirstOrDefault()).ToList();
}
}
The model for the FirstTable
public class FirstTable
{
public Guid? ParentId{ get; set; }
public string BatchId { get; set; }
public string Source { get; set; }
public bool IsActive { get; set; }
}
The model for the SecondTable
public class SecondTable
{
public Guid Id{ get; set; }
public int EndorsementNumber { get; set; }
public long IdNumber { get; set; }
}
Note: I just include the necessary properties in the model.
This approach is working as expected. I just wanna know if there is a possibility that these queries could be optimized that there is only 1 query for the SecondTable table.
Any help would be greatly appreciated, thanks in advance.
Yes, queries 1-3 can and should be combined. In order to do that you need, to have navigation properties in your model. It seems that there is one-to-many relationship between FirstTable and SecondTable. Let's use Customer and Order instead.
class Customer {
int CustomerId
string BatchId
ICollection<Order> Orders
}
class Order {
int OrderId
int CustomerId
Customer Customer
Risk Risk
}
in which case you just write third query as
List<Risk> = await _context.Orders.Where(o => o.Customer.BatchId == batchId)
.Select(o => o.Risk).ToListAsync();
Obviously, I am only guessing the structure and the relationship. But hopefully, this can get you started. For me Contains() is "code smell". There is a high chance that there will be large list out of your first query, and contains() will produce a huge IN clause in the database, that can easily crash the system
var parentIds = _context.Set<FirstTable>()
.Where(a => a.IsActive
&& a.BatchId.Equals(batchId)
&& a.Source.Equals(source)).Select(b => new { b.parentId });
var risks = await (from s in _context.Set<SecondTable>()
join p in parentIds on s.Id equals p.parentId
join r in _context.Set<SecondTable>() on s.IdNumber equals r.IdNumber
select r).GroupBy(r=>r.IdNumber)
.Select(r=> r.OrderByDescending(risk =>risk.EndorsementNumber).FirstOrDefault())
.ToArrayAsync();
return risks;
You can have 1 query instead of 3. It will perform better as the number of the rows from the first query grows.
EDIT: As #SvyatoslavDanyliv mentioned in the comments, group-take operations may not work depending on the version of the EF and the provider you use. You may need to separate the query and the group by operation like below :
var result = await (from s in _context.Set<SecondTable>()
join p in parentIds on s.Id equals p.parentId
join r in _context.Set<SecondTable>() on s.IdNumber equals r.IdNumber
select r).ToArrayAsync();
var risks = result.GroupBy(r=>r.IdNumber)
.Select(r=> r.OrderByDescending(
risk =>risk.EndorsementNumber).FirstOrDefault())
.ToArray();
return risks;
In the following LINQ Query I need to display All the customers with total number of their orders placed including the customers that have not placed any orders:
Models:
Public class Customer
{
public int CustomerId { get; set; }
public string Name{ get; set; }
}
Public class Order
{
public int OrderId { get; set; }
public int CustomerId { get; set; }
}
LINQ Query: Question: How can I display ALL customers (including the one that have no order) and total number of orders per customer (zero for those whose customerID is not in Orders table)
var Query1 = from c in Customers
join o in Orders into co
from t in co.DefaultIfEmpty()
select new {CustomerID = c.CustomerId, OrderID = (t == null ? 0 : t.OrderId)};
You basically need to do a LEFT JOIN between your Customer table and Customer Order table and then do a group by on that result to count the orders for each customer.
Assuming you have a class like this
public class CustomerOrder
{
public int CustomerId { set; get; }
public int? OrderId { set; get; }
}
This class is to store each item for the left join result
Now, you need to first do a LEFT JOIN and project the result of that to a list of CustomerOrder class objects, Then do a GroupBy on top of that
var usersWithCount = (from c in db.Customers
join o in db.Orders on c.CustomerId equals o.CustomerId
into result
from sub in result.DefaultIfEmpty()
select new CustomerOrder{ OrderId= sub!=null ? sub.OrderId :(int?) null,
CustomerId = u.CustomerId }
) // Here we have the left join result.
.GroupBy(g => g.CustomerId , (k, orders) => new {
CustomerId = k,
OrderCount = orders.Count(t=>t.OrderId !=null)}).ToList();
The result stored in usersWithCount will be a collection of annonymous object with a CustomerId and OrderCount property.
This isn't very efficient since it iterates through Orders for every customer, but it'll get the job done:
var query = Customers
.Select(c => new
{
Name = c.Name,
NumOrders = Orders.Count(o => o.CustomerId = c.CustomerId)
});
foreach (var result in query)
Console.WriteLine("{0} -> {1}", result.Name, result.NumOrders);
var Query1 = from c in Customers
join o in Orders on c.CustomerId equals o.CustomerId into OrdersGroup
from item in OrdersGroup.DefaultIfEmpty(new Order { OrderId = 0, CustomerId = 0 })
select new {CustomerID = c.CustomerId, OrderID = (item == null ? 0 : item.OrderId)};
this will return all customers (even if they have no orders ) and orders
https://dotnetfiddle.net/BoHx2d
I've spent the afternoon on StackOverflow and Google looking for a way to do a simple SQL query using linq to entities. I am trying to get data from two tables joined by a many-to-many relationship. In SQL I would write the query like this:
SELECT v.[VendorID]
, t.[UnitNumber]
, t.[Name]
, t.[Address]
, t.[CityStateZip]
FROM [Tenant] t
INNER JOIN [TenantVendor] tv ON tv.[TenantID] = t.[TenantID]
INNER JOIN [Vendor] v on v.[VendorID] = tv.[VendorID]
WHERE t.[UnitNumber] LIKE '%100A%'
AND t.[CompanyID] = 17874;
I have an object in a list that I'm selecting the data into that looks like this which gets applied directly to a grid:
public class SearchObject
{
public int IDField { get; set; }
public string UniqueField { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string CityStateZip { get; set; }
}
Here's the kicker, the m2m table is only made up of the primary keys from both tables, so it doesn't have an entity to select. They show up under each others entity as a collection like this (from the tenants entity):
public virtual ICollection<Vendor> Vendors { get; set; }
I would prefer lambda expressions, but query would work fine too. The closest I have got are these two:
SearchData = DBContext.Tenants
.Where(t => t.Company.Name == CompanyName && t.UnitNumber.ToString().Contains(SearchText))
.OrderBy(DynamicSort)
.Skip(StartRow)
.Take(PageSize)
.Select(t => new SearchObject { IDField = t.Vendors, UniqueField = t.UnitNumber.ToString(), Name = t.Name, Address = t.Address, CityStateZip = t.CityStateZip })
.ToList();
That one doesn't work because t.Vendors is a collection, what I want is just the vendorID.
This one works but returns way too many records since it's missing the join between the two tables:
SearchData = (from t in DBContext.Tenants
from v in DBContext.Vendors
where t.Company.Name == CompanyName && t.UnitNumber.ToString().Contains(SearchText)
select new SearchObject { IDField = v.VendorID , UniqueField = t.UnitNumber.ToString(), Name = t.Name, Address = t.Address, CityStateZip = t.CityStateZip })
.ToList();
EDIT / UPDATE
After ElMent provieded me with the correct answer, I figured out how to achieve the same result using C# methods, SelectMany was the key.
SearchData = DBContext.Tenants
.Where(t => t.Company.Name == CompanyName && t.UnitNumber.ToString().Contains(SearchText))
.OrderBy(DynamicSort)
.Skip(StartRow)
.Take(PageSize)
.SelectMany(t=>t.Vendors.Select(v => new SearchObject { IDField = v.VendorID, UniqueField = t.UnitNumber.ToString() + " - " + v.VendorNumber, Name = t.Name, Address = t.Address, CityStateZip = t.CityStateZip }))
.ToList();
What about this. See "from v in t.Vendors" on second line.
SearchData = (from t in DBContext.Tenants
from v in t.Vendors
where t.Company.Name == CompanyName && t.UnitNumber.ToString().Contains(SearchText)
select new SearchObject { IDField = v.VendorID , UniqueField = t.UnitNumber.ToString(), Name = t.Name, Address = t.Address, CityStateZip = t.CityStateZip })
.ToList();
Mor info here:
https://msdn.microsoft.com/en-us/library/bb386932(v=vs.110).aspx
In project I have this tables:
Product(id,catalogId, manufacturerId...)
Catalog
Manufacturer
Also Product model (id, name, catalogId, catalogTitle, manufacturerId, manufacturerName).
How can write in Linq this SQL query below if I want get Product item?
SELECT Product.Name, Product.CatalogId, Product.ManufacturerId, [Catalog].Name, Manufacturer.Name
FROM Product, [Catalog], Manufacturer
WHERE [Catalog].Id=Product.CatalogId AND Manufacturer.id=Product.ManufacturerId AND Product.Active=1
First, I'll answer your question.. then address your answer to comments. To answer your question, in Linq you would do the following:
from p in Product
join c in Catalog on c.Id equals p.CatalogId
join m in Manufacturer on m.Id equals p.ManufacturerId
where p.Active == 1
select new { Name = p.Name, CatalogId = p.CatalogId, ManufacturerId = p.ManufacturerId, CatalogName = c.Name, ManufacturerName = m.Name };
This will give you an anonymous object with the items you requested. If you need to use this elsewhere (and you're not using dynamic objects), I would suggest creating a view-model, and instantiating one of those in your select.
Example:
public class ProductInfoView
{
public string Name { get; set; }
public int CatalogId { get; set; }
public int ManufacturerId { get; set; }
public string CatalogName { get; set; }
public string ManufacturerName { get; set; }
}
from p in Product
join c in Catalog on c.Id equals p.CatalogId
join m in Manufacturer on m.Id equals p.ManufacturerId
where p.Active == 1
select new ProductInfoView() { Name = p.Name, CatalogId = p.CatalogId, ManufacturerId = p.ManufacturerId, CatalogName = c.Name, ManufacturerName = m.Name };
This will make referencing your query results a little less painful.
To answer your comment, you're doing a lot of joins if all you want is the product. Your criteria will only ensure three things
Your product's Active flag is 1
Your product has an existing Catalog entry
Your product has an existing Manufacturer entry
If #2 and #3 are superfluous and you don't necessarily need the names, you could simply do:
from p in Product
where p.Active == 1
select p
If Product is a CRUD model, you could potentially deep-load it to include Manufacturer/Catalog information, or use the aforementioned view-model.
Good luck!
To combine results from multiple tables without explicitly joins:
from p in Product
from c in Catalog
from m in Manufacturer
where c.Id == p.CatalogId && m.Id == p.ManufacturerId && p.Active == 1
select new
{
p.Name,
p.CatalogId,
p.ManufacturerId,
c.Name,
m.Name
};