Parametric LINQ query - linq

This is another take on accessing dynamic objects in F# There I'm using let y = x.Where(fun x -> x.City ="London").Select("new(City,Zip)") to parametrize the query and extract the necessary items. These would correspond to columns in an SQL query, and be represented by a property of the datacontext. This is the part that I would like to pass in as a parameter.
type Northwind = ODataService<"http://services.odata.org/Northwind/Northwind.svc">
let db = Northwind.GetDataContext()
let query2 = query { for customer in db.Customers do
select customer} |> Seq.toArray
let qryfun (x:Northwind.ServiceTypes.Customer) =
query { for x in query2 do
select (x.City,x.CompanyName,x.Country)}
Basically I would like to pass in not only x but also x.*. As I'm accessing one database that is fixed, I can factor out x. However I now have 40 small functions extracting the different columns. Is it possible to factor it out to one function and pass the property as an argument? So sometimes I extractx.City but other times x.Country. I have tried using quotations however cannot splice it properly and maybe that is not the right approach.

Regarding quotation splicing, this works for me:
open System.Linq
type record = { x:int; y:string }
let mkQuery q =
query {
for x in [{x=1;y="test"}].AsQueryable() do
select ((%q) x)
}
mkQuery <# fun r -> r.x, r.y #>
|> Seq.iter (printfn "%A")

Related

SqlDataProvider - Compose a where clause dynamically and execute the command

I have the following requirement:
Query a SQL table with a dynamically generated where clause that should be composed from a list at run time.
[<Literal>]
let connectionString = "Data Source=..."
type sql = SqlDataProvider<
ConnectionString = connectionString,
DatabaseVendor = Common.DatabaseProviderTypes.MSSQLSERVER,
UseOptionTypes = true>
let ctx = sql.GetDataContext()
type Key = {k:string;v:string}
let findCustomersByKeys (keys:Key list) =
query{
for c in ctx.Dbo.Customers do
where (keys.Any(fun k -> c.k = k.k && c.v = k.v))//this is what i wish i could do
select c
}
Is there a way to do it in F# with SqlDataProvider?
Any other technique?
You can use quotations to construct the predicate dynamically, and splice that directly into the query expressions since query expressions are actually compiled into quotations themselves.
The predicate is built by folding over the keys, recursively splicing or-conditions onto an initial condition of false. But because we can't close over c here, we also need to wrap each condition in a function and thread the argument through the predicate chain.
open Microsoft.FSharp.Quotations
type Key = {k:string;v:string}
let findCustomersByKeys (keys:Key list) =
let predicate =
keys
|> List.fold
(fun (acc: Expr<Key -> bool>) k ->
<# fun c -> (%acc) c || c.k = k.k && c.v = k.v #>)
<# fun c -> false #>
query {
for c in ctx.Dbo.Customers do
where ((%predicate) c)
select c
}

Parametrize F# queries based on properties

I'm looking to factor out some common queries over several tables. In a very simple example all tables have a DataDate column, so I have queries like this:
let dtexp1 = query { for x in table1 do maxBy x.Datadate }
let dtexp2 = query { for x in table2 do maxBy x.Datadate }
Based on a previous question I can do the following:
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 #>)
I would be interested if there are any other solutions not using quotations. The reason being is that for more complicated queries the quotations and the splicing become difficult to read.
This for example won't work, obviously, as we don't know that x has property DataDate.
let getMaxDt t = query { for x in t do maxBy x.Datadate }
Unless I can abstract over the type of table1, table2, etc. which are generated by SqlProvider.
The answer very much depends on what kind of queries you need to construct and how static or dynamic they are. Generally speaking:
LINQ is great if they are mostly static and if you can easily list all the templates for all queries you'll need - the main nice thing is that it statically type checks the queries
LINQ is not so great when your query structure is very dynamic, because then you end up composing lots of quotations and the type checking sometimes gets into the way.
If your queries are very dynamic (including selecting the source dynamically), but are not too complex (e.g. no fancy groupings no fancy joins), then it might be easier to write code to generate SQL query from an F# domain model.
For your simple example, the query is really just a table name and aggregation:
type Column = string
type Table = string
type QueryAggregate =
| MaxBy of Column
type Query =
{ Table : Table
Aggregate : QueryAggregate }
You can then create your two queries using:
let q1 = { Table = "table1"; Aggregate = MaxBy "Datadate" }
let q2 = { Table = "table2"; Aggregate = MaxBy "Datadate" }
Translating those queries to SQL is quite simple:
let translateAgg = function
| MaxBy col -> sprintf "MAX(%s)" col
let translateQuery q =
sprintf "SELECT %s FROM %s" (translateAgg q.Aggregate) q.Table
Depending on how rich your queries can be, the translation can get very complicated, but if the structure is fairly simple then this might just be an easier alternative than constructing the query using LINQ. As I said, it's hard to say what will be better without knowing the exact use case!

Questions about custom query operator

I'm working on a custom query provider and would like to support SQL Server's recursive CTEs. I have something that I think will work, but there are two improvements I'd like to make.
First, here's the signature of my query operator (mostly based on GroupJoin).
type QueryBuilder with
[<CustomOperation("recurse", IsLikeGroupJoin = true, JoinConditionWord = "on")>]
member x.Recurse(
anchorSource: QuerySource<'Anchor, 'Q>,
recursiveSource: QuerySource<'Recursive, 'Q>,
anchorKeySelector: ('Anchor -> 'Key),
recursiveKeySelector: ('Recursive -> 'Key),
resultSelector: ('Anchor -> IQueryable<'Recursive> -> 'Result)) =
Unchecked.defaultof<QuerySource<'Result, 'Q>>
And here's a sample query. Note the comment. The first improvement I'd like to make is prior range variables shouldn't be accessible afterwards. Is this possible?
query {
for x in customQueryable do
where (x.Day = 5)
recurse y in customQueryable
on (x.Subtract(TimeSpan.FromDays 1.0) = y) into g
for z in g do
//NOTE: `x` shouldn't be in scope here
select x
}
The second improvement is, I'd prefer to use the query syntax below, but couldn't figure out how to pull it off.
query {
for x in customQueryable do
where (x.Day = 5)
for y in customQueryable do
recurseOn (x.Subtract(TimeSpan.FromDays 1.0) = y) into g
for z in g do
select z
}
I'm also wondering if this is possible.

Translating Linq to a FSharp expression

The DocumentDB has a walk-though and sample project in C# that I am working through in FSharp. One of the 1st tasks is to locate an existing datbase. The C# code is this
var database = client.CreateDatabaseQuery().Where(db => db.Id == "FamilyRegistry").ToArray().FirstOrDefault();
I am attempting to do the same line in FSharp but I am not getting the .Where even though I am referencing the same libraries. Instead I am getting this:
Am I thinking about the problem wrong? Thanks in advance.
Linq isn't specific to C#. Even in C# you need to add a reference to System.Linq.dll and a using System.Linq; statement. The C# project templates already include these statements.
In F# you need to do the same, ensure your project has a reference to System.Linq and add an open System.Linq statement
There are at least two more idiomatic ways:
You can use the Seq module's functions with the pipeline operator achieve the same result as method chaining, eg:
let random = new System.Random()
Seq.initInfinite (fun _ -> random.Next())
|> Seq.filter (fun x -> x % 2 = 0)
|> Seq.take 5
|> Seq.iter (fun elem -> printf "%d " elem)
printfn ""
seq<'T> is a synonym of IEnumerable<T> so if you apply the methods to an IQueryable it will force the query's execution.
You can use Query Expressions, equivalent to LINQ's SQL-like syntax:
let countOfStudents =
query {
for student in db.Student do
select student
count
}
query returns a proper IQueryable<T>
Your specific query could be something like this:
let database =
query {
for db in client.CreateDatabaseQuery()
where db.Id == "FamilyRegistry"
select db
headOrDefault
}

How can I use custom expressions in DevArt LINQ to Entities and also use query comprehension syntax?

I've got a situation where I need to use a custom expression in a LINQ to Entities query (because I want to have custom logic that L2E wouldn't otherwise understand:
var query = db.MyTable.Where(MyPredicateExpression)
But I'd rather use query comprehension syntax:
var query = from x in db.MyTable where [x matches the predicate of MyPredicateExpression]
I know this is possible, because L2E supports it in other places:
var query = from x in db.MyTable where x.Length > 10
How do they make that work?
Edit: I'm using devart's LinqConnect for Oracle, which may behave somewhat differently than Microsoft L2E.
Entity Framework and LINQ to SQL do not support this scenario, because the translation of MyPredicateExpression should be added to expression tree translator.
I recommend you to create a stored function performing the predicate check and add this function to DataContext. You will be able to use a query like the following in this case:
var query = from x in db.MyTable where context.MyPredicateFunction(x.Field) select x;
Update. Here is the updated query that takes into account your comments:
int[] values = new int[] { 1, 2, 3 };
var query = from x in db.MyTable where values.Contains(x.AuditState) select x;
Update 2. You can add a Queryable property to your context that will be obtaining the necessary set of MyTable objects as shown in the following example:
public partial class MyDataContext {
IQueryable<MyTable> GetSpecialTables {
get {
int[] values = new int[] { 1, 2, 3 };
return this.MyTables.Where(x => values.Contains(x.AuditState));
}
}
}
Replace MyDataContext with the actual name of your context.
If I understand the problem correctly, you can either use an extension method OR call a function that returns a bool.

Resources