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
}
Related
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.
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")
Given a function f:ValueA -> ValueB, how could I map an IGrouping of type IGrouping<Key, ValueA> to IGrouping<Key, ValueB>?
Problem instance:
Say you have this type:
TaggedItem = { Tag:Tag ; Item:Item }
and this query:
query {
for i in taggedItems
groupBy i.Tag into g
select g
}
This would give you a seq of type: IGrouping<Tag, TaggedItem>, but I really want a seq of type: IGrouping<Tag, Item>.
The mapping function is: fun taggedItem -> taggedItem.Item
Solution
The solution is to avoid the mapping of groupings and instead do the transformation while doing the group, using groupValBy, as pointed by the selected answer. The selected answer also shows how to do the mapping from one type of grouping to the other, if you insist.
query {
for i in taggedItems
groupValBy i.Tag i.Item into g
select g
}
How about this?
let mapGrouping f (xs : IGrouping<_,_>) =
let projection = xs |> Seq.map (fun x -> xs.Key, f x)
(projection.GroupBy (fst, snd)).First()
From your code example, I think you want this:
query {
for i in taggedItems do
groupValBy i.Item i.Tag into g
select g
}
At https://msdn.microsoft.com/en-us/library/hh225374.aspx, we learn that groupValBy "selects a value for each element selected so far and groups the elements by the given key."
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
}
I'd like to know it is it possible to create programmatically single LINQ query (for EntityFramework 6) with N .Where() clauses, but with OR between these .Where() clauses.
Imagine IQueryable object defined like:
var query = dbContext.MyTable.Where(mt => mt.TimeStamp >= DateBegin);
What I need else is add N (unknown number) of Where clauses, but with OR condition between them.
Image list of some object:
List<MyObject> myObj =
new List<MyObject>({new MyObject {val = "a" }, new MyObject { val = "b"}}); //In real code there is more than 1 property.
then I'd like to add Where() clauses to query like:
myObj.ForEach(mo =>{
// THIS CREATES -AND- BETWEEN WHERE CLAUSES, BUT I NEED -OR-
query.Where(q=>q.MyValue == mo.val); // In real code there is more than 1 property to compare
});
I was thinking about .Union() beteween queries, but It could generate union between separated queries and it's not optimal I think.
Thanks!
Here's the solution: linq-to-entities-combining-predicates
Or course is necessary to use "latest" answer:
Copy/Paste class ParameterRebinder
Copy/Paste static class Utility
Usage:
Expression<Func<Car, bool>> theCarIsRed = c => c.Color == "Red";
Expression<Func<Car, bool>> theCarIsCheap = c => c.Price < 10.0;
Expression<Func<Car, bool>> theCarIsRedOrCheap = theCarIsRed.Or(theCarIsCheap);
var query = carQuery.Where(theCarIsRedOrCheap);
Because in my solution is N of expressions, I take first expression and then append other expressions in ForEach cycle.
var firstExpression = expressionList.First();
expressionList.Skip(1).ToList().ForEach(ex => { firstExpression = firstExpression.Or(ex); });