使用业务规则引擎过滤 LINQ 查询
本文讨论了使用其中一种新功能,即使用 Web Rule(基于 XML 的超快速规则引擎,可作为 ASP.NET 或 MVC 组件实现)对 LINQ 查询进行基于规则的过滤。
引言
如果您是一位经验丰富的程序员或 IT 经理,那么您在职业生涯的某个阶段很可能参与过使用业务规则引擎 (BRE) 的决策系统的开发。BRE 在许多大型行业(如银行、保险、医疗保健、零售和物流)中发挥着至关重要的作用。尽管业务规则的诞生可以追溯到 60 年代,但 IT 行业并没有一直停滞不前。相反,供应商通过实施新技术和添加新功能来不断改进其引擎。本文讨论了使用其中一种新功能,即使用Web Rule(基于 XML 的超快速规则引擎,可作为 ASP.NET 或 MVC 组件实现)对 LINQ 查询进行基于规则的过滤。
在深入探讨主要主题之前,让我们快速定义一下 BRE 是什么以及它是如何工作的。简而言之,BRE 的目标是根据业务规则评估一个类的实例(称为源对象,或事实对象,或简称为事实)。然后,它根据规则的输出处理该实例,或将进一步处理委托给规则的一个或多个操作。每个规则都代表组织使用的业务逻辑的一部分。BRE 的主要优点是规则的条件(即业务逻辑)可以在不更改使用该规则的系统的情况下进行更改。
例如,考虑以下业务规则
检查 Product.Type 是否为 Jeans 且 Product.Cost > 19.99
在这个过度简化的业务规则中,Product .NET 类是一个源对象。BRE 使用其 Type 和 Cost 属性作为规则字段。此规则的输出为 True 或 False。这是一个评估类型规则。您还可以定义一个执行类型规则,如下所示
如果 Product.Type 是 Jeans 且 Product.Cost > 19.99,则 ApplyDiscount(10),否则 ReduceShipping(5)
此规则类型使用 `ApplyDiscount` 和 `ReduceShipping` .NET 方法作为规则的操作。根据评估结果,引擎会调用其中一个操作,并传递由规则作者设置的参数值。
基于规则的数据过滤
所以,规则引擎很棒。但是我们如何将它们与数据过滤联系起来呢?仔细看看第一个规则。它提出了一个非常有趣的可能性。想象一下,如果我们将规则改写如下
选择 Product.Type 为 Jeans 且 Product.Cost > 19.99 的记录
从这个角度来看,很容易看出我们的业务规则可以解释为 `select` 语句中的 `where` 子句。实际上,任何评估类型的业务规则都可以用作数据过滤器。这就是 Web Rule 引擎新功能背后的理念,称为基于规则的数据过滤。Web Rule 独特的 UI 允许最终用户创建业务规则,并将其作为数据过滤器应用于几乎任何已建立的 LINQ 提供程序的 `select` 查询,所有这些都在单个 ASP.NET 页面或 MVC 视图上完成。从最终用户的角度来看,它看起来像这样
Web Rule 的此功能使高级搜索表单和即席报告的创建变得轻而易举。用户只需从一系列上下文相关菜单中选择字段、运算符和子句,即可构建高级数据过滤器并立即将其应用于数据源。Web Rule 甚至支持使用括号来优先处理过滤条件
互联网上充斥着各种高级搜索表单,它们使用各种 HTML 控件组合,以尽可能地让最终用户自由搜索数据。随着**基于规则的数据过滤**的引入,Web Rule 为该过程增加了全新的可能性。演示此新功能的实时演示可以在此处找到。
工作原理
本文附带 ASP.NET 和 MVC 演示项目。本文中使用的所有代码示例、数据库架构和实体模型均取自 MVC 演示项目。
要构建我们的高级搜索表单,我们只需要三样东西:一个包含一些测试数据的 SqlServer 数据库,一个带有托管搜索表单的视图的 MVC Web 应用程序,以及一个表示数据库的 LINQ To Entity 数据模型。Web Rule 也支持 LINQ To SQL。让我们从数据库开始
如您所见,它是一个简单的 SqlServer 2008 数据库,包含一小部分测试数据。它还声明了三个 SQL 视图:Orders、Products 和 Customers。以下是 Orders 视图的 SQL
CREATE VIEW [dbo].[Orders] AS SELECT dbo.[Order].OrderID, dbo.[Order].Sub, dbo.[Order].Tax, dbo.[Order].Freight, dbo.[Order].Sub + ISNULL(dbo.[Order].Tax, 0) + dbo.[Order].Freight AS Total, dbo.[Order].Placed AS DatePlaced, dbo.[Order].Paid AS DatePaid, dbo.[Order].Shipped AS DateShipped, dbo.Customer.FirstName AS CustomerFirstName, dbo.Customer.LastName AS CustomerLastName, dbo.Customer.CompanyName AS CustomerCompany, dbo.Customer.Email AS CustomerEmail, dbo.Customer.CellPhone AS CustomerCellPone, dbo.Product.ProductTypeID, dbo.Product.ColorID, dbo.Product.SizeID, dbo.Product.SKU AS ProductSKU, dbo.Product.[Description] AS ProductDescription, dbo.Product.[Weight] AS ProductWeight, dbo.Product.Cost AS ProductCost, dbo.Product.Manufactured AS ProductionDate, dbo.Manufacturer.Name AS ManufacturerName, dbo.Manufacturer.[Description] AS ManufacturerDescription, dbo.ProductType.Name AS ProductTypeName, dbo.Color.Name AS ProductColorName, dbo.Size.Name AS ProductSizeName FROM dbo.[Order] INNER JOIN dbo.OrderProduct ON dbo.[Order].OrderID = dbo.OrderProduct.OrderID INNER JOIN dbo.Product ON dbo.OrderProduct.ProductID = dbo.Product.ProductID INNER JOIN dbo.Manufacturer ON dbo.Product.ManufacturerID = dbo.Manufacturer.ManufacturerID INNER JOIN dbo.Customer ON dbo.[Order].CustomerID = dbo.Customer.CustomerID INNER JOIN dbo.Color ON dbo.Product.ColorID = dbo.Color.ColorID INNER JOIN dbo.ProductType ON dbo.Product.ProductTypeID = dbo.ProductType.ProductTypeID INNER JOIN dbo.Size ON dbo.Product.SizeID = dbo.Size.SizeID;
我们的实体模型将包含表示此 SQL 视图的 Order 类,以及用于其余视图的 Product 和 Customer 类。我们将使用 Order 类作为表单的源对象。使用 SQL 视图的原因有两个
- 在大多数情况下,我们不想只在一个表中搜索数据。SQL 视图是连接来自多个表的绝佳方式。幸运的是,LINQ To Entity 框架可以像处理常规表一样处理视图。
- Web Rule 的下一个主要版本将原生支持 IEnumerable 接口。这种支持将允许创建理解集合类型字段的过滤器,如以下规则:`获取 Order.Customers.FirstName = "John" 的记录` 这反过来将允许在过滤器本身内部通过外键连接多个表。当前版本如果语句使用来自多个表的数据,则需要使用 SQL 视图。
实体模型看起来像这样
请注意,我们只需要 SQL 视图和查找表的类。我们甚至不需要在模型中定义主键,因为 ProductTypeID、ColorID 和 SizeID 等字段的所有查找操作都将使用 Web Rule 的动态菜单数据源功能完成。这在服务器端稍微简化了事情。
现在我们有了模型,我们可以定义托管搜索表单的 MVC 视图、保存搜索结果的视图模型以及提供搜索逻辑的控制器。Web Rule 处理了大部分底层工作,因此您只需要向 HTML 添加少量代码即可在网页上声明过滤器编辑器
@using System.Web.Mvc;
@using CodeEffects.Rule.Common;
@using CodeEffects.Rule.Mvc;
@model CodeEffects.Rule.Demo.Filter.Mvc.Models.SearchModel
@{
Html.CodeEffects().Styles()
.SetTheme(ThemeType.Gray)
.Render();
}
@using(Html.BeginForm("Index", "Test", FormMethod.Post))
{
<div>
@{
Html.CodeEffects().RuleEditor()
.Id("Filter")
.Mode(RuleType.Filter)
.Rule(Model.Rule)
.Render();
}
</div>
<table>
<thead>
<tr>
@foreach(var header in Model.Headers)
{
<th>@header</th>
}
</tr>
</thead>
<tbody>
@foreach (var cells in Model.Data)
{
<tr>
@foreach(var cell in cells)
{
<td>@cell</td>
}
</tr>
}
</tbody>
</table>
}
@{
Html.CodeEffects().Scripts().Render();
}
首先,视图声明 `using` 指令和视图模型的类型。然后它添加了过滤器编辑器使用的 CSS 主题。接下来是表单声明,其中 Test 是控制器的名称。然后是过滤器编辑器的声明,它带有一些可选和必需的设置,后面是显示搜索结果的表格。最后,是写入 Web Rule 正常运行所需的客户端数据的那行代码。您可以在此文档文章中找到有关此实现的更多详细信息。
控制器声明了 Index 操作的两个重载,POST 和 GET。GET Index 操作仅创建具有默认值的视图模型和接受源对象类型的 RuleModel 实例
[HttpGet]
public ActionResult Index()
{
SearchModel s = new SearchModel();
s.Rule = RuleModel.Create(typeof(Order));
return View(s);
}
POST Index 操作包含最有趣的部分——实际搜索
[HttpPost]
public ActionResult Index(FormCollection form, RuleModel Filter)
{
// Create an instance of the view model
SearchModel s = new SearchModel();
// Check if the Search button was clicked
if(form["Search"] != null)
{
// Tell Web Rule which source object (entity) to use
Filter.BindSource(typeof(Order));
// Pass the filter to the view model
s.Rule = Filter;
// Check if the filter is empty or invalid
if(Filter.IsEmpty() || !Filter.IsValid())
{
return View(s);
}
// Get the grid headers (the method is declared elsewhere)
s.Headers = GetHeaders();
// Get the filters XML
string filterXml = Filter.GetRuleXml();
// Create the statement and apply the filter to it
var orders = from o in new WebRuleLinqEntities().Orders.Filter(filterXml)
select o;
// Get distinct results and convert them into displayable data
foreach(Order order in orders.ToList<Order>().Distinct<Order>(new EntityComparer()))
{
// The GetRow method is declared elsewhere
List<string> values = GetRow(order);
// Add the result to the model
s.Data.Add(values);
}
}
else
{
// The Search button was not clicked; just create the model
s.Rule = RuleModel.Create(typeof(Order));
}
return View(s);
}
首先,该操作创建一个新模型。然后它将从客户端收到的类型为 `RuleModel` 的过滤数据传递给它。接下来,它检查过滤器是否为空且有效。最后,该操作通过创建 LINQ 语句,应用Filter 扩展方法并将其传递过滤器的 XML 来执行实际搜索。请注意数据检索末尾的 Distinct 扩展的使用。由于我们使用 SQL 视图提供的数据,结果可能包含重复记录。不幸的是,我们不能简单地“去重”视图本身的 select 语句。Distinct 扩展帮助我们去除任何不需要的记录。为了在语句中方便地进行如此简单的数据过滤,这是一个相对较小的牺牲。
Web Rule 的 Filter 扩展完全支持延迟执行;它不会将整个表加载到服务器内存中再从中进行过滤。最终的 `select` 语句包含由实体提供程序创建的实际 `where` 子句。
Web Rule 的这项新功能将业务规则的使用提升到了一个新的水平,允许开发人员用最少的代码创建复杂的搜索表单和报告工具。
编程愉快!