gpt4 book ai didi

c# - 如何在不执行 Queryable 的情况下以通用方式替换 Queryable 中的列

转载 作者:行者123 更新时间:2023-12-03 20:26:15 25 4
gpt4 key购买 nike

例如,我有一个 Product 实体、一个 ProductViewModel 和一个 Label 实体。我的两个产品属性对应于标签代码而不是实际值。例如,产品的名称是“code1234”,它对应于具有“code1234”作为代码和“Milk”作为值的标签。标签没有作为外键加入。我们正在使用 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 到产品 View 模型。 (不知道在未知列数上的通用连接会是什么样子)
  • 通过构建查询并将它们作为参数发送到 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 语句中。

    关于c# - 如何在不执行 Queryable 的情况下以通用方式替换 Queryable<T> 中的列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60777749/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com