65.9K
CodeProject 正在变化。 阅读更多。
Home

使用业务规则引擎过滤 LINQ 查询

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2012 年 11 月 19 日

CPOL

7分钟阅读

viewsIcon

36921

downloadIcon

861

本文讨论了使用其中一种新功能,即使用 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 的这项新功能将业务规则的使用提升到了一个新的水平,允许开发人员用最少的代码创建复杂的搜索表单和报告工具。

编程愉快!

© . All rights reserved.