2017-01-06 4 views
3

강력한 클라이언트 측 쿼리 API를 사용하려고합니다. 클라이언트는 쿼리를 실행할 테이블과 조건을 쿼리에 지정할 수 있습니다. 물론 이것이 본격적인 LINQ 또는 SQL 로의 대체가 아니기 때문에 JS 클라이언트가 조인없이 테이블에서 복잡한 쿼리를 만들 수 있습니다.SqlProvider에서 리플렉션을 통해 데이터베이스 컨텍스트에서 테이블을 복구하는 방법은 무엇입니까?

이제 대부분은 다음과 같이 수행됩니다. 클라이언트는 JSON-serialized Predicate (JSON.NET과 deserialize)을 보내면 쉽게 쿼리로 작성할 수 있습니다.

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에는 my 테이블과 동일한 테이블 객체가 있고 my 함수는 이러한 테이블 객체를 허용합니다. 하지만 클라이언트가 테이블을 문자열로 보낼 수있게하려면 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

당신은 지정된 컨텍스트 및 테이블 전체 이름에 IQueryable을 반환하는 아래 getTable이 같은 함수를 정의 사용할 수 있습니다 : FSharp.Data.Sql.Schema.Table source code 당신이주의 사항 : tableFullName 당신이 보면, DB가 특정

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 

참고

편집 :/실제 전체 테이블 이름을 찾을 수 toubleshot하는 간단한 방법

테이블 전체 이름에 대한 의문이나 문제가있는 경우 DB.dataContext에 의존 할 수 있습니다. 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

이것은 아주 좋습니다. Sqlprovider 문서에 PR을 추가한다고 생각하십니까? – s952163

+1

@ s952163 감사합니다. 좋은 생각 인 것 같습니다. 이번 주말에 SQLProvider에서 DB 특정 스키마에 대한 자세한 정보를 찾을 수있을 것입니다. –

+0

Create 메서드를 사용하여 테이블 개체를 평가하면 어떤 테이블에 삽입하는 다른 메서드를 만들 수 있습니까? – YuriAlbuquerque