Должен ли слой пользовательского интерфейса передавать лямбда-выражения в сервисный уровень вместо вызова определенного метода?

Проект ASP.NET, над которым я работаю, имеет 3 слоя; UI, BLL и DAL. Я хотел знать, было ли приемлемо, чтобы пользовательский интерфейс передавал лямбда-выражение BLL, или если пользовательский интерфейс должен передавать параметры, а метод службы должен использовать эти параметры для создания выражения лямбда? Вот пример класса, показывающий оба сенария.

public class JobService 
{
    IRepository<Job> _repository;

    public JobService(IRepository<Job> repository) 
    {
        _repository = repository;
    }

    public Job GetJob(int jobID)
    {
        return _repository.Get(x => x.JobID == jobID).FirstOrDefault();
    }

    public IEnumerable<Job> Get(Expression<Func<Job, bool>> predicate)
    {
        return _repository.Get(predicate);
    }
}

Для вышеуказанного класса допустимо, чтобы пользовательский интерфейс вызывал следующее:

JobService jobService = new JobService(new Repository<Job>());
Job job = jobService.Get(x => x.JobID == 1).FirstOrDefault();

или ему разрешено только позвонить GetJob (int jobID)?

Это простой пример, и мой вопрос заключается в том, должен ли слой пользовательского интерфейса передавать лямбда-выражения на уровень сервиса вместо вызова определенного метода?

Ответ 1

Это решение, основанное на ситуации. Не обязательно ошибаться в таком предикате. Я думаю, что это следует рассматривать как незначительный неприятный запах.

Если переход в лямбда-выражение позволяет вам уменьшить 6 методов до 1, то это может быть хорошим шагом. С другой стороны, если вы просто так легко проходите простым типом, то синтаксис лямбды - это ненужное усложнение.

В приведенном выше примере, не зная контекста, моим предпочтением было бы использовать простой целочисленный параметр. Обычно должен быть базовый метод, который просто получает запись по этому идентификатору. И, возможно, один или два других метода, которые неоднократно используются в вашем приложении. И тогда, возможно, метод общего назначения, который принимает лямбда.

Вы также должны подумать о том, что некоторые предложили бы, должно быть, правило: у вас нет методов с указанными лямбда-предикатами между вашим пользовательским интерфейсом и бизнес-уровнем. (И некоторые полагают, по разуму, что ваши репозитории не должны даже иметь такие методы!) Я не верю, что это должно быть гладко одетые правила, но есть веские причины для этого. Уровни вашего бизнеса и данных между ними должны содержать опасные запросы. Если вы разрешаете пропускать lambdas, для младшего разработчика на уровне пользовательского интерфейса очень просто указать запросы, которые могут действительно наложить вашу базу данных. (Например, они будут делать огромные запросы против неиндексированных полей и/или фильтровать их с помощью метода LINQ-to-objects и не понимать, насколько это неэффективно.)

Как и многие другие хорошие практики, это будет зависеть от масштаба. В моем недавнем большом приложении у меня нет передачи синтаксиса лямбда из слоя пользовательского интерфейса на бизнес-уровень. Мой план заключался в том, чтобы вкладывать значительные средства в бизнес-уровень, чтобы сделать его очень умным. Он имеет все необходимые методы с простыми типами. Фактически, он обычно дает вам то, что вам нужно, через простые свойства объекта домена, без каких-либо параметров. Мой интерфейс гарантирует, что пользовательский интерфейс может вызывать только эффективные запросы, возможно, только незначительные предикаты LINQ to-Objects в слое пользовательского интерфейса. (Это относится даже к страницам "поиска". Мой бизнес-уровень принимает объект-критерий с ограниченными возможностями и обеспечивает эффективный запрос.)

Теперь вы сказали "слои", а не "уровни". Итак, все они в одной сборке? Другим недостатком лямбда является то, что они (в настоящее время) трудно сериализуются. Поэтому вы пожалеете об этом, если вам придется отделять уровни.

Ответ 2

Это всегда спорный вопрос о том, какой "слой", отображающий деревья выражений (и IQueryable s), должен быть ограничен.

Итак, первое очевидное - основное преимущество использования лямбда IQueryable в слое, очевидно, обеспечивает огромную гибкость в запросе без необходимости писать много методов типа GetXXXByYYY. Клиентские слои также могут напрямую управлять соединениями, используя .Include, чтобы указать глубину извлечения графа, и может управлять упорядочением (.OrderBy), группировкой (.GroupBy), ограничениями строк (.Take) в базе данных, которые как правило, будет иметь прирост производительности за то же самое в памяти.

Недостатки:

  • Testability - поскольку интерфейс настолько открыт, для проверки есть произвольно большое количество перестановок.
  • Trust - клиенты вашего уровня имеют свободное владение для выполнения произвольных запросов к вашей базе данных, которые могут иметь проблемы с производительностью (т.е. клиенты могут выполнять запросы, которые пропускают все индексы) и проблемы безопасности (получение данных, к которым у них не должно быть доступа).
  • Сериализация - Выражение Деревья не могут быть непосредственно сериализованы, поэтому вы не можете разоблачить свой BLL через, например. WCF (хотя они могут быть проксимированы)

Если вы разрешите дерево Expression в качестве параметра, тогда я предлагаю вам пойти "полностью" и вернуть IQueryable вместо IEnumerable. Это позволит запрограммировать/агрегировать запросы (например, вы можете изменить запрос позже, прежде чем материализовать его).

Лично мы не разрешаем деревья выражений мимо нашего BLL, поэтому мы не будем распространять это на наши предикаты UI-IMO Expression<>, которые рассматриваются как механизм реализации Evan specification.

Ответ 3

Предполагая, что причина, по которой вы думаете, может быть нецелесообразной, заключается в том, что она может нарушить инкапсуляцию BLL, я собираюсь пойти с , это зависит от.

Делегаты и лямбда-выражения являются базовыми языковыми конструкциями, поэтому простой акт их использования не нарушает какой-либо инкапсуляции как таковой.

Исключением является, конечно, если подпись делегата, переданная в качестве аргумента методу , предоставляет тип, который не должен быть доступен непосредственно из области видимости вызывающего абонента. Позвольте мне привести вам пример:

// Bad
public IEnumerable<Job> Get(Action<SqlCommand> queryCommand);

// Good
public IEnumerable<Job> Get(Func<Job, bool> predicate);

В первом примере сигнатура метода разбивает инкапсуляцию, подвергая деталь реализации вызывающей стороне в виде типа SqlCommand. Он также предоставляет пользователю слишком большую свободу, позволяя ему указывать любой запрос в качестве аргумента, который является простым опасным.

Во втором примере, с другой стороны, подпись подпрограммы метода предоставляет класс модели, который уже является частью общедоступного интерфейса уровня. Он не увеличивает площадь поверхности API и не раскрывает никаких подробностей о том, как выполняется эта операция.

Ответ 4

Мне лично это не нравится. Я предполагаю, что это не так, но он создает серое пространство, где вы должны писать свои запросы. Я думаю, что когда кто-то еще входит в мое приложение, и все выражения находятся в одном хорошем слое, это облегчает навигацию.

Ответ 5

Я бы этого не сделал. Это плохая практика. Это законно, но плохо.

Вы добавляете логику DAL в пользовательский интерфейс, предоставляя DAL в качестве обертки. Ваш пользовательский интерфейс должен передавать только параметры BLL или DAL, а не выполнять логику получения данных.

Ответ 6

Это только мое мнение, но то, что вы пытаетесь написать, похоже на то, как написано выражение LINQ, поэтому, если он достаточно хорош для LINQ, то почему бы вам не быть?

Если вы думаете, что это облегчает вашу работу, я не понимаю, почему вы не должны этого делать. Единственное, о чем я буду опасаться, это убедиться, что это легко поддерживать, т.е. Иметь возможность отслеживать все эти лямбды, которые вы проходите.