My models look like:
public class ReturnItem
{
public int returnItemId { get ; set; }
public int returnRequestId { get; set; }
public int quantity { get; set; }
public string item { get; set; }
}
public class ReturnRequest
{
public int returnRequestId { get; set; }
public string orderNumber { get; set; }
public IEnumerable<ReturnItem> returnItems { get; set; }
}
And I have the following query:
SELECT item, sum(quantity)
FROM ReturnItem
JOIN ReturnRequest
ON ReturnRequest.returnRequestId = ReturnItem.returnRequestId
WHERE ReturnRequest.orderNumber = '1XX'
GROUP BY item
How do I convert the query to Entity Framework and return a List<ReturnItem>? Can I use .Include instead of .Join?
from ri in db.ReturnItems
join rr in db.ReturnRequests
on ri.returnRequestId equals rr.returnRequestId
where rr.orderNumber == "1XX"
group ri by ri.item into g
select new {
Item = g.Key,
Quantity = g.Sum(i => i.quantity)
}
You can't use Include instead of Join because Include translated into Left Outer Join but you need Inner Join here.
But you can use navigation property to perform join implicitly:
db.ReturnRequests
.Where(rr => rr.orderNumber == "1XX")
.SelectMany(rr => rr.returnItems)
.GroupBy(ri => ri.item)
.Select(g => new {
Item = g.Key,
Quantity = g.Sum(ri => ri.quantity)
});
Related
I have three tables like this:
public partial class PriceSite
{
public DateTime ValidFromGmtDtm { get; set; }
public long PriceSiteId { get; set; }
public virtual ICollection<PriceSiteProduct> PriceSiteProduct { get; set; }
}
public partial class PriceSiteProduct
{
public long PriceSiteId { get; set; }
public long ProductId { get; set; }
public virtual Product Product { get; set; }
}
public partial class Product
{
public string ProductCd { get; set; }
public string ProductNm { get; set; }
public long ProductId { get; set; }
public virtual ICollection<PriceSiteProduct> PriceSiteProduct { get; set; }
}
We need to group the data by Productname and choose the product with highest ValidFromGmtDtm (Pricesite).
I tried this query
using (var dbContext = _dbContextProvider.DbContext)
{
var data = from t1 in dbContext.PriceSite
join t2 in dbContext.PriceSiteProduct
on t1.PriceSiteId equals t2.PriceSiteId
join t3 in dbContext.Product
on t2.ProductId equals t3.ProductId
where t1.ValidFromGmtDtm > shellSiteNotification.ValidFromDtm
select new
{
PriceSiteId = t1.PriceSiteId,
ValidFrom = t1.ValidFromGmtDtm,
ProductId = t3.ProductId,
ProductNm = t3.ProductNm,
ProductValues = t3.PumpPriceProduct
};
var res1 = data.ToList();
var data2 = from element in res1
group element by element.ProductNm into groups
select groups.OrderByDescending(p => p.ValidFrom).FirstOrDefault();
}
I am getting timeout error by running this query.
I also tried this query:
var grouped = from priceSite in _dbContextProvider.DbContext.PriceSite
where priceSite.ValidFromGmtDtm < shellSiteNotification.ValidFromDtm
from priceSiteProduct in priceSite.PriceSiteProduct
group priceSiteProduct by priceSiteProduct.Product.ProductNm into produtGroup
select new
{
ProductNamKey = produtGroup.Key,
produtGroup = produtGroup.OrderByDescending(x => x.PriceSite.ValidFromGmtDtm)
};
This code throws a runtime error as we cannot access the navigation property in select in EF Core.
I also tried another query - but this also results in a timeout
var result = from s in _dbContextProvider.FreshReadOnlyPricingDbContext.PriceSite
from r in s.PriceSiteProduct
let p = r.Product
select new
{
PriceSiteId = s.PriceSiteId,
ValidFrom = s.ValidFromGmtDtm,
ProductId = p.ProductId,
ProductNm = p.ProductNm,
ProductValues = p.PumpPriceProduct
};
var list = result.ToList();
It is known EF and SQL limitation, you cannot get items after grouping. Only Key and aggregation result is supported. So basically EF proposes to put ToList() before grouping and it means that you will load several million records to the client.
Without third party extensions which can do that effectively, you can use the following workaround:
using (var dbContext = _dbContextProvider.DbContext)
{
var dataQuery =
from t1 in dbContext.PriceSite
join t2 in dbContext.PriceSiteProduct
on t1.PriceSiteId equals t2.PriceSiteId
join t3 in dbContext.Product
on t2.ProductId equals t3.ProductId
where t1.ValidFromGmtDtm > shellSiteNotification.ValidFromDtm
select new
{
PriceSiteId = t1.PriceSiteId,
ValidFrom = t1.ValidFromGmtDtm,
ProductId = t3.ProductId,
ProductNm = t3.ProductNm,
ProductValues = t3.PumpPriceProduct
};
var query =
from key in dataQuery.Select(d => new { d.ProductNm }).Distinct()
from d in dataQuery.Where(d => d.ProductNm == key.ProductNm)
.OrderByDescending(d => d.ValidFrom)
.Take(1)
select d;
var result = query.ToList();
}
This question already has answers here:
Check if some items are same in a c# list
(1 answer)
Group by in LINQ
(11 answers)
Closed 4 years ago.
I have a list of type ProductDetailDTO.
List<ProductDetailDTO> productDTOs;
public class ProductDetailDTO
{
public int ProductId { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public byte[] Image { get; set; }
public string Description { get; set; }
public string Brand { get; set; }
public string GUID { get; set; }
public string VariantName { get; set; }
public string VariantValue { get; set; }
public decimal Price { get; set; }
}
I have used linq to bind the data to the list.
var productDetails = (from product in ekartEntities.Products
join productImage in ekartEntities.ProductImages on product.ProductId equals productImage.ProductId
join category in ekartEntities.ProductCategories on product.Category equals category.CategoryId
join mapping in ekartEntities.ProductVariantMappings on product.ProductId equals mapping.ProductId
join variant in ekartEntities.ProductVariants on mapping.ProductVariantId equals variant.ProductVariantId
join inventory in ekartEntities.Inventories on mapping.GUID equals inventory.Guid
where product.ProductId == productDetailDTO.ProductId
select new ProductDetailDTO()
{
ProductId = product.ProductId,
Name = product.Name,
Category = category.Name,
Description = product.Description,
Brand = product.Brand,
Image = productImage.Image,
GUID = mapping.GUID.ToString(),
VariantName = variant.Name,
VariantValue = mapping.Value,
Price = inventory.Price
}).ToList();
Now, I want to display all the variants (VariantName and VariantValue) with the same GUIDs together. How can I achieve that?
You can use GroupBy and Select like this:
var variants = productDTOs
.GroupBy(k => k.GUID)
.Select(v => v
.Select(variant => new
{
variant.VariantName,
variant.VariantValue
}));
You can use Group By
group p by p.GUID into g
select new { Id = g.Key, ProductDetail = g.ToList()).ToList();
If you have tables before group by, then you can add new object in the group itself
group new { p.xyz, n.xyz }
by new { p.GUID } into g
otherwise use let to save the intermediate object in an object and do grouping on it
I have 3 classes and trying to use LINQ methods to perform an INNER JOIN and a LEFT JOIN. I'm able to perform each separately, but no luck together since I can't even figure out the syntax.
Ultimately, the SQL I'd write would be:
SELECT *
FROM [Group] AS [g]
INNER JOIN [Section] AS [s] ON [s].[GroupId] = [g].[Id]
LEFT OUTER JOIN [Course] AS [c] ON [c].[SectionId] = [s].[Id]
Classes
public class Group {
public int Id { get; set; }
public int Name { get; set; }
public bool IsActive { get; set; }
public ICollection<Section> Sections { get; set; }
}
public class Section {
public int Id { get; set; }
public int Name { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
public bool IsActive { get; set; }
public ICollection<Course> Courses { get; set; }
}
public class Course {
public int Id { get; set; }
public int UserId { get; set; }
public int Name { get; set; }
public int SectionId { get; set; }
public bool IsActive { get; set; }
}
Samples
I want the result to be of type Group. I successfully performed the LEFT JOIN between Section and Course, but then I have an object of type IQueryable<a>, which is not what I want, sinceGroup`.
var result = db.Section
.GroupJoin(db.Course,
s => s.Id,
c => c.SectionId,
(s, c) => new { s, c = c.DefaultIfEmpty() })
.SelectMany(s => s.c.Select(c => new { s = s.s, c }));
I also tried this, but returns NULL because this performs an INNER JOIN on all tables, and the user has not entered any Courses.
var result = db.Groups
.Where(g => g.IsActive)
.Include(g => g.Sections)
.Include(g => g.Sections.Select(s => s.Courses))
.Where(g => g.Sections.Any(s => s.IsActive && s.Courses.Any(c => c.UserId == _userId && c.IsActive)))
.ToList();
Question
How can I perform an INNER and a LEFT JOIN with the least number of calls to the database and get a result of type Group?
Desired Result
I would like to have 1 object of type Group, but only as long as a Group has a Section. I also want to return the Courses the user has for the specific Section or return NULL.
I think what you ask for is impossible without returning a new (anonymous) object instead of Group (as demonstrated in this answer). EF will not allow you to get a filtered Course collection inside a Section because of the way relations and entity caching works, which means you can't use navigational properties for this task.
First of all, you want to have control over which related entities are loaded, so I suggest to enable lazy loading by marking the Sections and Courses collection properties as virtual in your entities (unless you've enabled lazy loading for all entities in your application) as we don't want EF to load related Sections and Courses as it would load all courses for each user anyway.
public class Group {
public int Id { get; set; }
public int Name { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Section> Sections { get; set; }
}
public class Section {
public int Id { get; set; }
public int Name { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
In method syntax, the query would probably look something like this:
var results = db.Group
.Where(g => g.IsActive)
.GroupJoin(
db.Section.Where(s => s.IsActive),
g => g.Id,
s => s.GroupId,
(g, s) => new
{
Group = g,
UserSections = s
.GroupJoin(
db.Course.Where(c => c.IsActive && c.UserId == _userId).DefaultIfEmpty(),
ss => ss.Id,
cc => cc.SectionId,
(ss, cc) => new
{
Section = ss,
UserCourses = cc
}
)
})
.ToList();
And you would consume the result as:
foreach (var result in results)
{
var group = result.Group;
foreach (var userSection in result.UserSections)
{
var section = userSection.Section;
var userCourses = userSection.UserCourses;
}
}
Now, if you don't need additional filtering of the group results on database level, you can as well go for the INNER JOIN and LEFT OUTER JOIN approach by using this LINQ query and do the grouping in-memory:
var results = db.Group
.Where(g => g.IsActive)
.Join(
db.Section.Where(s => s.IsActive),
g => g.Id,
s => s.GroupId,
(g, s) => new
{
Group = g,
UserSection = new
{
Section = s,
UserCourses = db.Course.Where(c => c.IsActive && c.UserId == _userId && c.SectionId == s.Id).DefaultIfEmpty()
}
})
.ToList() // Data gets fetched from database at this point
.GroupBy(x => x.Group) // In-memory grouping
.Select(x => new
{
Group = x.Key,
UserSections = x.Select(us => new
{
Section = us.UserSection,
UserCourses = us.UserSection.UserCourses
})
});
Remember, whenever you're trying to access group.Sections or section.Courses, you will trigger the lazy loading which will fetch all child section or courses, regardless of _userId.
Use DefaultIfEmpty to perform an outer left join
from g in db.group
join s in db.section on g.Id equals s.GroupId
join c in db.course on c.SectionId equals s.Id into courseGroup
from cg in courseGroup.DefaultIfEmpty()
select new { g, s, c };
Your SQL's type is not [Group] (Type group would be: select [Group].* from ...), anyway if you want it like that, then in its simple form it would be:
var result = db.Groups.Where( g => g.Sections.Any() );
However, if you really wanted to convert your SQL, then:
var result = from g in db.Groups
from s in g.Sections
from c in s.Courses.DefaultIfEmpty()
select new {...};
Even this would do:
var result = from g in db.Groups
select new {...};
Hint: In a well designed database with relations, you very rarely need to use join keyword. Instead use navigational properties.
Hi I have following two model classes
public class c1
{
public int id { get; set; }
public int ptId { get; set; }
public int bId { get; set; }
public int rId { get; set; }
public IEnumerable<styles> newStruct { get; set; }
}
public class styles
{
public int id { get; set; }
public int bId { get; set; }
public string desc { get; set; }
}
I am trying to write a linq query
var records = (from y in db.main
join c in db.secondary on y.bId equals c.bId
where c.id == id
select new c1
{
pId= c.pId,
id = c.id,
newStruct = new List<styles>
{
new styles
{
id=y.room_id,
desc=y.desc,
}
}
});
return records.ToList();
Problem I am having is that in newStruct is suppose to be List of all the styles but it just returns one style at a time rather than one list.
Please let me know how can it return record where inside it contains list of styles
Thanks
If you want to get a sublist by main list, you should use group by,
You can try this, but I'm not sure it worked. Becasue I couldn't compile it.
var records = (from y in db.main
join c in db.secondary on y.bId equals c.bId
where c.id == id
group c by new
{
c.pId,
c.id
} into gcs
select new c1
{
pId = c.Key.pId,
id = c.Key.id,
newStruct = from g in gcs select new styles { id=g.room_id, desc=g.desc}
});
Is this LINQ to Entities? If so, and the mappings are correct in the edmx, you can try:
var records =
from c in db.secondary
where c.id == id
select new c1
{
pId = c.pId,
id = c.id,
newStruct = c.main.Select(m => new styles { id = m.room_id, desc = m.desc })
};
return records.ToList();
I have a linq query, which is further having a subquery, I want to store the result of that query into a user defined type, my query is
var val = (from emp in Employees
join dept in Departments
on emp.EmployeeID equals dept.EmployeeID
select new Hello
{
EmployeeID = emp.EmployeeID
Spaces = (from order in Orders
join space in SpaceTypes
on order.OrderSpaceTypeID equals space.OrderSpaceTypeID
where order.EmployeeID == emp.EmployeeID group new { order, space } by new { order.OrderSpaceTypeID, space.SpaceTypeCode } into g
select new
{
ID = g.Key.SpaceTypeID,
Code = g.Key.SpaceTypeCode,
Count = g.Count()
})
}).ToList();
Definition for my Hello class is
public class Hello
{
public IEnumerable<World> Spaces { get; set; }
public int PassengerTripID { get; set; }
}
Definition for my World class is
public class World
{
public int ID { get; set; }
public string Code { get; set; }
public int Count { get; set; }
}
You are creating anonymous object but you need to specify type name World
select new World
{
ID = g.Key.SpaceTypeID,
Code = g.Key.SpaceTypeCode,
Count = g.Count()
})