Questions about custom query operator - linq

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.

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
}

Parametric LINQ query

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")

Can you convert this Linq statement into Lambda without using join statements?

I see this all over the web, however I'm curious if there isn't an easier way to write this in lambda?
var x = from c in db.Client
from p in db.Prospects
from ct in db.Countys
select new ViewModelExcelReport
{
client = c.ClientName,
cntyCounty = ct.County,
sctSection = p.Section
};
I would like to see a lambda expression that does NOT use joins, as though i am almost certain that i have seen one without the joins, but if this isn't possible ofcouse i'd like to see one with, thanks.
Basically multiple from clauses contribute SelectMany calls. So your code is something like:
var x = db.Client
.SelectMany(c => db.Prospects, (c, p) => new { c, p })
.SelectMany(z => db.Countys, (z, ct) => new { z, ct })
.Select(zz => new ViewModelExcelReport
{
client = zz.z.c.ClientName,
cntyCounty = zz.ct.County,
sctSection = zz.z.p.Section
});
Note how this is rather more longwinded than the query expression - the compiler takes care of keeping track of all the range variables via transparent identifiers. Why do you want this in lambda form? What do you see as being the benefit? The query expression will be translated into exactly the same code, so you should use whichever one is clearer - which in this case looks like the query expression, IMO.
As an aside, I'd strongly recommend that you change your property names (in ViewModelExcelReport) to more idiomatic ones if you possibly can.

LINQ, can I write cleaner joins with method syntax?

Using pubs
If I want to join using query syntax I would do this.
from a in db.authors
join ta in db.titleauthors on a.au_id equals ta.au_id
join t in db.titles on ta.title_id equals t.title_id
join s in db.sales on t.title_id = s.title_id
select new { a.au_lname, t.title1, s.qty }
Using method syntax
db.authors
.Join(db.titleauthors,
a => a.au_id,
ta => ta.au_id,
(a, ta) => new {a, ta})
.Join(db.titles,
z => z.ta.title_id,
t => t.title_id,
(z, t) => new { z.a, z.ta, t })
.Join(db.sales,
z => z.t.title_id,
s => s.title_id,
(z, s) => new { z.a, z.ta, z.t, s })
.Select(z => new { z.a.au_lname, z.t.title1, z.s.qty })
I was wondering if there was an elegant way of dealing with this line
(z, X) => new { z.Y1, z.Y2, z.Y3... , X }
maybe something like
(z, X) => z.push(X)
so I don't have to write everything out.
Does something like that exist or is possible?
No. Basically query expressions exist in order to keep this stuff away from you. There's no particularly simple way of mimicking transparent identifiers.
I find that when you hit transparent identifiers, it's almost always cleaner to use query expression syntax. It's definitely worth knowing both, as very simple queries are cleaner using method syntax, but the more complicated the query, the more likely it is to be easier to read using query expressions.
That's assuming it can all be represented with method expressions, of course. Don't forget you can break up queries into separate statements without changing the meaning, so if you do need to call methods which don't have query expression equivalents, I sometimes find it best to separate it out like this:
var foo = from x in y
join a in b on x.Z equals a.Z
select new { a, x };
var bar = foo.Skip(5)
.Take(10)
.ToList();
I find that cleaner than just using brackets to mush the two syntax forms together.

linq select clause syntax

Linq Select method takes Func as input parameter. This means I can have multiple statements in selector for Select, such as
var myresult = sources.Select(s =>
{int x; if (s.val = high) {x=1} else if (s.val = med) {x=2} else {x=3}; return x;
}
)
How can I do this using Linq query syntax
var myresult = from s in sources
select ...
Here, the code in Func part (if ... else if .. else) is artificial. What I really want to know is the syntax of select clause, which may be described as
select select-expression
What is the syntax of
select-expression
I wouldn't want to see your first version in my code. If you need to have what is basically a full function in the lambda, I would rather see the lambda simply invoke a full function! In other words...
theQuery.Select(s => GetX(s)); // just define a GetX function
And that would also be a straightforward translation to query expression syntax
from s in sources
select GetX(s);
You would not be able to put your full code block into the query expression syntax. You could translate your given logic to something usable (yet messy), however I'm quite sure your snippet is just a general example. On the offhand change it isn't, you might try
select s.val == high ? 1 : (s.val == med ? 2 : 3); // totally messy
Instead of special-casing values, with the if/else equivalent of a switch statement, it is more Linq-friendly to group and filter your values:
var myResult = from s in sources
group by s.val into g
select new { Val = g.Key, Sources = g };
var groupHigh = myResult.Where(i => i.Val == high);
var groupMedium = myResult.Where(i => i.Val == medium);
var groupOther = myResult.Except(groupHigh.Concat(groupMedium));
Note that the code I've provided is just a starting place, and isn't the best way to achieve your specific goal. I'd address this in one of these ways:
Change how group by is used (use SomeFunction(s.Val) instead of directly using s.Val)
Change the code around this query to flow better with the natural groupings, so I didn't require the groups to be transformed
This is not possible.
If you really want to, you could create an Func<T> from an anonymous method and invoke it, but that would be horrible.
MSDN indicates select is a contextual keyword of C# 4.0. So I checked the C# Language Specififcation 4.0. Its Select clauses section (7.16.2.5) specifies that
A query expression of the form
from x in e select v
is translated into
( e ) . Select ( x => v )
except when v is the identifier x, the translation is simply
( e )
As the result, the syntax for
select select-expresion
select-expression should be anything that can be used as TResult in Select Method. So the functionality can be done using anonymous Func in Select method may not be able to achieved using select clause.
Conclusion is that you should stick with Method syntax as this is how the code really runs behind the scene.

Resources