Linq + MoreLinq 如何将一个结果道具聚合到列表中?
Linq + MoreLinq how to aggregate one result prop to list?
查询有问题,请查看。
我的目标是:
- 我需要拿走所有
Products
和一个 Image
。
Products
有 Values
是关于产品的一些附加信息,例如规格等。
Image
和 Values
可以是 null
- 想要 return 所有
Products
和 Image
和 Values
- 对于
Values
我只需要Ids
所以可以得到List<int> of Values
ProductValues
和 ImageObjects
连接 table 的关系 --> Products
可以有很多 ProductValues
也 Products
可以有很多ImageObjects
但可以有一个 Image
DistinctBy
来自 more linq
问题 我不知道如何以正确的方式将 Values
聚合到 return 每个 Product
的 Values
列表]
PS 我也在用更多的 linq
var q1 = (from p in Products
join pv in ProductValues on p.ProductId equals pv.ProductId into ljpv
from pv in ljpv.DefaultIfEmpty()
select new
{
ProductId = p.ProductId,
Description = p.Description,
Name = p.Name,
Price = p.Price,
Quantity = p.Quantity,
Type = p.Type,
Values = (from v in ValueTypes
where v.ValueId == pv.ValueId
select new {
ValueId = v.ValueId
}).ToList(),
ImageObjects = (from io in ImageObjects
where io.ProductId == p.ProductId && io.IsDefault == true
select new
{
Image = io.Image,
IsDefault = io.IsDefault,
ProductId = io.ProductId
})
.ToList()
})
.DistinctBy(x=>x.Name)
.OrderBy(x=>x.Name);
q1.Dump();
回答
Values = (from tmp in ljpv select new { ValueId = tmp.ValueId}),
我知道这不是要回答的地方,但可能会有人对我的代码提出任何建议,或者可能会做得更清楚或更快。
这个查询我想了很久了,但是写信给你的时候,我眼花缭乱了:)
在 @Harald Coppoolse
回答后 - 代码更快!
return context.Product.GroupJoin(
context.ProductValue,
context.ImageObject.Include(x => x.Image),
p => p.ProductId,
pv => pv.ProductId,
io => io.ProductId,
(p, pv, io) => new ProductModel
{
ProductId = p.ProductId,
Name = p.Name,
Price = p.Price,
ProductValue = pv
.Select(npv => new ProductValueModel
{
ProductId = npv.ProductId,
}).ToList(),
ImageObject = io
.Select(nio => new ImageObjectModel
{
Image = nio.Image.DtoToModel(),
IsDefault = nio.IsDefault,
ProductId = nio.ProductId
}).ToList(),
});
所以你有一个 Products
的 table 和一个 ProductValues
的 table 具有一对多关系:每个 Product
有零或多个 ProductValues
并且每个 ProductValue
恰好属于一个 Product
,即外键 ProductId
指向的 Product
。
您想要所有 Products
(的几个属性),每个 Product
及其 ProductValues
。在那之后你 DistinctBy
和 OrderBy
,但这不是你的问题。
只要你想要 "items with their sub-items",例如 "Schools with their Students"、"Customers with their Orders"、"Orders with their Order lines",请考虑使用 Enumerable.GroupJoin
GroupJoin 实际上是 Left Outer Join,然后是 GroupBy。
var productsWithTheirProductValues = products.GroupJoin( // GroupJoin Products
productValues, // with ProductValues
product => product.ProductId, // from every Product take the ProductId
productValue => productValue.ProductId, // from every ProductValue take the foreign key
// ResultSelector: take the product with its zero or more matching ProductValues
// to make a new object:
(product, productValuesOfThisProduct) => new
{
// Select only the product properties you plan to use:
Id = product.Id,
Name = product.Name,
Price = product.Price,
...
ProductValues = productValuesOfThisProduct
// only if you don't want all ProductValues of this product:
.Where(productValue => ...)
.Select(productValue => new
{
// again select only the properties you plan to use
Id = productValue.Id,
...
// not needed: the foreign key, you already know the value
// ProductId = productValue.ProductId,
})
.ToList(),
});
在您的情况下,您不想 GroupJoin 两个序列,而是三个序列。你需要做一个额外的 GroupJoin:
var result = products.GroupJoin(productValues,
product => product.ProductId,
productValue => productValue.ProductId,
// ResultSelector: remember the product and all its productValues
(product, productValuesOfThisProduct) => new
{
Product = product,
ProductValues = productValuesOfThisProduct,
})
// now do the 2nd join:
.GroupJoin(imageObjects,
firstJoinResult => firstJoinResult.Product.ProductId,
imageObject => imageObject.ProductId,
// result selector:
(firstJoinResult, imageObjectsOfThisProduct) => new
{
Product = firstJoinResult.Product,
ProductValues = firstJoinResult.ProductValues,
ImageObjects = imageObjectsOfThisProduct,
})
// take each element of this group join result and select the items that you want
.Select(joinResult => new
{
ProductId = joinResult.Product.ProductId,
Price = joinResult.Product.Price,
...
ProductValues = joinResult.ProductValues.Select(productValue => new
{
...
})
.ToList(),
ImageObjects = joinResult.ImageObjects.Select(imageObject => new
{
...
})
.ToList(),
});
这看起来很糟糕。因此,如果您必须更频繁地使用三个 table 进行 GroupJoin,请考虑为三个 table 创建一个 GroupJoin:
static IEnumerable<TResult> GroupJoin<T1, T2, T3, TKey, TResult>(
this IEnumerable<T1> source1, IEnumerable<T2> source2, IEnumerable<T3> source3,
Func<T1, TKey> key1Selector,
Func<T2, TKey> key2Selector,
Func<T3, TKey> key3Selector,
Func<T1, IEnumerable<T2>, IEnumerable<T3>, TResult> resultSelector)
{
// put all source2 and source3 elements in lookuptables, using the keyselector
var lookup2 = source2.ToLookup(item => key2Selector(item));
var lookup3 = source3.ToLookup(item => key3Selector(item));
// now for every source1 item, get all source2 and source3 items with the same key
// and create the result:
foreach (var item1 in source1)
{
TKey key1 = key1Selector(item1);
IEnumerable<T2> items2 = lookup2[key1];
IEnumerable<T3> items3 = lookup3[key1];
// returns empty collection if no items with this key
TResult result = resultSelector(item1, items2, items3);
yield return result;
}
}
用法:
var result = products.GroupJoin(productValues, imageObjects,
product => product.ProductId,
productValue => productValue.ProductId,
imageObject => imageObject.ProductId,
(product, productValues, imageObjects) => new
{
ProductId = product.ProductId,
...
ProductValues = productValues.Select(productValue => new
{
...
}),
ImageObjects = imageObjects.Select(imageObject => new
{
...
}),
});
查询有问题,请查看。 我的目标是:
- 我需要拿走所有
Products
和一个Image
。 Products
有Values
是关于产品的一些附加信息,例如规格等。Image
和Values
可以是null
- 想要 return 所有
Products
和Image
和Values
- 对于
Values
我只需要Ids
所以可以得到List<int> of Values
ProductValues
和ImageObjects
连接 table 的关系 -->Products
可以有很多ProductValues
也Products
可以有很多ImageObjects
但可以有一个Image
DistinctBy
来自more linq
问题 我不知道如何以正确的方式将 Values
聚合到 return 每个 Product
的 Values
列表]
PS 我也在用更多的 linq
var q1 = (from p in Products
join pv in ProductValues on p.ProductId equals pv.ProductId into ljpv
from pv in ljpv.DefaultIfEmpty()
select new
{
ProductId = p.ProductId,
Description = p.Description,
Name = p.Name,
Price = p.Price,
Quantity = p.Quantity,
Type = p.Type,
Values = (from v in ValueTypes
where v.ValueId == pv.ValueId
select new {
ValueId = v.ValueId
}).ToList(),
ImageObjects = (from io in ImageObjects
where io.ProductId == p.ProductId && io.IsDefault == true
select new
{
Image = io.Image,
IsDefault = io.IsDefault,
ProductId = io.ProductId
})
.ToList()
})
.DistinctBy(x=>x.Name)
.OrderBy(x=>x.Name);
q1.Dump();
回答
Values = (from tmp in ljpv select new { ValueId = tmp.ValueId}),
我知道这不是要回答的地方,但可能会有人对我的代码提出任何建议,或者可能会做得更清楚或更快。 这个查询我想了很久了,但是写信给你的时候,我眼花缭乱了:)
在 @Harald Coppoolse
回答后 - 代码更快!
return context.Product.GroupJoin(
context.ProductValue,
context.ImageObject.Include(x => x.Image),
p => p.ProductId,
pv => pv.ProductId,
io => io.ProductId,
(p, pv, io) => new ProductModel
{
ProductId = p.ProductId,
Name = p.Name,
Price = p.Price,
ProductValue = pv
.Select(npv => new ProductValueModel
{
ProductId = npv.ProductId,
}).ToList(),
ImageObject = io
.Select(nio => new ImageObjectModel
{
Image = nio.Image.DtoToModel(),
IsDefault = nio.IsDefault,
ProductId = nio.ProductId
}).ToList(),
});
所以你有一个 Products
的 table 和一个 ProductValues
的 table 具有一对多关系:每个 Product
有零或多个 ProductValues
并且每个 ProductValue
恰好属于一个 Product
,即外键 ProductId
指向的 Product
。
您想要所有 Products
(的几个属性),每个 Product
及其 ProductValues
。在那之后你 DistinctBy
和 OrderBy
,但这不是你的问题。
只要你想要 "items with their sub-items",例如 "Schools with their Students"、"Customers with their Orders"、"Orders with their Order lines",请考虑使用 Enumerable.GroupJoin
GroupJoin 实际上是 Left Outer Join,然后是 GroupBy。
var productsWithTheirProductValues = products.GroupJoin( // GroupJoin Products
productValues, // with ProductValues
product => product.ProductId, // from every Product take the ProductId
productValue => productValue.ProductId, // from every ProductValue take the foreign key
// ResultSelector: take the product with its zero or more matching ProductValues
// to make a new object:
(product, productValuesOfThisProduct) => new
{
// Select only the product properties you plan to use:
Id = product.Id,
Name = product.Name,
Price = product.Price,
...
ProductValues = productValuesOfThisProduct
// only if you don't want all ProductValues of this product:
.Where(productValue => ...)
.Select(productValue => new
{
// again select only the properties you plan to use
Id = productValue.Id,
...
// not needed: the foreign key, you already know the value
// ProductId = productValue.ProductId,
})
.ToList(),
});
在您的情况下,您不想 GroupJoin 两个序列,而是三个序列。你需要做一个额外的 GroupJoin:
var result = products.GroupJoin(productValues,
product => product.ProductId,
productValue => productValue.ProductId,
// ResultSelector: remember the product and all its productValues
(product, productValuesOfThisProduct) => new
{
Product = product,
ProductValues = productValuesOfThisProduct,
})
// now do the 2nd join:
.GroupJoin(imageObjects,
firstJoinResult => firstJoinResult.Product.ProductId,
imageObject => imageObject.ProductId,
// result selector:
(firstJoinResult, imageObjectsOfThisProduct) => new
{
Product = firstJoinResult.Product,
ProductValues = firstJoinResult.ProductValues,
ImageObjects = imageObjectsOfThisProduct,
})
// take each element of this group join result and select the items that you want
.Select(joinResult => new
{
ProductId = joinResult.Product.ProductId,
Price = joinResult.Product.Price,
...
ProductValues = joinResult.ProductValues.Select(productValue => new
{
...
})
.ToList(),
ImageObjects = joinResult.ImageObjects.Select(imageObject => new
{
...
})
.ToList(),
});
这看起来很糟糕。因此,如果您必须更频繁地使用三个 table 进行 GroupJoin,请考虑为三个 table 创建一个 GroupJoin:
static IEnumerable<TResult> GroupJoin<T1, T2, T3, TKey, TResult>(
this IEnumerable<T1> source1, IEnumerable<T2> source2, IEnumerable<T3> source3,
Func<T1, TKey> key1Selector,
Func<T2, TKey> key2Selector,
Func<T3, TKey> key3Selector,
Func<T1, IEnumerable<T2>, IEnumerable<T3>, TResult> resultSelector)
{
// put all source2 and source3 elements in lookuptables, using the keyselector
var lookup2 = source2.ToLookup(item => key2Selector(item));
var lookup3 = source3.ToLookup(item => key3Selector(item));
// now for every source1 item, get all source2 and source3 items with the same key
// and create the result:
foreach (var item1 in source1)
{
TKey key1 = key1Selector(item1);
IEnumerable<T2> items2 = lookup2[key1];
IEnumerable<T3> items3 = lookup3[key1];
// returns empty collection if no items with this key
TResult result = resultSelector(item1, items2, items3);
yield return result;
}
}
用法:
var result = products.GroupJoin(productValues, imageObjects,
product => product.ProductId,
productValue => productValue.ProductId,
imageObject => imageObject.ProductId,
(product, productValues, imageObjects) => new
{
ProductId = product.ProductId,
...
ProductValues = productValues.Select(productValue => new
{
...
}),
ImageObjects = imageObjects.Select(imageObject => new
{
...
}),
});