I have a query
query {
for user in db.Users do
join (for selection in db.CourseSelection ->
user.UserID = selection.UserID)
select (user, selection)
}
|> Seq.iter (fun (user, selection) -> printfn "%d %s %d" user.UserID user.Name selection.GroupID)
Why it says "The value or constructor 'query' is not defined" ?
I have implemented the Linq library
open System
open System.Linq
or is there a better way to do it in builtin way?
Like built-in query expressions?
Here is the documentation and tutorial on F# query expressions:
http://msdn.microsoft.com/en-us/library/vstudio/hh225374(v=vs.110).aspx
It states that "Projects should add references to System.Data, System.Data.Linq, and FSharp.Data.TypeProviders assemblies."
Also in a sample it refer to these namespaces:
open Microsoft.FSharp.Data.TypeProviders
open System.Data.Linq.SqlClient
open System.Linq
open Microsoft.FSharp.Linq
Related
In C# you can group by .Date:
db.History.GroupBy(x => x.Timestamp.Date)
.Select(g => new { key = g.Key, aggregate = g.Count() })
However, the equivalent F# does not work:
db.History.GroupBy(fun x -> x.Timestamp.Date)
.Select(fun g -> { Date = g.Key; Count = g.Count()} )
The relevant record:
type DateCount = {
Date: DateTime
Count: int
}
It throws the following error:
System.InvalidOperationException: The LINQ expression 'DbSet<HistoryEntity>
.GroupBy(
source: h => copyOfStruct => copyOfStruct.Date.Invoke(h.Timestamp),
keySelector: h => h)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
How can I group by date?
So in C#, when you use LINQ-to-SQL queries, you are using the extension methods on IQueryable<T>. If we look at the signature of say, the GroupBy method, you will see that the function signature is actually
IQueryable<TSource>.GroupBy<TSource,TKey>(Expression<Func<TSource,TKey>> keySelector)
What's going on? Expression<> is a special type - when the C# compiler spots an Expression<> type, the compiler builds an AST, and passes the AST object (of type Expression<Func<>>), instead of the usual delegate. The underlying functions are expected to inspect the AST and build whatever query expression is finally needed, like SQL for querying the database.
You can try it yourself:
Expression<Func<int>> getRandom = () => 4; //random, chosen by fair dice roll
You can inspect the properties of getRandom to see the AST.
Since the magic happens in the C# compiler, when you do it in F#, this won't cut it.
To go into more detail, the F# compiler can recognize the Expression<>, but it does so by applying an implicit F# quotation - so you get an F# quotation wrapped method call that translates to the C# expression tree. (Sorry if, that was run on.)
F# has its own query comprehension builder for SQL. It lets you write the computation expressions like that of seq which translate to SQL queries. This works like you'd expect.
query {
for record in db do
select record
}
Group by .Date works when using in a query expression.
query {
for h in db.History do
groupValBy h h.Timestamp.Date into g
select {
Date = g.Key
Count = g.Count()
}
}
Code stolen from here.
If someone could explain why the query expression works but the LINQ version doesn't, I'd appreciate it :)
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")
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
}
Can anyone help me understand why the piece of code below returns no results from the query yet the second sample does (though woe betide me if I try to use criteria on the second one!)
type EbayData =
ODataService<"http://ebayodata.cloudapp.net">
let Ebay = EbayData.GetDataContext()
let Favourites title number = query{
for deal in Ebay.Deals do
where (deal.Title.Contains(title))
take number
}
let Esearch title number = [for item in Favourites title number do
yield item]
The working version:
type Catalog = ODataService< "http://ebayodata.cloudapp.net/" >
let ebay = Catalog.GetDataContext()
let trial =
[ for item in ebay.Deals do
yield item]
I can't seem to output the first to any kind of list, no matter what do with |> etc. The second sample doesn't seem to bring back many resuls to do a text query on. However, my real issue is I can't seem to get anything out of the LINQ in F# version.
The output is used in a WPF application where I use VB to talk to the list. I have populated a non discriminated list of 10 items with it, so that end does work. This is the VB code.
For Each Deal In trial.Where(Function(p) p.Title.Contains(title.Text))
DealResults.Items.Add(buildStackPanel(Deal))
Next
The spacing for the F Sharp on this post doesn't seem to work when I hit Ctrl-K so if anyone can tell me what I'm doing wrong - I guess that's a second question!
I don't know why this is not working for you. I knocked out the following and it seems to work:
open Microsoft.FSharp.Data
type Catalog = TypeProviders.ODataService< "http://ebayodata.cloudapp.net/" >
let ebay = Catalog.GetDataContext()
let trial =
[ for item in ebay.Deals do
yield item]
let trial2 = query {
for deal in ebay.Deals do
where (deal.Title.Contains "a")
take 2
}
let ESearch title number =
query {
for deal in ebay.Deals do
where (deal.Title.Contains title)
take number
}
[<EntryPoint>]
let main argv =
trial |> Seq.take 2 |> Seq.iter (fun d -> printfn "%s" d.Title)
trial2 |> Seq.iter (fun d -> printfn "%s" d.Title)
ESearch "a" 2 |> Seq.iter (fun d -> printfn "%s" d.Title)
0
Maybe you tried searching for stuff that doesn't exist? At the moment there are only 6 deals, so this is not unlikely.
Querying Items
Read about the eBay OData service here: http://ebayodata.cloudapp.net/docs
It has special needs when querying for Items:
(search parameter or $filter with Seller, PrimaryCategoryId or
SecondaryCategoryId is required)
So to query Items, you'll need to provide at least a search phrase. Your where statement doesn't get translated to a search parameter in the final url. To add custom parameters in this Type Provider, you do .AddQueryOption.
let ItemSearch title number =
query {
for item in ebay.Items
.AddQueryOption("search", title) do
take number
}
// use
ItemSearch "wario" 2 |> Seq.iter (fun d -> printfn "%s" d.Title)
I am using following Linq query:
from p in People
where p.Name == "George Lucas"
select p.TitlesActedIn
where TitlesActedIn is a list. People and TitlesActedIn are associted
But I am getting error:
InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.PropertyExpression' to type 'System.Data.Services.Client.ResourceExpression'.
Please suggest solution.
A very simple way to do it:
var query = People
.Expand("TitlesActedIn")
.Where(p => p.Name == "George Lucas")
.First()
.TitlesActedIn.Select(t => t.ShortName);
query.Dump();
Its important to note, that this will crash if the name you pass it does not exist. (The First Operator will throw an exception. You would need to either guarantee that the name exists, or do it in two steps.
If you want to do it in one step it comes down to this:(please note what is coming back)
http://odata.netflix.com/catalog/People()?$filter=Name eq 'George Lucas'&$top=1&$expand=TitlesActedIn
You need the expand or it will quit evaluating after the .First(), because TitlesActedIn will be empty.
It basically translates to select the Person, include (expand) the TitlesActedIn association, then select the name (client side)
The downside of this is that you are pulling back everything (all fields) from the Titles table. So for every title associated to the Person it is returning (Title, Year, Description, ShortName, etc).
If you did this in two queries you could only pull back "ShortName" from the TitlesActedIn association.
UPDATED: See this question and answer to understand the limitations on Select Many in Data Services + Another solution based on $expand (note this requires the server to support expand)
If this is WCF Data Services and TitlesActedIn is a collection of related movies.
Then you can do this in one query only if Person.Name is the primary key.
To illustrate this:
var titles = from p in people
where p.Name == "George Lucas"
from m in p.TitlesActedIn
select m;
Will do what you want but only if Name is the key of the Person entity, otherwise this is unsupported.
If Name is not the key one way to do this (today) is with two queries, something like this:
var key = (from p in people
where p.Name == "George Lucas"
select new {p.Id}).Single().Id;
var titles = from p in people
where p.Id == key
from m in p.TitlesActedIn
select m;
Another option though would be do an expand:
var george = (from p in people.Expand("TitlesActedIn")
where p.Name == "George Lucas"
select p).Single();
var titles = george.TitlesActedIn;
But that relies on the server supporting $expand - which not all servers do...
Note we are currently working on adding any/all support to OData and WCF Data Services, once that is released you would be able to write:
var titles = from t in titles
where t.Actors.Any(a => a.Name == "George Lucas")
select t;
Hope this helps
Note: in the code that gets the key for George Lucas I create an anonymous type because today WCF Data Services doesn't support materializing primitives directly.
Interestingly, the following works:
from p in People
where p.Name == "George Lucas"
select new { p.TitlesActedIn }
as does this:
(from p in People
where p.Name == "George Lucas"
select new { p.TitlesActedIn }).First().TitlesActedIn
The WCF client automatically adds the expansion call in the URI translation:
http://odata.netflix.com/Catalog/People()?$filter=Name eq 'George Lucas'&$top=1&$expand=TitlesActedIn&$select=TitlesActedIn/*
I get a similar error if I use a group by clause along with the lambda expression to fetch data using WCF Data Service. I got to know that certain operations are not supported by WCF data services. Please make sure you are not using unsupported LINQ operations.
http://msdn.microsoft.com/en-us/library/ee622463.aspx