c# - 如何替换 Queryable 中的列以通用方式不执行 Queryable
问题描述
例如,我有一个 Product 实体、一个 ProductViewModel 和一个 Label 实体。我的两个产品属性对应于标签代码而不是实际值。因此,例如,产品名称是“code1234”,它对应于具有“code1234”作为代码和“牛奶”作为值的标签。标签不作为外键加入。我们使用 AutoMapper 进行投影。
public class Product{
public int ProductId {get; set;}
public string Name {get; set;}
public string Description {get; set;}
}
public class ProductViewModel{
public int ProductId {get; set;}
public string Name {get; set;}
public string Description {get; set;}
}
public class Label{
public int LabelId {get; set;}
public string Code {get; set;}
public string Value {get; set;}
public int LanguageId {get; set}
}
我正在寻找一种替代方法
var currentLanguageId = 1; //As an example
IQueryable<Product> queryFromDb;
var Products = queryFromDb
.ProjectTo<ProductViewModel>().AsEnumerable();
foreach(var Product in Products) {
Product.Name = db.Labels.Where(x => x.Code == Product.Name && x.LanguageId == currentLanguageId).Single().Value;
Product.Description = db.Labels.Where(x => x.Code == Product.Description && x.LanguageId == currentLanguageId).Single().Value;
}
使用将延迟查询的代码,因为过滤和排序是在与产品名称和描述相对应的标签值上完成的,而不是在名称和描述本身上,它们是没有意义的代码。
然后我还需要一种使整个事情通用的方法,因为我们数据库中的许多实体都有标签代码的属性。
到目前为止我所拥有的:
var result = scope.GetRepository<Product>().GetAll() //returns Queryable<Product>
.ProjectTo<ProductViewModel>(_mapper.ConfigurationProvider) //returns Queryable<ProductViewModel>
.WithLabels(_mapper, scope, x => x.Name, x => x.Description) //returns Queryable with columns replaced with a query
.ToDataResult(request); //sorts filters takes skips, etc.
public static IQueryable<T> WithLabels<T>(this IQueryable<T> instance,
IMapper mapper,
IBaseReadContextScope scope,
params Expression<Func<T, string>>[] expressions) where T : class
{
var currentLanguage = 1; //as an example
var labels = scope.GetRepository<Label>().GetAll(x => x.Language == currentLanguageId);
foreach (var expression in expressions)
{
var query = instance
.GroupJoin(
labels,
expression,
label => label.Code,
(x, y) => new { Obj = x, Label = y })
.SelectMany(
xy => xy.Label.DefaultIfEmpty(),
(x, y) => new { Obj = x.Obj, Label = y })
.Select(s => new ObjectWithLabel<T>()
{
Object = s.Obj,
Label = s.Label
});
instance = mapper.ProjectTo<T>(query, new { propertyName = ExpressionUtilities.PropertyName(expression) });
}
return instance;
}
CreateMap<ObjectWithLabel<ProductViewModel>, ProductViewModel>()
.ForMember(x => x.Name, m =>
{
m.Condition(x => propertyName == nameof(ProductViewModel.Name));
m.MapFrom(x => x.Label.Value);
})
.ForMember(x => x.Name, m =>
{
m.Condition(x => propertyName != nameof(ProductViewModel.Name));
m.MapFrom(x => x.Object.Name);
})
.ForMember(x => x.Description, m =>
{
m.Condition(x => propertyName == nameof(ProductViewModel.Description));
m.MapFrom(x => x.Label.Value);
})
.ForMember(x => x.Description, m =>
{
m.Condition(x => propertyName != nameof(ProductViewModel.Description));
m.MapFrom(x => x.Object.Description);
});
这实际上适用于单个属性,但不适用于多个。最重要的是,必须从 ProductViewModel 和 ObjectWithLabel 来回投影并不是很好。我使用 .Condition 而不是简单的三元运算符的原因是 ProjectTo 不支持表达式,我无法让 UseAsDataSource() 工作(这将为我们翻译该表达式)
到目前为止的想法:
使用查询拦截器。使用 AutoMapper,我们将构建一个类似于 INTERCEPTME_ColumnName_Code_Language 的字符串,并用查询替换该字符串(用 LINQ 编写或以 SQL 函数的形式)。这似乎可行,但缺点是不能使用 NOSQL(可能)进行单元测试,需要检查每个传入的查询(除非有办法将查询标记为“可拦截”。
使用类似于我当前的 WithLabels 方法的方法,但构建一个包含多个列的单个连接,然后从 ObjectWithLabel s只投影一次到 ProductViewModel。(不知道在未知数量的列上的通用连接会是什么样子)
通过构建查询并将它们作为参数发送到 AutoMapper,找到一种无需使用中间对象/投影即可直接替换列的方法,快速说明基本思想:
Dictionary<string, IQueryable<string>> dictionary = null;
CreateMap<Product, ProductViewModel>()
.ForMember(x => x.Name, m => m.MapFrom(x =>
dictionary["Name"]))
.ForMember(x => x.Description, m => m.MapFrom(x =>
dictionary["Description"])));
将使用返回与所需代码和语言相对应的 Label.Value 的查询构建字典。
我将不胜感激有关基本问题的任何输入,即在不执行该查询的情况下替换查询中的对象列,以及我提到的任何潜在解决方案。
谢谢
解决方案
我不确定我的问题陈述是否正确,而且我认为我在评论中的 LINQ 方向完全错误。
看来您正在尝试从数据库中加入两个表(并在其中一个上应用 LanguageId 过滤器)。我相信使用 EF.Core 3(我假设您使用最新版本)您不需要定义 FK 来连接表:
var Products = db.Products.Join(
db.Labels.Where(l => l.LanguageId == 1), product => product.Name,
label => label.Code,
(p, l) => new {p, l})
.Join(db.Labels.Where(l => l.LanguageId == 1), p => p.p.Description, l => l.Code,
(pp, l) => new ProductViewModel { Name = pp.l.Value, ProductId = pp.p.ProductId, Description = l.Value});
产生以下 SQL:
SELECT [t].[Value] AS [Name], [p].[ProductId], [t0].[Value] AS [Description]
FROM [Products] AS [p]
INNER JOIN (
SELECT [l].[LabelId], [l].[Code], [l].[LanguageId], [l].[Value]
FROM [Labels] AS [l]
WHERE [l].[LanguageId] = 1
) AS [t] ON [p].[Name] = [t].[Code]
INNER JOIN (
SELECT [l0].[LabelId], [l0].[Code], [l0].[LanguageId], [l0].[Value]
FROM [Labels] AS [l0]
WHERE [l0].[LanguageId] = 1
) AS [t0] ON [p].[Description] = [t0].[Code]
如您所见,这种方式可能更容易直接ProductViewModel
在 select 语句中创建。
推荐阅读
- android - 隐藏控件时亚马逊 Fire TV 遥控器不会暂停 ExoPlayer
- html - CSS 显示网格高度问题。3998px 是网格的最大高度吗?
- rpm - 如何通过自动下载/安装其他依赖项来安装本地 .rpm 文件
- c - 结果的类型是什么?可能吗?
- apache-spark - 使用结构化流一次将所有数据写入镶木地板文件
- ansible - 使用 Ansible-Playbook 接受 Splunk 许可协议
- java - Springboot中使用@ConfigurationProperties和@PropertySource时如何将属性序列化为对象?
- javascript - 如何在javascript中组合多个数组?
- python-3.x - 从 AWS Lambda 上的 S3 读取文件时出现 IncompleteReadError
- python-sphinx - 狮身人面像在 TOC 中不显示本地标题