I would like to group my LINQ query by ItemNumber and return the whole table with the total for Quantity.
Example:
ItemNumber - ItemName - Quantity
100 Item1 1
150 Item2 2
100 Item1 2
200 Item3 1
150 Item2 2
Should be:
ItemNumber - ItemName - Quantity
100 Item1 3
150 Item2 4
200 Item3 1
This is the query I am trying to group:
public IQueryable<WebsiteOrderStatus> GetOrderStatusByAccountNumberWithoutDeleted
(string accountNumber)
{
return db.WebsiteOrderStatus
.Where(x => x.AccountNumber == accountNumber && x.LastUpdatedStatus != 1);
}
And my best result so far(this can't compile though):
public IQueryable<IGrouping<Int32?, WebsiteOrderStatus>> lol(string accountNumber)
{
db.WebsiteOrderStatus
.Where(x => x.AccountNumber == accountNumber && x.LastUpdatedStatus != 1)
.GroupBy(g => g.ItemNumber)
.Select(g => new
{
g.Key.ItemNumber,
Column1 = (Int32?)g.Sum(p => p.Quantity)
});
}
EDIT:
Thanks for the replies everyone, I must face it. Theese anonymous types are pretty hard to work with in my opinion, so I found another solution.
I made another method, which sums the quantity of the users items and grouped the first one.
public IQueryable<WebsiteOrderStatus> GetOrderStatusByAccountNumberWithoutDeleted(string accountNumber)
{
return db.WebsiteOrderStatus.Where(x => x.AccountNumber == accountNumber && x.LastUpdatedStatus != 1).GroupBy(x => x.ItemNumber).Select(grp => grp.First());
}
public int GetQuantityOfUsersItem(string accountNumber, string itemNumber)
{
return db.WebsiteOrderStatus.Where(x => x.ItemNumber == itemNumber && x.AccountNumber == accountNumber).Sum(x => x.Quantity);
}
At the page where I have my gridview I did:
var query = websiteOrderStatusRep.GetOrderStatusByAccountNumberWithoutDeleted(AppSession.CurrentLoginTicket.AccountNumber).Select(x => new { x.ItemName, x.ItemNumber, x.FormatName, x.Price, x.Status, x.Levering, Quantity = websiteOrderStatusRep.GetQuantityOfUsersItem(x.AccountNumber, x.ItemNumber)});
public IQueryable<IGrouping<Int32?, WebsiteOrderStatus>> lol(string accountNumber)
{
db.WebsiteOrderStatus
.Where(x => x.AccountNumber == accountNumber && x.LastUpdatedStatus != 1)
.GroupBy(g => g.ItemNumber)
.Select(g => new
{
ItemNumber = g.Key,
ItemName = g.First().ItemName,
Count = g.Sum(item => item.Quantity)
});
}
public IQueryable<OrderStatus > lol(string accountNumber)
{
return db.WebsiteOrderStatus
.Where(x => x.AccountNumber == accountNumber && x.LastUpdatedStatus != 1)
.GroupBy(g => g.ItemNumber)
.Select(g =>
new OrderStatus //This is your custom class, for binding only
{
ItemNumber = g.Key,
ItemName = g.First().ItemName,
Quantity = g.Sum(g => g.Quantity)
}
);
}
I think the Select should be:
.Select(g => new
{
ItemNumber = g.Key,
Column1 = (Int32?)g.Sum(p => p.Quantity)
});
Note the change in the first line of the anonymous type. The key of the grouping is already the item number.
The only problems I see with your query are
Missing return statement as per comments
The select statement should be:
-
.Select(g => new {
ItemNumber = g.Key,
Total = g.Sum(p => p.Quantity)
});
EDIT: If you want to get, lets say ItemNumber and ItemName , in the resulting object, you must also group on those fields
db.WebsiteOrderStatus
.Where(x => x.AccountNumber == accountNumber && x.LastUpdatedStatus != 1)
.GroupBy(g => new { g.ItemNumber, g.ItemName })
.Select(g => new
{
ItemNumber = g.Key.ItemNumber,
ItemName = g.Key.ItemName,
Count = g.Sum(item => item.Quantity)
});
You cannot use anonymous type for return value type. So you will never compile the code.
Also your linq expression has IQueryable< [anonymous type] > result type.
I believe that you can do something like this:
public IQueryable<OrderStatus> lol(string accountNumber)
{
db.WebsiteOrderStatus
.Where(order => order.AccountNumber == accountNumber && order.LastUpdatedStatus != 1)
.GroupBy(order => order.ItemNumber)
.Select(grouping => new OrderStatus //This is your custom class, for binding only
{
ItemNumber = grouping.Key,
ItemName = grouping.First().ItemName,
Quantity = grouping.Sum(order => order.Quantity)
});
}
I`ve fixed my answer too :)
Related
I have Linq which counts the goods, the problem is that the names that I pass, they do not work
ProductName, CompanyName, CustomerName,
Maybe there is a error in Linq?
It produces many anonymous methods that have these fields, but after ToList() everything does not work
public async Task<IEnumerable<SalesReportItem>> GetReportData(DateTime dateStart, DateTime dateEnd)
{
dateStart = new DateTime(2000, 1, 1);
var context = await _contextFactory.CreateDbContextAsync();
var queryable = context.SalesTransactionRecords.Join(
context.Products,
salesTransactionRecords => salesTransactionRecords.ProductId,
products => products.Id,
(salesTransactionRecords, products) =>
new
{
salesTransactionRecords,
products
})
.Join(context.Companies,
combinedEntry => combinedEntry.salesTransactionRecords.CompanyId,
company => company.Id,
(combinedEntry, company) => new
{
combinedEntry,
company
})
.Join(context.VendorCustomers,
combinedEntryAgain => combinedEntryAgain.combinedEntry.salesTransactionRecords.CustomerId,
vendorCustomer => vendorCustomer.Id,
(combinedEntryAgain, vendorCustomer) => new
{
CompanyName = combinedEntryAgain.company.Name,
CustomerName = vendorCustomer.Name,
ProductId = combinedEntryAgain.combinedEntry.products.Id,
ProductName = combinedEntryAgain.combinedEntry.products.Name,
combinedEntryAgain.combinedEntry.salesTransactionRecords.MovementType,
combinedEntryAgain.combinedEntry.salesTransactionRecords.Period,
combinedEntryAgain.combinedEntry.salesTransactionRecords.Quantity,
combinedEntryAgain.combinedEntry.salesTransactionRecords.Amount,
}).Where(x => x.Period >= dateStart && x.Period <= dateEnd)
.GroupBy(combinedEntryAgain => new
{
combinedEntryAgain.ProductId,
combinedEntryAgain.ProductName,
combinedEntryAgain.CompanyName,
combinedEntryAgain.CustomerName,
}
).Select(x => new SalesReportItem
{
ProductId = x.Key.ProductId,
Quantity = x.Sum(a => a.Quantity),
Amount = x.Sum(x => (x.MovementType == TableMovementType.Income ? x.Amount : -(x.Amount)))
});
var items = await queryable.ToListAsync();
return _mapper.Map<IEnumerable<SalesReportItem>>(items);
}
my mistake was that I did not specify the fields in the select, otherwise everything is buzzing, the upper code is working
Select(x => new SalesReportItem
{
ProductId = x.Key.ProductId,
ProductName = x.Key.ProductName,
CompanyName = x.Key.CompanyName,
CustomerName = x.Key.CustomerName,
Quantity = x.Sum(x => (x.MovementType == TableMovementType.Income ? x.Quantity : - x.Quantity)),
Amount = x.Sum(x => (x.MovementType == TableMovementType.Income? x.Amount: - x.Amount))
});
Thanks for the help
Hans Kesting
I want to remove columns which I don't need to display!
Basically customer needs to see only FirstName and LastName, so I wanted to return only FirstName and LastName to end user.
But my app breaks If I remove other props from select:
var product = await _context.Products.OrderBy(p => p.CreatedDate)
.Select(x => new Product
{
ProductId = x.ProductId,
GroupId = x.GroupId,
ProductStatus = x.ProductStatus,
Title = x.Title,
Price = x.Price
}).FirstOrDefaultAsync(u => u.ProductId == dbObj.Id && u.GroupId == ProductGroup.Drinks && u.ProductStatus.Id == (int)ProductStatusEnum.Active);
I would like to have something like this:
var product = await _context.Products.OrderBy(p => p.CreatedDate)
.Select(x => new Product
{
Title = x.Title,
Price = x.Price
}).FirstOrDefaultAsync(u => u.ProductId == dbObj.Id && u.GroupId == ProductGroup.Drinks && u.ProductStatus.Id == (int)ProductStatusEnum.Active);
In classic (T-SQL) SQL statement I should be able to have columns in WHERE but NOT IN SELECT also, and here in LINQ looks like I must have columns in SELECT if I use them in WHERE.
Thanks
Cheers
Your query is not selecting columns, it's selecting new Product objects which you are setting the property values on. In the second case, you are only setting the Title and Price properties, and the others will be whatever the defaults are.
You may want to look at anonymous types, e.g.:
.Select(x => new { Title = x.Title, Price = x.Price });
Just change the order of the Linq statements. First filter by using Where before calling Select and use an anonymous object to project into:
var product = await _context.Products
.Where(u => u.ProductId == dbObj.Id
&& u.GroupId == ProductGroup.Drinks
&& u.ProductStatus.Id == (int) ProductStatusEnum.Active)
.OrderBy(p => p.CreatedDate)
.Select(x => new
{
Title = x.Title,
Price = x.Price
}).FirstOrDefaultAsync();
This creates an anonymous type (product) with 2 properties Title and Price.
If you don't want to use an anonymous type another option is to create a class which contains only the properties you want.
public class ProductSummary
{
public string Title { get; set; }
public decimal Price { get; set; }
}
Then you can select into the concrete class:
.Select(x => new ProductSummary { Title = x.Title, Price = x.Price })
I am working on a LINQ query which includes some pivot data as below
var q = data.GroupBy(x => new
{
x.Med.Name,
x.Med.GenericName,
}).ToList().Select(g =>
new SummaryDto
{
Name= g.Key.Name,
GenericName = g.Key.GenericName,
Data2012 = g.Where(z => z.ProcessDate.Year == 2012).Count(),
Data2013 = g.Where(z => z.ProcessDate.Year == 2013).Count(),
Data2014 = g.Where(z => z.ProcessDate.Year == 2014).Count(),
Data2015 = g.Where(z => z.ProcessDate.Year == 2015).Count(),
Data2016 = g.Where(z => z.ProcessDate.Year == 2016).Count(),
Data2017 = g.Where(z => z.ProcessDate.Year == 2017).Count(),
TotalCount = g.Count(),
}).AsQueryable();
return q;
The above LINQ takes too long as it queries grp q.Count()*6 times. If there are 10000 records, then it queries 60000 times
Is there a better way to make this faster?
Add year to the group key, then group again, and harvest per-group counts:
return data.GroupBy(x => new {
x.Med.Name
, x.Med.GenericName
, x.ProcessDate.Year
}).Select(g => new {
g.Key.Name
, g.Key.GenericName
, g.Key.Year
, Count = g.Count()
}).GroupBy(g => new {
g.Name
, g.GenericName
}).Select(g => new SummaryDto {
Name = g.Key.Name
, GenericName = g.Key.GenericName
, Data2012 = g.SingleOrDefault(x => x.Year == 2012)?.Count ?? 0
, Data2013 = g.SingleOrDefault(x => x.Year == 2013)?.Count ?? 0
, Data2014 = g.SingleOrDefault(x => x.Year == 2014)?.Count ?? 0
, Data2015 = g.SingleOrDefault(x => x.Year == 2015)?.Count ?? 0
, Data2016 = g.SingleOrDefault(x => x.Year == 2016)?.Count ?? 0
, Data2017 = g.SingleOrDefault(x => x.Year == 2017)?.Count ?? 0
, TotalCount = g.Sum(x => x.Count)
}).AsQueryable();
Note: This approach is problematic, because year is hard-coded in the SummaryDto class. You would be better off passing your DTO constructor an IDictionary<int,int> with counts for each year. If you make this change, the final Select(...) would look like this:
.Select(g => new SummaryDto {
Name = g.Key.Name
, GenericName = g.Key.GenericName
, TotalCount = g.Sum(x => x.Count)
, DataByYear = g.ToDictionary(i => i.Year, i => i.Count)
}).AsQueryable();
I suggest grouping inside the group by year and then converting to a dictionary to access the counts. Whether it is faster to group with year first and then count in-memory depends on the distribution of the initial grouping, but with the database it may depend on how efficiently it can group by year, so I would test to determine which seems fastest.
In any case grouping by year after the initial grouping is about 33% faster than your query in-memory, but again it is vastly dependent on the distribution. As the number of initial groups increase, the grouping by Year queries slow down to match the original query. Note that the original query without any year counts is about 1/3 the time.
Here is grouping after the database grouping:
var q = data.GroupBy(x => new {
x.Med.Name,
x.Med.GenericName,
}).ToList().Select(g => {
var gg = g.GroupBy(d => d.ProcessDate.Year).ToDictionary(d => d.Key, d => d.Count());
return new SummaryDto {
Name = g.Key.Name,
GenericName = g.Key.GenericName,
Data2012 = gg.GetValueOrDefault(2012),
Data2013 = gg.GetValueOrDefault(2013),
Data2014 = gg.GetValueOrDefault(2014),
Data2015 = gg.GetValueOrDefault(2015),
Data2016 = gg.GetValueOrDefault(2016),
Data2017 = gg.GetValueOrDefault(2017),
TotalCount = g.Count(),
};
}).AsQueryable();
I need to get sum of my products amount in different period of times in just one row.
for example:
ID Date Amount
-------------------------
1 2017/01/01 10
1 2017/01/01 12
1 2017/04/03 5
2 2017/01/02 10
I need to get sum for spring season and summer season of each product, so we have this for product 1:
ID SumSpring SumSummer
-----------------------------
1 22 5
I have used this code:
var pDetails = ordersTotal.Select(g => new
{
g.ProductID,
DateType = (((String.Compare(g.BuyDate, "2017/01/01") >= 0 && String.Compare(g.BuyDate, "2017/03/30") <= 0)) ? "Spring" : "Summer"),
g.Amount
}).GroupBy(x => new { id = x.ProductID, type = x.DateType }).Select(x => new
{
ProductID = x.Key.id,
SumSpring = (x.Where(z => z.DateType == "Spring").Count() == 0 ? 0 : x.Where(z => z.DateType == "Spring").Sum(z => z.Amount)),
SumSummer = (x.Where(z => z.DateType == "Summer").Count() == 0 ? 0 : x.Where(z => z.DateType == "Summer").Sum(z => z.Amount)),
});
but it returns several rows for each product which is not what I expected and I do not know why!
This is the output for one product:
ID SumSpring SumSummer
-----------------------------
1 22 0
1 0 5
two rows for one product, but it should be one!
You can get the quarter of a year in this way:
int quarter = (month + 2) / 3;
But don't include it in the GroupBy, you only want to group by ProductID
var pDetails = ordersTotal.Select(x => new
{
x.ProductID,
x.Amount,
Quarter = (x.BuyDate.Month + 2) / 3
})
.Where(x => x.Quarter == 1 || x.Quarter == 2) // it seems you only want these
.GroupBy(x => x.ProductID)
.Select(g => new
{
ProductID = g.Key,
SumSpring = g.Where(x => x.Quarter == 1)
.Select(x => x.Amount)
.DefaultIfEmpty(0)
.Sum(),
SumSummer = g.Where(x => x.Quarter == 2)
.Select(x => x.Amount)
.DefaultIfEmpty(0)
.Sum()
});
Note that this query doesn't care about the year. But it seems you don't care about it anyway.
Try following. the major issue is that you are grouping by Amount :
var pDetails = ordersTotal.Select(g => new
{
ProductID = g.ProductID,
DateType = ((g.BuyDate >= DateTime.Parse("1/1/17")) && (g.BuyDate <= DateTime.Parse("3/30.17"))) ? "Spring" : "Summer",
Amount = g.Amount
}).GroupBy(x => new { id = x.ProductID}).Select(x => new
{
ProductID = x.Key.id,
SumSpring = x.Where(z => z.DateType == "Spring").Sum(z => z.Amount),
SumSummer = x.Where(z => z.DateType == "Summer").Sum(z => z.Amount),
}).ToList();
I have a lambda expression which returns the records I'm looking for, except that the names are not distinct, so I need to group by name, here is what I have, can someone give me a hand with this.
var cities = _context.country.OrderBy(c => c.Name)
.Where(c => c.Feature_Code != featureCode
&& c.Feature_Code != featureCode2
&& c.Country_Code == CountryCode
&& c.Admin1_code == StateId)
.Select(c => new CityViewModel
{
CityId = c.CountryID,
CityName = c.Name
}
).AsQueryable();
You could use:
var cities = _context.country
.Where(c => /*Your where conditions*/)
.GroupBy(c => c.Name)
.OrderBy(grp => grp.Key)
.Select(grp => new CityViewModel
{
CityId = grp.First().CountryID,
CityName = grp.Key
});
OrderBy should be used after Where.
Depending on what you want, you could add:
.GroupBy(c => c.CityName)
You could use:
var cities = _context.country.Distinct(c => c.Name).Where(c => c.Feature_Code != featureCode
&& c.Feature_Code != featureCode2
&& c.Country_Code == CountryCode
&& c.Admin1_code == StateId)
.Select(c => new CityViewModel
{
CityId = c.CountryID,
CityName = c.Name
}
This should return only one instance of each name.