如何创建具有不同类型的Expression <func <t,object =“” >>的集中提供程序?

时间:2019-05-22 20:37:40

标签: c# dictionary generics asp.net-core expression

我为ASP.NET Core 2.2 Web应用程序编写了排序逻辑。我的概念是定义一个包含排序顺序规则的字典。字典具有string键。每个规则都与实体类型相关联。也有可能通过string键检索特定类型实体的排序顺序规则。

我写的排序逻辑不对,它仅存储并提供排序所需的信息。每个排序规则都有:

  • 一个Expression<Func<T, object>>;
  • 描述排序方式(升序/降序)的bool标志;
  • 一个bool标志,指示特定的排序规则是否为默认规则。

我在ISortOrderRule<T>界面中定义了该数据:

public interface ISortOrderRule<T>
{
    Expression<Func<T, object>> Expression { get; }
    bool IsDescending { get; }
    bool IsDefault { get; }
}

SortOrderRule<T>类中具有默认实现:

public class SortOrderRule<T> : ISortOrderRule<T>
{
    public Expression<Func<T, object>> Expression { get; set; }
    public bool IsDefault { get; set; }
    public bool IsDescending { get; set; }
}

例如,表达式可以用作OrderBy() LINQ方法的参数。如果找不到其他IsDefault标志,可以在后备机制中使用它来默认排序顺序。

现在,为了为特定实体创建排序规则,我创建了一个通用接口ISortOrderCollection<T>,可以将排序规则存储在底层词典中

public interface ISortOrderCollection<T> :
    IReadOnlyDictionary<string, ISortOrderRule<T>>,
    IReadOnlyCollection<KeyValuePair<string, ISortOrderRule<T>>>,
    IEnumerable<KeyValuePair<string, ISortOrderRule<T>>>,
    IEnumerable
{

}

只读,因为我希望它对外界不开放,但对来自SortOrderCollectionBase<T>的类开放:

public abstract class SortOrderCollectionBase<T> : ISortOrderCollection<T>
{
    private readonly IDictionary<string, ISortOrderRule<T>> _rules;

    public SortOrderCollectionBase()
    {
        _rules = new Dictionary<string, ISortOrderRule<T>>();
    }

    protected void AddSortOrderRule(string key, ISortOrderRule<T> sortOrderRule)
    {
        // Tweak over the key, add prefix or suffix depending on sorting way
        // So sort order rules for the same property but with opposite
        // sorting way can be distinguished.
        var sortRuleKey = BuildSortOrderRuleKey(key, sortOrderRule);

        _rules.Add(sortRuleKey, sortOrderRule);
    }

    // Implementations of interface members removed for brevity.
}

现在,我可以为Level实体添加一些排序规则:

public class LevelSortOrderCollection : SortOrderCollectionBase<Level>
{
    public LevelSortOrderCollection()
    {
        AddSortOrderRule(nameof(Level.Position), new SortOrderRule<Level>
        {
            Expression = (level) => level.Position,
            IsDefault = true,
        });
        AddSortOrderRule(nameof(Level.Position), new SortOrderRule<Level>
        {
            Expression = (level) => level.Position,
            IsDescending = true,
        });
    }
}

关卡模型:

public class Level
{
    public int Id { get; set; }
    public int Position { get; set; }
}

使用ISortOrderCollection<T>方法在Startup中注册实现ConfigureServices()的类型:

services.AddScoped<ISortOrderCollection<Level>, LevelSortOrderCollection>();
// ...

最后我可以在控制器中使用排序顺序集合:

public class LevelsController : Controller
{
    private readonly IRepository<Level> _levelsRepository;
    private readonly ISortOrderCollection<Level> _levelSortOrder;

    public LevelsController(
        IRepository<Level> levelsRepository,
        ISortOrderCollection<Level> levelSortOrder)
    {
        _levelsRepository = levelsRepository;
        _levelSortOrder = levelSortOrder;
    }

    public async Task<IActionResult> Index(string sort)
    {
        var sortOrder = _levelSortOrder[sort];
        var result = await _levelsRepository.GetPageAsync(sortOrder.Expression);

        return View(result);
    }
}
GetPageAsync()中的

IRepository<Level>接受一个表达式,该表达式以后可用于对OrderBy()进行记录排序。

请注意,我故意剪切了一些代码IMO,但没有提供任何值得放置的内容,例如空检查,验证,控制器/存储库逻辑,选择是调用OrderBy()还是OrderByDescending()并回退到默认排序顺序。如果您需要查看更多内容,请在评论中告诉我。

问题

如何创建可访问多个实体的许多排序顺序集合的集中式排序顺序规则提供程序?与其直接为控制器内的特定实体类型注入排序顺序集合,不如注入一种通用的排序顺序提供程序,如下所示:

private readonly IRepository<Level> _levelsRepository;
private readonly ISortOrderProvider _sortOrderProvider;

public LevelsController(
    IRepository<Level> levelsRepository,
    ISortOrderProvider sortOrderProvider)
{
    _levelsRepository = levelsRepository;
    _sortOrderProvider = sortOrderProvider;
}

然后我会使用类型参数调用某种方法:

var sortOrder = _provider.GetSortOrderRule<Level>("Position");

它将尝试查找通过具有匹配的string键的类型参数传递的实体类型的排序顺序规则。

最后的笔记

我知道所有这一切都非常复杂,因此有很大的机会采用一种完全不同的方式来实现我想做的事情。我对这种答案并不介意。

1 个答案:

答案 0 :(得分:3)

首先,我建议您不要在ISortOrderCollection<T>中实现所有这些字典接口。虽然使该集合成为 actual 集合对您来说可能很有意义,但实际上并没有太多好处:毕竟,您通常会添加项目或通过其键检索项目。您实际上并不需要这些接口需要您实现的所有其他功能。取而代之的是,使用一个仅涵盖您实际需要的小界面,并使用合成来实现此目的。这也使测试更加容易。

因此,您要说的是想要一个提供程序,该提供程序可以为所有类型保留所有排序顺序表达式。首先考虑一下您将如何使用它,并构建一个提供必要API的接口:

public interface ISortOrderProvider
{
    void Add<T>(string name, ISortOrderRule<T> sortOrderRule);

    ISortOrderRule<T> Get<T>(string name);

    ISortOrderRule<T> GetDefault<T>();
}

好的,因此,如果您看一下,您会发现实际上已经有了两个键:排序顺序键和类型。因此,将两者都用作字典中的键。

现在,对于默认规则,有一些策略:您可以拥有一个单独的字典,该字典仅存储每种类型的默认规则;您还可以遍历所有规则,在请求时查找默认值;或者您可以将默认规则存储在通用名称下,从而可以直接查找该默认规则。我会选择后者。

这是一个简单的示例实现:

public class SortOrderProvider : ISortOrderProvider
{
    private const string DefaultKey = "__default";
    private readonly Dictionary<(Type, string), object> _rules = new Dictionary<(System.Type, string), object>();

    public void Add<T>(string name, ISortOrderRule<T> sortOrderRule)
    {
        _rules[(typeof(T), name)] = sortOrderRule;

        if (sortOrderRule.IsDefault)
            _rules[(typeof(T), DefaultKey)] = sortOrderRule;
    }

    public ISortOrderRule<T> Get<T>(string name)
    {
        if (_rules.TryGetValue((typeof(T), name), out var value))
            return (ISortOrderRule<T>)value;
        return GetDefault<T>();
    }

    public ISortOrderRule<T> GetDefault<T>()
    {
        if (_rules.TryGetValue((typeof(T), DefaultKey), out var value))
            return (ISortOrderRule<T>)value;
        else
            return null;
    }
}