2017-01-06 4 views
3

Я пытаюсь иметь мощный API запросов на стороне клиента, где клиент может указать, в какой таблице будет выполняться запрос и условия запроса. Разумеется, это не замена полномасштабному LINQ или SQL, так что JS-клиент может создавать сложные запросы в таблицах без объединения.Как восстановить таблицу из контекста базы данных через отражение в SqlProvider?

Теперь, большинство из них сделано, как показано ниже. Клиент отправляет мне JSON-сериализованный Predicate (который я deserialize с JSON.NET), и я могу легко составить его в запрос.

module Query = 
    open System.Linq 
    open FSharp.Data.Sql.Common 
    open FSharpComposableQuery 

    type Predicate = 
    | All 
    | Greater of string * System.IComparable 
    | GreaterEq of string * System.IComparable 
    | Lesser of string * System.IComparable 
    | LesserEq of string * System.IComparable 
    | Equal of string * obj 
    | Diff of string * obj 
    | And of Predicate * Predicate 
    | Or of Predicate * Predicate 
    | Not of Predicate 

    let satisfies (table : IQueryable<SqlEntity>) = 
     <@ fun p -> query { 
      for c in table do 
      if p c 
      then yield c 
      } @> 

    let rec eval t = 
     match t with 
      | All     -> <@ fun _ -> true @> 
      | Greater (column, n) -> <@ fun (c : SqlEntity) -> c.GetColumn column > n @> 
      | GreaterEq (column, n) -> <@ fun c -> c.GetColumn column >= n @> 
      | Lesser (column, n) -> <@ fun c -> c.GetColumn column < n @> 
      | LesserEq (column, n) -> <@ fun c -> c.GetColumn column <= n @> 
      | Equal (column, n)  -> <@ fun c -> c.GetColumn column = n @> 
      | Diff (column, n)  -> <@ fun c -> c.GetColumn column <> n @> 
      | And (p1, p2)   -> <@ fun c -> (%eval p1) c && (%eval p2) c @> 
      | Or (p1, p2)   -> <@ fun c -> (%eval p1) c || (%eval p2) c @> 
      | Not p     -> <@ fun c -> not((%eval p) c) @> 

    let predicate = Or (Equal ("myColumn", "myValue"), Equal ("myColumn", "myOtherValue")) 
    let t = query { 
     for c in <someDataProvidedTable> do 
     select (c :> SqlEntity) 
    } 
    let result = query { yield! (%satisfies t) (%eval predicate) } |> Seq.toArray 

module DataLayer = 
    open FSharp.Data.Sql 
    open FSharp.Data.Sql.Common 
    let [<Literal>] ConnectionString = "Data Source=" + __SOURCE_DIRECTORY__ + @"/db.sqlite3;Version=3" 
    type Sql = SqlDataProvider< 
       ConnectionString = ConnectionString, 
       DatabaseVendor = Common.DatabaseProviderTypes.SQLITE, 
       IndividualsAmount = 1000, 
       UseOptionTypes = true > 
    let ctx = Sql.GetDataContext() 
    let db = ctx.Maint 

Все, что осталось параметризовать, это ... таблица. DataLayer.db имеет таблицы, эквивалентные моим таблицам, и мои функции принимают эти объекты таблицы. Но я хочу, чтобы клиент мог отправить мне таблицу в виде строки, и я проверяю DataLayer.db, чтобы извлечь из нее таблицу.

Теперь я знаю, что смогу это решить, имея словарь из строки в объект таблицы. Но это сделает всю конструкцию менее полезной (мы создаем схему и клиент из внешнего DSL). Конечно, я пробовал DataLayer.db.GetType().InvokeMethod, но я получаю следующее исключение, независимо от параметров.

System.MissingMethodException: Method 'FSharp.Data.Sql.Runtime.SqlDataContext.FsmIssue' not found. 
    at System.RuntimeType.InvokeMember (System.String name, System.Reflection.BindingFlags bindingFlags, System.Reflection.Binder binder, System.Object target, System.Object[] providedArgs, System.Reflection.ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, System.String[] namedParams) [0x008a0] in <dca3b561b8ad4f9fb10141d81b39ff45>:0 
    at System.Type.InvokeMember (System.String name, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object target, System.Object[] args) [0x00000] in <dca3b561b8ad4f9fb10141d81b39ff45>:0 
    at <StartupCode$FSI_0062>[email protected]() [0x0001a] in <906a7cff96cb466bbad0babb35abf8de>:0 
    at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&) 
    at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00038] in <dca3b561b8ad4f9fb10141d81b39ff45>:0 
Stopped due to error 

Итак, кто-нибудь может мне помочь?

+1

В чем исключение? – Asti

+1

Я просто редактировал сообщение, чтобы включить исключение. – YuriAlbuquerque

ответ

4

Вы можете использовать определить функцию как GetTable ниже, которая возвращает IQueryable для данного контекста и таблицы FULLNAME:

open FSharp.Data.Sql 
open System.Reflection 
open FSharp.Data.Sql.Common 
open System.Linq 

type DB = SqlDataProvider< 
       ConnectionString = "Data Source=/Path/to/db", 
       DatabaseVendor = Common.DatabaseProviderTypes.SQLITE, 
       IndividualsAmount = 1000, 
       UseOptionTypes = true> 

let getTable ctx (tableFullName: string) = 
    let m = 
    ctx.GetType().GetMethod("FSharp-Data-Sql-Common-ISqlDataContext-CreateEntities", BindingFlags.NonPublic ||| BindingFlags.Public ||| BindingFlags.Instance) 

    m.Invoke(ctx,[| tableFullName |]) :?> IQueryable<SqlEntity> 

[<EntryPoint>] 
let main _ = 
    let ctx = DB.GetDataContext() 

    getTable ctx "Main.Test" |> Seq.iter (fun x-> printfn "%A" x.ColumnValues) 

    0 

отмечают, что tableFullName является DB конкретно, если вы посмотрите на: FSharp.Data.Sql.Schema.Table source code вы заметили она основана на схеме и TableName, где члены записи устанавливаются в отдельных поставщиков услуг:

EDIT: простой способ toubleshot/найти фактическое полное имя таблицы

В случае сомнений/проблем с полным именем таблицы вы можете положиться на DB.dataContext, который использует полное имя для различных объектов базы данных.

UPDATE: вопрос в комментариях по поводу доступа Создать

Аналогично для доступа все записи, создать можно получить с помощью отражения как:

let create ctx (tableFullName: string) (data: (string * obj) seq) = 
    let m = 
    ctx.GetType().GetMethod("FSharp-Data-Sql-Common-ISqlDataContext-CreateEntity", BindingFlags.NonPublic ||| BindingFlags.Public ||| BindingFlags.Instance) 

    let e = m.Invoke(ctx,[| tableFullName |]) :?> SqlEntity 
    e._State <- Created 
    e.SetData data 
    e.DataContext.SubmitChangedEntity e 
    e 


[<EntryPoint>] 
let main _ = 
    let ctx = DB2.GetDataContext() 

    seq { yield "test",232L:>obj } |> create ctx "Main.Test" |> ignore 

    ctx.SubmitUpdates() 
    0 

этот пример использует (строка * OBJ) послед как параметр, как видно из FSharp.Data.Sql.SqlDesignTime в create3. Его можно настроить так, чтобы он использовался в create1 или create2 или любой другой необходимый.

+1

Это довольно приятно. Вы могли бы подумать о том, что PR должен добавить его в документы Sqlprovider? – s952163

+1

@ s952163 Спасибо, похоже, это хорошая идея, я думаю, что мне удастся сделать это к концу этой недели, когда я найду более подробную информацию о специфичных для DB программах в SQLProvider. –

+0

Как оценивать объект таблицы с помощью метода Create, так что я могу создать другой метод, который вставляет в произвольную таблицу, например? – YuriAlbuquerque