草庐IT

c# - 多态模型可绑定(bind)表达式树解析器

coder 2024-05-28 原文

我正在尝试找出一种方法来构建我的数据,以便它是模型可绑定(bind)的。我的问题是我必须创建一个可以表示数据中的多个表达式的查询过滤器。

例如:

x => (x.someProperty == true && x.someOtherProperty == false) || x.UserId == 2

x => (x.someProperty && x.anotherProperty) || (x.userId == 3 && x.userIsActive)

我已经创建了这个代表所有表达式的结构,我的问题是我如何才能使它成为模型可绑定(bind)的属性

public enum FilterCondition
{
    Equals,
}

public enum ExpressionCombine
{
    And = 0,
    Or
}

public interface IFilterResolver<T>
{
    Expression<Func<T, bool>> ResolveExpression();
}

public class QueryTreeNode<T> : IFilterResolver<T>
{
    public string PropertyName { get; set; }
    public FilterCondition FilterCondition { get; set; }
    public string Value { get; set; }
    public bool isNegated { get; set; }

    public Expression<Func<T, bool>> ResolveExpression()
    {
        return this.BuildSimpleFilter();
    }
}

//TODO: rename this class
public class QueryTreeBranch<T> : IFilterResolver<T>
{
    public QueryTreeBranch(IFilterResolver<T> left, IFilterResolver<T> right, ExpressionCombine combinor)
    {
        this.Left = left;
        this.Right = right;
        this.Combinor = combinor;
    }

    public IFilterResolver<T> Left { get; set; }
    public IFilterResolver<T> Right { get; set; }
    public ExpressionCombine Combinor { get; set; }

    public Expression<Func<T, bool>> ResolveExpression()
    {
        var leftExpression = Left.ResolveExpression();
        var rightExpression = Right.ResolveExpression();

        return leftExpression.Combine(rightExpression, Combinor);
    }
}

我的左右成员只需要能够解析为 IResolvable,但模型绑定(bind)器仅绑定(bind)到具体类型。我知道我可以编写一个自定义模型 Binder ,但我更愿意只拥有一个有效的结构。

我知道我可以将 json 作为解决方案传递,但作为要求我不能传递

有没有一种方法可以改进这个结构,使其在模型可绑定(bind)的同时仍然可以表示所有简单的表达式?还是有一种简单的方法可以应用此结构,使其与模型 Binder 一起使用?

编辑 为了以防万一有人想知道,我的表达式构建器有一个成员表达式的白名单,它会根据它进行过滤。动态过滤工作我只是在寻找一种自然绑定(bind)此结构的方法,以便我的 Controller 可以接收 QueryTreeBranch 或接收准确表示相同数据的结构。

public class FilterController
{
     [HttpGet]
     [ReadRoute("")]
     public Entity[]  GetList(QueryTreeBranch<Entity> queryRoot)
     {
         //queryRoot no bind :/
     }
}

目前 IFilterResolver 有 2 个实现,需要根据传递的数据动态选择

我正在寻找最接近开箱即用的 WebApi/MVC 框架的解决方案。不需要我将输入调整为另一种结构以生成我的表达式的更可取

最佳答案

乍一看,可以在DTO上拆分过滤逻辑,DTO包含一个独立于实体类型的表达式树,以及一个依赖于类型的生成器Expression<Func<T, bool>> .因此,我们可以避免使 DTO 泛化和多态化,这会导致困难。

可以注意到,您对 IFilterResolver<T> 使用了多态性(2 种实现)因为您想说,过滤树的每个节点都是叶子或分支(也称为 disjoint union )。

型号

好的,如果这个特定的实现导致问题,让我们尝试另一个:

public class QueryTreeNode
{
    public NodeType Type { get; set; }
    public QueryTreeBranch Branch { get; set; }
    public QueryTreeLeaf Leaf { get; set; }
}

public enum NodeType
{
    Branch, Leaf
}

当然,您需要对此类模型进行验证。

所以节点要么是分支要么是叶子(我在这里稍微简化了叶子):

public class QueryTreeBranch
{
    public QueryTreeNode Left { get; set; }
    public QueryTreeNode Right { get; set; }
    public ExpressionCombine Combinor { get; set; }
}

public class QueryTreeLeaf
{
    public string PropertyName { get; set; }
    public string Value { get; set; }
}

public enum ExpressionCombine
{
    And = 0, Or
}

上面的 DTO 不是很方便从代码创建,所以可以使用下面的类来生成这些对象:

public static class QueryTreeHelper
{
    public static QueryTreeNode Leaf(string property, int value)
    {
        return new QueryTreeNode
        {
            Type = NodeType.Leaf,
            Leaf = new QueryTreeLeaf
            {
                PropertyName = property,
                Value = value.ToString()
            }
        };
    }

    public static QueryTreeNode Branch(QueryTreeNode left, QueryTreeNode right)
    {
        return new QueryTreeNode
        {
            Type = NodeType.Branch,
            Branch = new QueryTreeBranch
            {
                Left = left,
                Right = right
            }
        };
    }
}

查看

绑定(bind)这样的模型应该没有问题(ASP.Net MVC 适用于递归模型,请参阅 this question )。例如。以下虚拟 View (将它们放在 \Views\Shared\EditorTemplates 文件夹中)。

对于分支:

@model WebApplication1.Models.QueryTreeBranch

<h4>Branch</h4>
<div style="border-left-style: dotted">
    @{
        <div>@Html.EditorFor(x => x.Left)</div>
        <div>@Html.EditorFor(x => x.Right)</div>
    }
</div>

对于叶子:

@model WebApplication1.Models.QueryTreeLeaf

<div>
    @{
        <div>@Html.LabelFor(x => x.PropertyName)</div>
        <div>@Html.EditorFor(x => x.PropertyName)</div>
        <div>@Html.LabelFor(x => x.Value)</div>
        <div>@Html.EditorFor(x => x.Value)</div>
    }
</div>

对于节点:

@model WebApplication1.Models.QueryTreeNode

<div style="margin-left: 15px">
    @{
        if (Model.Type == WebApplication1.Models.NodeType.Branch)
        {
            <div>@Html.EditorFor(x => x.Branch)</div>
        }
        else
        {
            <div>@Html.EditorFor(x => x.Leaf)</div>
        }
    }
</div>

示例用法:

@using (Html.BeginForm("Post"))
{
    <div>@Html.EditorForModel()</div>
}

Controller

最后,您可以实现一个采用过滤 DTO 和 T 类型的表达式生成器,例如来自字符串:

public class SomeRepository
{
    public TEntity[] GetAllEntities<TEntity>()
    {
        // Somehow select a collection of entities of given type TEntity
    }

    public TEntity[] GetEntities<TEntity>(QueryTreeNode queryRoot)
    {
        return GetAllEntities<TEntity>()
            .Where(BuildExpression<TEntity>(queryRoot));
    }

    Expression<Func<TEntity, bool>> BuildExpression<TEntity>(QueryTreeNode queryRoot)
    {
        // Expression building logic
    }
}

然后你从 Controller 调用它:

using static WebApplication1.Models.QueryTreeHelper;

public class FilterController
{
    [HttpGet]
    [ReadRoute("")]
    public Entity[]  GetList(QueryTreeNode queryRoot, string entityType)
    {
        var type = Assembly.GetExecutingAssembly().GetType(entityType);
        var entities = someRepository.GetType()
            .GetMethod("GetEntities")
            .MakeGenericMethod(type)
            .Invoke(dbContext, queryRoot);
    }

    // A sample tree to test the view
    [HttpGet]
    public ActionResult Sample()
    {
        return View(
            Branch(
                Branch(
                    Leaf("a", 1),
                    Branch(
                        Leaf("d", 4),
                        Leaf("b", 2))),
                Leaf("c", 3)));
    }
}

更新:

正如评论中所讨论的,最好有一个模型类:

public class QueryTreeNode
{
    // Branch data (should be null for leaf)
    public QueryTreeNode LeftBranch { get; set; }
    public QueryTreeNode RightBranch { get; set; }

    // Leaf data (should be null for branch)
    public string PropertyName { get; set; }
    public string Value { get; set; }
}

...和一个编辑器模板:

@model WebApplication1.Models.QueryTreeNode

<div style="margin-left: 15px">
    @{
        if (Model.PropertyName == null)
        {
            <h4>Branch</h4>
            <div style="border-left-style: dotted">
                <div>@Html.EditorFor(x => x.LeftBranch)</div>
                <div>@Html.EditorFor(x => x.RightBranch)</div>
            </div>
        }
        else
        {
            <div>
                <div>@Html.LabelFor(x => x.PropertyName)</div>
                <div>@Html.EditorFor(x => x.PropertyName)</div>
                <div>@Html.LabelFor(x => x.Value)</div>
                <div>@Html.EditorFor(x => x.Value)</div>
            </div>
        }
    }
</div>

同样,这种方式需要大量验证。

关于c# - 多态模型可绑定(bind)表达式树解析器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46057040/

有关c# - 多态模型可绑定(bind)表达式树解析器的更多相关文章

  1. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  5. ruby - 用逗号、双引号和编码解析 csv - 2

    我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

  6. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  7. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  8. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  9. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  10. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

随机推荐