简体   繁体   中英

How can I find the city where the product number x is most ordered in a Linq query?

My class structures are as follows;

public class Users
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public int CityId { get; set; }
}
public class Order
{
    public int Id { get; set; }
    public int UserId { get; set; }
}

public class OrderDetail
{
    public int Id { get; set; }
    public int OrderId { get; set; }
    public int ProductId { get; set; }
    public int Amount { get; set; }
}

The query I wants to write as a Linq is as follows;

select * from
(select u.CityId, sum(od.Amount),
        ROW_NUMBER() over(order by sum(od.Amount) desc) as rn
    from Order o
        inner join OrderDetail od on o.Id = od.OrderId
        inner join Users u on u.Id = o.UserId
    where od.ProductId = x
    group by u.CityId
) 
where rn = 1;

I have tried many things but I could not fit the structure. Thank you for now.

Apparently you have a table Cities with a one-to-many relation with Users : Every City has zero or more Users, every User lives in exactly one City, namely the City that the foreign key User.CityId refers to.

Similarly, there is a one-to-many relation between Users and Orders : Every User has placed zero or more Orders, every Order has been placed by exactly one User, namely the User that the foreign key Order.UserId refers to.

Two other one-to-many relations: between Orders and OrderDetails and Products and OrderDetails : every Product is a Product in zero or more OrderDatails. Every OrderDetail is about exactly one Product, namely the Product that foreign key ProductId refers to.

How can I find the city where the product number x is most ordered in a Linq query?

You are not interested in Users, Orders, OrderDetails, not even interested in all Products, only in Product X. And you want the City where this Product X is most ordered.

To do this, you need to inner join Cities - Users - Orders - OrderDetails, and keep only combinations of [City; OrderDetail.Amount; Product.Id].

Remove all combinations that are not about "Product number X". The remaining combinations are all about Product X. GroupBy City, and Sum all the Amounts of all OrderDetails of this City.

Order By Descending Sum, and keep the first one.

My, this is going to be a big LINQ. Luckily not really difficult.

First we do inner joins to make the combinations [City, User, Order, OrderDetails], and we keep only the properties that we need: [City, Amount, ProductId]

var productNumberX = ...;
var result = dbContext.Cities.Join(dbContext.Users,   // inner join Cities and Users
city => city.Id,                               // from every city take the primary key
user => user.CityId,                           // from every user take the foreign key

(city, user) => new                            // when they match, make one new
{
    City = city,
    UserId = user.Id,                     // for the next join we only need the UserId
})

Continue the LINQ with the 2nd join: join with Orders:

.Join(dbContext.Orders,
previousJoinResult => previousJoinResult.UserId,  // from previous join take the UserId
order => order.UserId,                            // from the Order take the UserId

(previousJoinResult, order) => new                // when they match, make one new
{
    City = previousJoinResult.City,
    OrderId = order.Id,                  // for the next Join we only need the OrderId
})

3rd join: with the OrderDetails. Remember amount and ProductId.

.Join(dbContext.OrderDetails,
previousJoinResult => previousJoinResult.OrderId,   // get the OrderId from previous join
orderDetail => orderDetail.OrderId,                 // get foreign key OrderId

(previousJoinResult, orderDetail) => new
{
    City = previousJoinResult.City,
    Amount = orderDetail.Amount,
    ProductId = orderDetail.ProductId,
})

Keep only [City, Amount, ProductId] combinations that are about "Product number X":

.Where(joinResult => joinResult.ProductId == productNumberX)

Phew, so now we have combinations [City, Amount, ProductId] of all productNumberX. Make groups of Same City. Use the overload of Queryable.GroupBy that has parameters elementSelector and resultSelector .

So:
KeySelector: groups of same City;
ElementSelector: only the Amount of all joinresult combinations;
ResultSelector: every city with the sum of its amounts

We don't need the ProductId, they are all the same anyway.

.GroupBy(joinResult => joinResult.City,

// elementSelector: keep only the Amount of the JoinResult
joinResult => joinResult.Amount,

// parameter resultSelector: for every City, and all Amounts of orderDetails of Orders
// that were placed by Users in this City, make one new
// containing the City and the sum of the amounts
(city, amountsInThisCity) => new
{
    City = city,
    TotalAmount = amountsInThisCity.Sum(),
})

So, for every City, that had at least one User that placed an Order that had an OrderDetail about Product X, you now have the sum of the Amounts of all OrderDetails of product X of all Orders of all Users in the city.

Order By Descending TotalAmount, and take the first:

.OrderByDescending(city => city.TotalAmount)
.FirstOrDefault();

Result:

the city where the product number x is most ordered, or null if no one ever ordered product number X.

Try following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Context db = new Context();
            int productId = 123;
            var results = (from u in db.Users
                           join o in db.Order on u.Id equals o.UserId
                           join od in db.OrderDetails.Where(x => x.ProductId == productId) on o.Id equals od.OrderId
                           select new { u = u, o = o, od = od }
                           ).GroupBy(x => x.u.CityId)
                           .Select(x => new { city = x.Key, sum = x.Sum(y => y.od.Amount) })
                           .OrderByDescending(x => x.sum)
                           .ToList();
        }
    }
    public class Context
    {
        public List<Users> Users { get;set;}
        public List<Order> Order { get; set; }
        public List<OrderDetail> OrderDetails { get; set; }
    }
    public class Users
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Surname { get; set; }
        public int CityId { get; set; }
    }
    public class Order
    {
        public int Id { get; set; }
        public int UserId { get; set; }
    }

    public class OrderDetail
    {
        public int Id { get; set; }
        public int OrderId { get; set; }
        public int ProductId { get; set; }
        public int Amount { get; set; }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM