【问题标题】:Avoid Query Client Evaluation error on a query with method definition inside entity class在实体类中使用方法定义的查询避免查询客户端评估错误
【发布时间】:2019-07-16 18:08:03
【问题描述】:

.NET Core 2.1 项目中,我在 SQL Server 数据库上使用带有命令模式的 EF Core(使用 MediatR 库)。

我通过使用这些设置来设置项目以避免客户端查询评估:

var phaseOptions = new DbContextOptionsBuilder<PhaseDbContext>().UseSqlServer(configuration.GetConnectionString("PhaseDbContext"),
        sqlServerOptions => sqlServerOptions
            .EnableRetryOnFailure(
                maxRetryCount: 5,
                maxRetryDelay: TimeSpan.FromSeconds(30),
                errorNumbersToAdd: null))
    .ConfigureWarnings(warnings => warnings
        .Throw(RelationalEventId.QueryClientEvaluationWarning)) // Disable Client query evaluation
    .Options;

现在我通过这个查询得到一个QueryClientEvaluationException

var articleCodes = await PhaseContext.PhaseArticles
    .Where(a => !request.ArticleFamily.HasValue || a.GetArticleFamily() == request.ArticleFamily.Value)
    .ToListAsync(cancellationToken);

问题出在a.GetArticleFamily() 方法调用上,因为该方法现在在PhaseArticle 实体类中定义如下:

public class PhaseArticle
{
    public int Id { get; set; }
    public string Code { get; set; }
    public string Description { get; set; }
    public string UnitOfMeasure { get; set; }
    public string Category { get; set; }
    public string Group { get; set; }
    public string Family { get; set; }
    public double UnitCost { get; set; }
    public string AdditionalDescription { get; set; }
    public string ExternalCode { get; set;}
    public string ColorCode { get; set;}
    public string Note { get; set; }

    public ArticleFamily GetArticleFamily()
    {
        switch (Family)
        {
            case "CEL":
                return ArticleFamily.Cell;
            case "STR":
                return ArticleFamily.String;
            case "RAW":
                return ArticleFamily.OtherRawMaterial;
            case "SFP":
                return ArticleFamily.SemiFinishedPanel;
            case "FP":
                return ArticleFamily.FinishedPanel;
            default:
                return ArticleFamily.Other;
        }
    }
}

现在,我想知道是否可以通过某种方式重构(并且可能远离实体类)GetArticleFamily() 方法来保留 QueryClientEvaluationWarning 选项。

2019 年 2 月 26 日更新

@StriplingWarrior 我再次根据您对ValueConverter() 的建议更新了代码,但现在出现此错误:

无法将 Lambda 表达式转换为表达式树。

2019 年 2 月 25 日更新

按照@StriplingWarrior 的建议,我正在尝试编写一个自定义转换器,但我无法编译我的代码。

下面代码的错误是关于第一个 switch 块的 返回值(它是 string,但应该是 enum)和预期的 第二个开关块的输入值(它是string,但应该是enum)。

这是代码:

public static void ApplyPhaseConversions<T>(this ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<PhaseArticle>()
        .Property(e => e.Family)
        .HasConversion(new ValueConverter<ArticleFamily, string> {
            v =>
            {
                switch (v)
                {
                    case ArticleFamily.Cell:
                        return "CEL";
                    case ArticleFamily.String:
                        return "STR";
                    case ArticleFamily.OtherRawMaterial:
                        return "RAW";
                    case ArticleFamily.SemiFinishedPanel:
                        return "SFP";
                    case ArticleFamily.FinishedPanel:
                        return "FP";
                    default:
                        return "";
                }
            },
            v =>
            {
                switch (v)
                {
                    case "CEL":
                        return ArticleFamily.Cell;
                    case "STR":
                        return ArticleFamily.String;
                    case "RAW":
                        return ArticleFamily.OtherRawMaterial;
                    case "SFP":
                        return ArticleFamily.SemiFinishedPanel;
                    case "FP":
                        return ArticleFamily.FinishedPanel;
                    default:
                        return ArticleFamily.Other;
                }
            }});
}

【问题讨论】:

    标签: c# sql-server entity-framework entity-framework-core lazy-evaluation


    【解决方案1】:

    看起来您正在使用 GetArticleFamily() 在数据库值和 C# 枚举之间进行转换。 EF Core 有一个名为 Value Conversions 的内置功能,旨在解决此问题:https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions

    您应该能够定义一个 ValueConverter 以在 ArticleFamily 值之间进行转换,然后将 Family 属性的类型更改为 ArticleFamily,并在查询中使用该属性:

    var articleCodes = await PhaseContext.PhaseArticles
        .Where(a => !request.ArticleFamily.HasValue || a.Family == request.ArticleFamily.Value)
        .ToListAsync(cancellationToken);
    

    PS--我不确定上面的代码会产生什么样的查询,但是这样编写查询可能会更好:

    var articleQuery = PhaseContext.PhaseArticles.AsQueryable();
    if(request.ArticleFamily.HasValue)
    {
        articleQuery = articleQuery.Where(a => a.Family == request.ArticleFamily.Value);
    }
    var articleCodes = await articleQuery.ToListAsync(cancellationToken);
    

    【讨论】:

    • @GoldenAge:看起来request.ArticleFamily 是一个可选过滤器。如果它为空,则返回所有PhaseArticles
    • (我猜request 永远不应该为空。它可能是从一个从客户端发送到 Web API 操作的 JSON 对象初始化的。)
    • @GoldenAge 使用 ValueConverter 是解决此(客户端到数据库映射)问题的正确 EF Core 解决方案。它将自动执行与您手动执行的操作类似的转换和相同的查询。但条件 .Where 部分不是必需的,因为 EF Core 执行常量谓词消除,因此生成的查询将是相同的。
    • 感谢 Ivan 对常量谓词消除的澄清。我学到了一些新东西。 :-)
    • @CheshireCat:看起来您在值转换器中使用 switch 语句的想法是正确的,但是查看 HasConversion method 的方法签名我很确定您需要传入new ValueConverter&lt;ArticleFamily, string&gt;() 而不是直接将这些转换委托作为参数传递。
    【解决方案2】:

    您可以创建一个新变量并传输 request.ArticleFamily.Value 结果,以便它可以返回 ArticleFamily.Cell 或 ArticleFamily.String 然后运行查询

    例如

    if(request != null && !request.ArticleFamily.HasValue)
    // or throw an exception here
     return ...;
    
    ArticleFamily newVariable = (ArticleFamily)Enum.Parse(typeof(ArticleFamily), request.ArticleFamily);
    var articleCodes = await PhaseContext.PhaseArticles
        .Where(a => a.Family == newVariable)
        .ToListAsync(cancellationToken);
    

    方法参数的验证应该在运行查询之前完成。另一件事是,如果请求是null,会发生什么?

    编辑

    还需要验证request 对象。当您在发送到 API 的 JSON 对象的结构中出现拼写错误或错误(定义字段值后忘记添加逗号)时,可能会出现这种情况。在这种情况下,请求对象将具有null 值,因此需要验证这种行为。例如。你可以添加

    if (!ModelState.IsValid)
    {
       return BadRequest(ModelState);
    }
    

    在您的控制器的操作中验证整个请求正文。客户端将收到正确的错误消息。

    【讨论】:

      【解决方案3】:

      正如@StriplingWarrior 所说,最后,解决方案几乎就在那里。

      由于C#编译器的限制,无法为这段代码创建表达式树,解决方法是将转换代码工厂化为方法,然后在HasConversion中调用。

      protected override void OnModelCreating(ModelBuilder modelBuilder)
      {
          modelBuilder
              .Entity<PhaseArticle>()
              .Property(e => e.Family)
              .HasConversion(new ValueConverter<ArticleFamily, string>(
                  v => StringFromArticleFamily(v),
                  v => ArticleFamilyFromString(v));
      }
      
      private static ArticleFamily ArticleFamilyFromString(string family)
      {
          switch (family)
          {
              case "CEL":
                  return ArticleFamily.Cell;
              case "STR":
                  return ArticleFamily.String;
              case "RAW":
                  return ArticleFamily.OtherRawMaterial;
              case "SFP":
                  return ArticleFamily.SemiFinishedPanel;
              case "FP":
                  return ArticleFamily.FinishedPanel;
              default:
                  return ArticleFamily.Other;
          }
      }
      
      private static string StringFromArticleFamily(ArticleFamily articleFamily)
      {
          switch (articleFamily)
          {
              case ArticleFamily.Cell:
                  return "CEL";
              case ArticleFamily.String:
                  return "STR";
              case ArticleFamily.OtherRawMaterial:
                  return "RAW";
              case ArticleFamily.SemiFinishedPanel:
                  return "SFP";
              case ArticleFamily.FinishedPanel:
                  return "FP";
              default:
                  return "";
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多