处理检索脚本(Lookup Scripts)

如果我们现在打开 Suppliers 页面,我们可以看到 tenant2 只能查看属于自己的供应商。但是在网格列表的右上角的 country 下拉列表中,所有的国家选项都被列出来了。

Tenant2 All Countries

这个数据是通过动态脚本提供给脚本端。它不会在我们最近处理的 服务列表 中加载该数据。

提供给该下拉列表的检索脚本在 SupplierCountryLookup.cs 中定义:

namespace MultiTenancy.Northwind.Scripts
{
    using Serenity.ComponentModel;
    using Serenity.Data;
    using Serenity.Web;

    [LookupScript("Northwind.SupplierCountry")]
    public class SupplierCountryLookup : 
        RowLookupScript<Entities.SupplierRow>
    {
        public SupplierCountryLookup()
        {
            IdField = TextField = "Country";
        }

        protected override void PrepareQuery(SqlQuery query)
        {
            var fld = Entities.SupplierRow.Fields;
            query.Distinct(true)
                .Select(fld.Country)
                .Where(
                    new Criteria(fld.Country) != "" &
                    new Criteria(fld.Country).IsNotNull());
        }

        protected override void ApplyOrder(SqlQuery query)
        {
        }
    }
}

因为实际上 Northwind 数据库并没有 country 表, 所以我们不能在行(row)类上使用简单的 [LookupScript] 特性。我们从供应商表的现有记录中收集不同的国家名称。

我们应该通过当前的租户来筛选查询。

但是这个检索类继承自基类 RowLookupScript 。让我们创建一个新的基类,为我们稍后再处理其他检索脚本准备。

namespace MultiTenancy.Northwind.Scripts
{
    using Administration;
    using Serenity;
    using Serenity.Data;
    using Serenity.Web;
    using System;

    public abstract class MultiTenantRowLookupScript<TRow> : 
        RowLookupScript<TRow>
        where TRow : Row, IMultiTenantRow, new()
    {
        public MultiTenantRowLookupScript()
        {
            Expiration = TimeSpan.FromDays(-1);
        }

        protected override void PrepareQuery(SqlQuery query)
        {
            base.PrepareQuery(query);
            AddTenantFilter(query);
        }

        protected void AddTenantFilter(SqlQuery query)
        {
            var r = new TRow();
            query.Where(r.TenantIdField ==
                ((UserDefinition)Authorization.UserDefinition).TenantId);
        }

        public override string GetScript()
        {
            return TwoLevelCache.GetLocalStoreOnly("MultiTenantLookup:" + 
                    this.ScriptName + ":" +
                    ((UserDefinition)Authorization.UserDefinition).TenantId, 
                    TimeSpan.FromHours(1),
                new TRow().GetFields().GenerationKey, () =>
                {
                    return base.GetScript();
                });
        }
    }
}

这将是我们多租户检索脚本的基类。

我们首先将过期时间设置设为一个负的时间间隔来禁用缓存。为什么要这样做呢?因为动态脚本使用键值(keys)管理检索脚本的缓存。但我们会有多个基于 TenantId 值的检索脚本的版本。

我们会在动态脚本管理层面关闭缓存并在 GetScript 方法中自己处理缓存。在 GetScript 方法中,我们使用 TwoLevelCache.GetLocalStoreOnly 调用基方法生成我们的检索脚本,并缓存其包含 TenantId 缓存键的结果。

更多关于 TwoLevelCache 类的信息,请查看相关章节。

通过重写 PrepareQuery 方法,我们添加一个使用当前 TenantId 的过滤器,就像我们在列表服务处理中做的一样。

现在是时候使用新基类重写 SupplierCountryLookup

namespace MultiTenancy.Northwind.Scripts
{
    using Serenity.ComponentModel;
    using Serenity.Data;
    using Serenity.Web;

    [LookupScript("Northwind.SupplierCountry")]
    public class SupplierCountryLookup : 
        MultiTenantRowLookupScript<Entities.SupplierRow>
    {
        public SupplierCountryLookup()
        {
            IdField = TextField = "Country";
        }

        protected override void PrepareQuery(SqlQuery query)
        {
            var fld = Entities.SupplierRow.Fields;
            query.Distinct(true)
                .Select(fld.Country)
                .Where(
                    new Criteria(fld.Country) != "" &
                    new Criteria(fld.Country).IsNotNull());

            AddTenantFilter(query);
        }

        protected override void ApplyOrder(SqlQuery query)
        {
        }
    }
}

因为我们没有在这里调用基类的 PrepareQuery 方法(因此它不会由基类调用),所以我们就手工调用 AddTenantFilter 方法。

如果有 Northwind.DynamicScripts.cs 文件,请先删除它。

CustomerCountryLookupCustomerCityLookupOrderShipCityLookupOrderShipCountryLookup 有几个非常相似的检索脚本。我将对它们做类似的修改:把基类改为 MultiTenantRowLookupScript 并在 PrepareQuery 方法中调用 AddTenantFilter

我们现在还有一个问题需要解决:如果你打开 Orders 页面,你将看到 Ship ViaEmployee 过滤下拉列表一直列出其他租户的记录。这是因为我们使用含 [LookupScript] 特性的行定义检索脚本。

让我们首先来修复雇员检索(employee lookup),从 EmployeeRow 中删除 [LookupScript] 特性。

[ConnectionKey("Northwind"), DisplayName("Employees"), InstanceName("Employee"), TwoLevelCached]
[ReadPermission(Northwind.PermissionKeys.General)]
[ModifyPermission(Northwind.PermissionKeys.General)]
public sealed class EmployeeRow : Row, IIdRow, INameRow, IMultiTenantRow
{
    //...

并在 EmployeeRow.cs 旁边的 EmployeeLookup 文件添加一个新的检索:


namespace MultiTenancy.Northwind.Scripts
{
    using Entities;
    using Serenity.ComponentModel;
    using Serenity.Web;

    [LookupScript("Northwind.Employee")]
    public class EmployeeLookup : 
        MultiTenantRowLookupScript<EmployeeRow>
    {
    }
}

由于基类会帮我们处理所有的事情,我们没有重写任何东西。默认情况下,行的 LookupScript 特性使用 RowLookupScript 作为基类定义一个新的自动检索脚本类。

由于没有办法重写每行的基类,我们显式定义检索脚本类,并使用 MultiTenantRowLookupScript 作为基类。

现在如果生成并运行应用程序,你将得到一个错误:

'MultiTenancy.Northwind.Entities.EmployeeRow' type doesn't have a 
[LookupScript] attribute, so it can't be used with a LookupEditor!

这是由于我们的行类前面没有 [LookupScript] 特性,但是在一些像表单的地方,我们需要使用 [LookupEditor("Northwind.Employee")]

打开 OrderRow.cs,你将看到在 EmployeeID 属性上面有这个特性。把它修改为 [LookupEditor("Northwind.Employee")]

我们在 ShipperRow 做类似的事情。删除 LookupScript 特性并定义下面的类:


namespace MultiTenancy.Northwind.Scripts
{
    using Entities;
    using Serenity.ComponentModel;
    using Serenity.Web;

    [LookupScript("Northwind.Shipper")]
    public class ShipperLookup : 
        MultiTenantRowLookupScript<ShipperRow>
    {
    }
}

并且在 OrderRowShipVia 属性上面你将找到另一个相似的 LookupEditor 特性。把它修改为 [LookupEditor("Northwind.Shipper")]。

ProductRow 重复同样的步骤。


namespace MultiTenancy.Northwind.Scripts
{
    using Entities;
    using Serenity.ComponentModel;
    using Serenity.Web;

    [LookupScript("Northwind.Product")]
    public class ProductLookup : MultiTenantRowLookupScript<ProductRow>
    {
    }
}

OrderDetailRowProductID 属性上面你将找到另一个相似的 LookupEditor 特性。把它修改为 [LookupEditor("Northwind.Product")]。

SupplierCategoryRegionTerritory 是下一个需要做类似处理的类。请查看 Serenity 教程的 github 仓库提交日志。

现在 Northwind 支持多租户。

可能有一些被我忽略了的小问题,如果发现任何问题,请在 Serenity 的 Github 仓库中的提交报告。

如果大家对多租户应用程序有足够的兴趣,该功能可能会被集成到 Serenity。

results matching ""

    No results matching ""