正如我在 cmets 中指出的,您的查询似乎过于复杂:
-
使用
…
join x in (from z in context.Xx select z) on …
而不仅仅是:
…
join x in context.Xx on …
那些检查非现有数据 (go.DefaultIfEmpty()):表单
您正在使用的join 是 inner 连接:将返回数据
仅当条件两边都存在匹配对象时。
但最后,您的问题是引用原始集合
在最后的select 子句中而不是group by 的结果中。
当然PdtDeliveryTable 中也没有SellingPrice
在最后的 select 子句中使用。
我处理此类查询的方法是逐步构建,
确保我了解每一步我在做什么。
所以第一步是进行连接。为此,我定义了
一个稍微简单的结构来尝试保持清晰(参见
这个答案的底部定义)和一些测试数据。我正在使用
LINQ to Objects,但 LINQ 运算符的语法和语义
是一样的(而且它节省了创建更复杂的项目和数据库)。
一位客户有多个订单,每个订单都有一个 SKU(库存控制
单位——产品)。订单可以选择覆盖默认价格
sku(因此使用Nullable<decimal>)。还有一些
测试数据。
第一步是检查我的加入是否正确,然后检查
我正在处理价格覆盖:
var ctx = GetTestData();
var query = from c in ctx.Customers
join o in ctx.Orders on c.CustomerId equals o.CustomerId
join s in ctx.Skus on o.SkuId equals s.SkuId
select new { Customer = c, Order = o, Sku = s };
Console.WriteLine("CustId\tCust\t\tOrder\tSku\tPaid");
foreach (var v in query) {
Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}",
v.Customer.CustomerId,
v.Customer.Name,
v.Order.OrderId,
v.Sku.SkuId,
v.Order.SpecificPrice ?? v.Sku.DefaultPrice);
}
导致
CustId 客户订单 SKU 已支付
1 Acme 公司 1 1 10.0
1 Acme 公司 2 2 15.0
1 Acme 公司 3 3 30.0
2 贝塔公司 4 1 9.99
请注意,唯一的数据是每种类型都有匹配的对象,所以
第三个客户什么都没有(没有订单),只有一行
来自第二个客户(没有任何其他 SKU 的订单):那里
没有要删除的空对象。
第二步是进行分组。这将导致
一个相当不同的数据结构,例如:
class AnonOfGroup : IEnumerable<AnonOfRow> {
KeyType Key;
}
其中AnonOfRow 是您要分组的任何类型。
因此:
var query = from c in ctx.Customers
join o in ctx.Orders on c.CustomerId equals o.CustomerId
join s in ctx.Skus on o.SkuId equals s.SkuId
select new { Customer = c, Order = o, Sku = s } into joinRes
group joinRes by joinRes.Customer.CustomerId into g
select g;
使用上述术语,Key 的类型是 CustomerId 的类型,并且
AnonOfRow 是 joinRes 的类型,这是第一个类型
select 子句。
这可以用双循环显示:
- 每个不同组的外部(使用相同的键)
- 每个组中每个对象的内部(由第一个
select 创建
子句)
所以:
Console.WriteLine("Customer");
Console.WriteLine("\tOrder\tSku\tPrice");
foreach (var grp in query) {
Console.WriteLine("{0}: {1}", grp.Key, grp.First().Customer.Name);
foreach (var row in grp) {
Console.WriteLine("\t{0}\t{1}\t{2}",
row.Order.OrderId,
row.Sku.SkuId,
row.Order.SpecificPrice ?? row.Sku.DefaultPrice);
}
}
给予:
顾客
订购 SKU 价格
1:Acme 公司
1 1 10.0
2 2 15.0
3 3 30.0
2:贝塔公司
4 1 9.99
还请注意,我可以从外部循环访问“内部数据”
通过执行一些枚举(在这种情况下,我知道客户是
每个内部对象都相同,所以我将使用第一个)。这是安全的
因为底层连接是内连接。
最后一步是对组内的每个订单求和。这个可以
在最后的select cluase 中为每个组完成。这将被处理
once 对于每个 group,但在该子句中我们可以聚合
在该组中的行上:
var query = from c in ctx.Customers
join o in ctx.Orders on c.CustomerId equals o.CustomerId
join s in ctx.Skus on o.SkuId equals s.SkuId
select new { Customer = c, Order = o, Sku = s } into joinRes
group joinRes by joinRes.Customer.CustomerId into g
select new {
CustomerId = g.Key,
CustomerName = g.First().Customer.Name,
TotalPrice = g.Sum(r => r.Order.SpecificPrice ?? r.Sku.DefaultPrice)
};
Console.WriteLine("Cust\tName\t\tTotal");
foreach (var row in query) {
Console.WriteLine("{0}\t{1}\t{2}", row.CustomerId, row.CustomerName, row.TotalPrice);
}
在这种情况下,聚合将列表列表更改为(平面)
列表,因此只需要一个循环即可查看所有数据。指某东西的用途
求和中的空值检查意味着没有空值:
客户名称总计
1 Acme 公司 55.0
2 Beta 公司 9.99
这对于输入数据显然是正确的。
您的解决方案应该只是替换您的类型,添加第四个
作为一个额外的连接,并适应略有不同的类型。
class Customer {
public int CustomerId;
public string Name;
}
class Sku {
public int SkuId;
public decimal DefaultPrice;
}
class Order {
public int OrderId;
public int CustomerId;
public int SkuId;
public decimal? SpecificPrice;
}
class Context {
public List<Customer> Customers;
public List<Sku> Skus;
public List<Order> Orders;
}
static Context GetTestData() {
var customers = new List<Customer> {
new Customer { CustomerId = 1, Name = "Acme Corp" },
new Customer { CustomerId = 2, Name = "Beta Corp" },
new Customer { CustomerId = 3, Name = "Gamma Corp" }
};
var skus = new List<Sku> {
new Sku { SkuId = 1, DefaultPrice = 10.0m },
new Sku { SkuId = 2, DefaultPrice = 20.0m },
new Sku { SkuId = 3, DefaultPrice = 30.0m }
};
var orders = new List<Order> {
new Order { OrderId = 1, CustomerId = 1, SkuId = 1 },
new Order { OrderId = 2, CustomerId = 1, SkuId = 2, SpecificPrice = 15.0m },
new Order { OrderId = 3, CustomerId = 1, SkuId = 3 },
new Order { OrderId = 4, CustomerId = 2, SkuId = 1, SpecificPrice = 9.99m }
};
return new Context {
Customers = customers,
Skus = skus,
Orders = orders
};
}