Параметризация запросов F# на основе свойств

Я хочу выделить некоторые общие запросы по нескольким таблицам. В очень простом примере все таблицы имеют столбец DataDate, поэтому у меня есть такие запросы:

let dtexp1 = query { for x in table1 do maxBy x.Datadate }
let dtexp2 = query { for x in table2 do maxBy x.Datadate }

Основываясь на предыдущем вопросе, я могу сделать следующее:

let mkQuery t q  = 
        query { for rows in t do maxBy ((%q) rows) }

let getMaxDt1 = mkQuery table1 (<@ fun q -> q.Datadate @>)   
let getMaxDt2 = mkQuery table2 (<@ fun q -> q.Datadate @>)

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

Это, например, не будет работать, очевидно, поскольку мы не знаем, что x имеет свойство DataDate.

let getMaxDt t = query { for x in t do maxBy x.Datadate }

Если я не могу абстрагироваться от типа table1, table2 и т. д., которые генерируются SqlProvider.


person s952163    schedule 23.07.2017    source источник


Ответы (1)


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

  • LINQ великолепен, если они в основном статичны, и если вы можете легко перечислить все шаблоны для всех запросов, которые вам понадобятся - главное, приятно то, что он статически проверяет запросы.

  • LINQ не так хорош, когда структура вашего запроса очень динамична, потому что тогда вы в конечном итоге составляете множество цитат, а проверка типов иногда мешает.

Если ваши запросы очень динамичны (включая динамический выбор источника), но не слишком сложны (например, без причудливых группировок и причудливых объединений), то может быть проще написать код для генерации SQL-запроса из модели предметной области F#.

Для вашего простого примера запрос — это просто имя таблицы и агрегация:

type Column = string
type Table = string

type QueryAggregate =
  | MaxBy of Column

type Query = 
  { Table : Table
    Aggregate : QueryAggregate }

Затем вы можете создать два запроса, используя:

let q1 = { Table = "table1"; Aggregate = MaxBy "Datadate" }
let q2 = { Table = "table2"; Aggregate = MaxBy "Datadate" }

Преобразование этих запросов в SQL довольно просто:

let translateAgg = function
  | MaxBy col -> sprintf "MAX(%s)" col

let translateQuery q =
  sprintf "SELECT %s FROM %s" (translateAgg q.Aggregate) q.Table

В зависимости от того, насколько богатыми могут быть ваши запросы, перевод может стать очень сложным, но если структура довольно проста, это может быть просто более простой альтернативой, чем построение запроса с использованием LINQ. Как я уже сказал, трудно сказать, что будет лучше, не зная точного варианта использования!

person Tomas Petricek    schedule 23.07.2017
comment
спасибо. это интересный подход. Подумает. Да, запросы несколько динамичны, и я могу обрабатывать их с помощью цитат. Но было любопытно, есть ли что-нибудь, чтобы сделать его более идиоматичным/проще, с дженериками или интерфейсами, или (вздох) SRTP в крайнем случае. - person s952163; 23.07.2017
comment
Если вы идете по пути «конкатации строк»: убедитесь, что вы не помещаете свое приложение в опасную зону SQL-инъекций. - person CaringDev; 24.07.2017
comment
@CaringDev Хороший вопрос. При использовании SqlCommand для выполнения результирующего запроса лучше всего добавить параметры через SqlParameter (см. docs.microsoft.com/en-us/dotnet/framework/data/adonet/). Таким образом, пользовательский ввод никогда не будет объединен со строкой запроса, и все будет в порядке! - person Tomas Petricek; 24.07.2017