Как создать централизованный поставщик Expression <Func <T, object >> с разными типами? - PullRequest
2 голосов
/ 22 мая 2019

Я написал логику сортировки для веб-приложения ASP.NET Core 2.2. Моя концепция - определить словарь, содержащий правила порядка сортировки. Словарь содержит string ключей. Каждое правило связано с типом объекта. Также существует возможность получения правила порядка сортировки для определенного типа объекта с помощью клавиши string.

Логика сортировки, которую я написал , не упорядочивает какой-либо коллекции, она только хранит и предоставляет информацию, необходимую для сортировки. Каждое правило порядка сортировки имеет:

  • и Expression<Func<T, object>>;
  • a bool флаг, описывающий способ сортировки (по возрастанию / по убыванию);
  • a 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 Ответ

3 голосов
/ 23 мая 2019

Прежде всего, я бы порекомендовал вам не реализовывать все эти словарные интерфейсы в вашем ISortOrderCollection<T>.Хотя для вас может иметь смысл сделать эту коллекцию фактической коллекцией, на самом деле для этого нет большой выгоды: в конце концов, вы в основном добавляете элементы или извлекаете элементы по их ключу.На самом деле вам не нужны все остальные вещи, которые эти интерфейсы потребуют от вас реализовать.Вместо этого используйте небольшой интерфейс, который охватывает только то, что вам действительно нужно, и используйте композицию для реализации этого.Это также облегчает тестирование.

Итак, вы говорите, что вам нужен один поставщик, который хранит все выражения порядка сортировки для всех ваших типов.Сначала подумайте, как бы вы хотели это использовать, и создайте интерфейс, который предлагает необходимые 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;
    }
}
...