如果我像(F#交互式)中那样创建自定义查询构建器
type T1 = T1 of int list
with
member this.Content = let (T1 list) = this in list
type QueryBuilder1() =
inherit Linq.QueryBuilder()
member __.Source (source: T1) = base.Source(source.Content)
let qb1 = new QueryBuilder1()
let list = [1;2;3;4;5]
let t1 = T1 list
let q1 = query { for i in list do select i }
let q2 = query { for i in t1.Content do select i }
let q3 = qb1 { for i in t1 do select i } // OK
一切正常。
但是如果我尝试使用SQL数据库源进行同样的操作
#r @"C:\Root\Project\Ocnarf\packages\SQLProvider.1.1.44\lib\net451\FSharp.Data.SqlProvider.dll"
let [<Literal>] dbVendor = FSharp.Data.Sql.Common.DatabaseProviderTypes.MSSQLSERVER
let [<Literal>] schemaConnString = @"Data Source=..."
type internal Schema = FSharp.Data.Sql.SqlDataProvider<dbVendor, schemaConnString>
type DowntimeEntity = Schema.dataContext.``dbo.DowntimesEntity``
type DowntimeQuery = System.Linq.IQueryable<DowntimeEntity>
type T2 = T2 of DowntimeQuery
with
member this.Query = let (T2 q) = this in q
type QueryBuilder2() =
inherit Linq.QueryBuilder()
member __.Source (source: T2) = base.Source(source.Query)
let db = Schema.GetDataContext()
let tables = db.Dbo
let qry = tables.Downtimes
let t2 = T2 qry
let qb2 = new QueryBuilder2()
let q4 = query { for d in qry do select d }
let q5 = query { for d in t2.Query do select d }
let q6 = qb2 { for d in t2 do select d } // exception
然后我得到以下运行时异常
System.NotSupportedException:这不是有效的查询表达式。 方法 'Microsoft.FSharp.Linq.QuerySource
2[FSharp.Data.Sql.Common.SqlEntity,System.Linq.IQueryable] Source[IQueryable](T2)' was used in a query but is not recognized by the F#-to-LINQ query translator. Check the specification of permitted queries and consider moving some of the operations out of the query expression at Microsoft.FSharp.Linq.QueryModule.TransInner$cont@1180-3(Boolean check, FSharpExpr immutQuery, Unit unitVar) at Microsoft.FSharp.Linq.QueryModule.TransInner(CanEliminate canElim, Boolean check, FSharpExpr immutQuery) at Microsoft.FSharp.Linq.QueryModule.TransInner(CanEliminate canElim, Boolean check, FSharpExpr immutQuery) at Microsoft.FSharp.Linq.QueryModule.TransInnerApplicative(Boolean check, FSharpExpr source, FSharpVar immutConsumingVar, FSharpExpr immutConsumingExpr) at Microsoft.FSharp.Linq.QueryModule.TransInner(CanEliminate canElim, Boolean check, FSharpExpr immutQuery) at Microsoft.FSharp.Linq.QueryModule.TransInnerAndCommit(CanEliminate canElim, Boolean check, FSharpExpr x) at Microsoft.FSharp.Linq.QueryModule.TransInnerWithFinalConsume(CanEliminate canElim, FSharpExpr immutSource) at Microsoft.FSharp.Linq.QueryModule.EvalNonNestedInner(CanEliminate canElim, FSharpExpr queryProducingSequence) at Microsoft.FSharp.Linq.QueryModule.EvalNonNestedOuter(CanEliminate canElim, FSharpExpr tm) at Microsoft.FSharp.Linq.QueryModule.clo@1727-1.Microsoft-FSharp-Linq-ForwardDeclarations-IQueryMethods-Execute[a,b](FSharpExpr
1 q)在。$ FSI_0005.main @()
问题
该如何解决此异常?
答案 0 :(得分:1)
问题是SQL查询从F#报价转换为SQL。这是一个非常敏感的操作,它仅适用于具有标准F#查询生成器生成的确切格式的查询。在您的情况下,构造的查询将调用新的Source
方法,而转换仅识别基类的Source
方法。
没有简单简单的方法可以解决此问题,但是您可以做一些技巧。您可以重新定义在创建的查询中调用的Run
方法(F#引号),然后将引号转换为F#到SQL转换器可以理解的形式。
根据您的情况,您可以将<inst>.Source(<arg>)
替换为Source
(您的<inst>.Source(<arg>.Query)
方法),并调用基类的Source
方法。以下代码可以做到这一点:
open Microsoft.FSharp.Quotations
let rec replace expr =
match expr with
| Patterns.Call(Some inst, mi, [arg]) when mi.Name = "Source" ->
let sourceMethod =
typeof<Linq.QueryBuilder>.GetMethods()
|> Seq.filter (fun mi ->
mi.Name = "Source" &&
mi.GetParameters().[0].ParameterType.Name = "IQueryable`1")
|> Seq.head
let sourceMethod =
sourceMethod.MakeGenericMethod
[| typeof<DowntimeEntity>; typeof<System.Linq.IQueryable> |]
let queryProp = typeof<T2>.GetProperty("Query")
Expr.Call(inst, sourceMethod, [ Expr.PropertyGet(arg, queryProp) ])
| ExprShape.ShapeCombination(o, args) ->
ExprShape.RebuildShapeCombination(o, List.map replace args)
| ExprShape.ShapeLambda(a, b) -> Expr.Lambda(a, replace b)
| ExprShape.ShapeVar(v) -> Expr.Var(v)
type QueryBuilder2() =
inherit Linq.QueryBuilder()
member __.Source (source: T2) = base.Source(source.Query)
member __.Run(e:Expr<Linq.QuerySource<'a, System.Linq.IQueryable>>) =
let e = Expr.Cast<Linq.QuerySource<'a, System.Linq.IQueryable>>(replace e.Raw)
base.Run(e)
我测试了该翻译是否有效,但尚未针对真正的SQL数据库运行此翻译。诀窍在于,在将repalce
函数传递给SQL转换器的F#报价之前,在查询上调用了replace
函数,并且在var g = new dagreD3.graphlib.Graph({compound:true})
.setGraph({}).setDefaultEdgeLabel(function() { return {}; });
g.setNode('step1', {label: 'Step 1', style: "fill:gray" });
g.setNode('step2', {label: 'Step 2', style: "fill:gray" });
g.setNode('step3', {label: 'Step 3', style: "fill:gray" });
g.setEdge('step1', 'step2');
g.setEdge('step2', 'step3');
var render = new dagreD3.render();
var svg = d3.select("svg").append("g");
render(d3.select("svg g"), g);
// example node color change
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
sleep(2000).then(() => {
// ????? another way to do this without calling render()?
g.node('step1').style = "fill:blue";
render(d3.select("svg g"), g);
});
中模式匹配的第一种情况下,它执行了上面概述的替换。