Facebook Developer Toolkit LINQ to FQL Addon
Preface
LINQ to FQL is an open source project. Sources, binaries and samples can be found at CodePlex. This library extends the Facebook Developer Toolkit.
Introduction
This library enables developers to use LINQ (.NET Language-Integrated Query) with Facebook instead of string based FQL queries. The main benefits of using this library are:
- Type safety: queries are now typed and their syntax is verified during compilation
- Auto-complete now functions when composing queries in Visual Studio
- Using Facebook / anonymous objects for query results
Example: The following sample code retrieves the names and pictures of all of your friends.
var db = new FacebookDataContext();
var friendIDs = from friend in db.friend_info where friend.uid1 ==
db.uid select friend.uid2;
var friendDetails = from user in db.user where friendIDs.Contains(user.uid)
select new { Name = user.name, Picture = user.pic_small };
Background
While the LINQ infrastructure is extensible, the LINQ to SQL (formerly known as DLINQ) infrastructure is not. Its implementation is internal and supports SQL Server only. In order to natively support FQL, it was required to mimic the LINQ to SQL infrastructure from scratch, as follows:
FqlDataContext
represents a data source that supports FQL, and consists of FqlTable
instances. FqlTable
represents a table in the model, and provides as the target for LINQ queries. FqlDataQuery
represents a LINQ query that is executed over an FqlTable
. FqlProvider
manages the transformation and execution of queries. FqlQueryBuilder
transforms LINQ expressions to FQL strings. FacebookDataContext
derives from FqlDataContext
, and exposes the Facebook Developer Toolkit model as tables.
The flow of a LINQ to FQL query looks like this:
- A table is queried using LINQ (in
FqlTable.CreateQuery
) - The LINQ expression is analyzed and transformed into a valid FQL text (in
FqlQueryBuilder.BuildQuery
) - The FQL text is sent through the
Facebook
API (in API.linq.query
) - The XML result is returned, and deserialized back into proper objects (in
FqlQueryResultEnumerator.GetEnumerator
)
Transforming LINQ to FQL
The LINQ to FQL transformation is done through the FqlQueryBuilder
class. This class takes an expression tree, that is, an instance of System.Linq.Expressions.Expression
, and recursively iterates its sub expressions. During this iteration, each sub expression is identified and converted to FQL text by its expression data and the current state of the builder. For example:
- Query clauses (
SELECT
, FROM
, WHERE
, ORDERBY
, etc...) are identified by MethodCall
expressions to well known LINQ extension methods, for example, the SELECT
clause should match the System.Linq.Queryable.Select
method. - Tables are identified by
MemberExpressions
when their member type implements the IFqlTable
interface. - Columns are identified by
MemberExpressions
when their member has the proper custom attributes. - Sub queries are identified by
MemberExpressions
when their member type implements the IFqlDataQuery
interface.
Integrating with LINQ
Several classes and interfaces are used to integrate with LINQ:
IEnumerable
, IEnumerable<T>
, IEnumerator
, IEnumerator<T>
are used to return physical iteratable results. IQueryable
, IQueryable<T>
are used to create classes that have more control over LINQ queries that are applied on it. IQueryProvider
is used for rewriting and executing queries. - The
System.Linq.Expressions
namespace is used as the basic model of LINQ queries.
The main class that serves as a target for handling LINQ queries in this library is the FqlTable<T>
class which implements both the IQueryable
and IQueryProvider
interfaces.
public class FqlTable<T> : IQueryable, IQueryable<T>, IQueryProvider
{
public IQueryProvider Provider
{
get { return this; }
}
public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
{
return new FqlDataQuery<TResult>(this.context, expression, typeof(T));
}
}
Running the Sample
This sample is available online, simply click the Run button to run the sample, or click on any of the source files to view or edit them.
In order to run the sample locally, please follow these steps:
- Open the SampleQuery.sln file in Visual Studio.NET 2008.
- When you are prompted to create a virtual directory at http://localhost/SimpleQuery for the application, click yes.
You may run the application, note that it will use a predefined generic Facebook application (courtesy of CodeRun) that will redirect Facebook to your local sample. In order to use your own application, continue to these steps:
- Go to the Developer application in your Facebook account.
- Click on "Setup new application".
- Give your application the name
SimpleQuery
. - Set the callback URL to: http://localhost/SimpleQuery/facebookcallback.ashx.
- In the canvas tab, set the
Render
Method to IFrame
. - Click on "Save changes".
- In the next page - copy and paste the API Key, Application Secret and Application ID to the sample project's web.config file.
<appSettings>
<add key="Facebook.Linq.ApplicationID" value="xxxxxxxx" />
<add key="Facebook.Linq.APIKey" value="xxxxxxxxxxxxxxxxxxxxxx" />
<add key="Facebook.Linq.Secret" value="xxxxxxxxxxxxxxxxxxxxxx" />
</appSettings>
Run the application by clicking Run in Visual Studio or here.
Using the Code
The main entry point to use LINQ queries is the FacebookDataContext
class. This class should contain all of Facebook's tables, and each table is queryable using LINQ. To use it - create a new instance of FacebookDataContext
, and use LINQ on any table property on it.
var db = new FacebookDataContext();
var myUser = db.user.Where(t => t.uid==db.uid).FirstOrDefault();
It is also possible to specify the exact connection to Facebook to be used, using the facebook.API
class (located in the Facebook Developer Toolkit assembly).
var api = new API { ApplicationKey = "MyAppKey",
AuthToken = "MyAuthToken", SessionKey = "MySessionKey", Secret = "MySecret" };
var db = new FacebookDataContext(api);
Example 1: Syntax
There are two types of syntax to use LINQ, one is called LINQ Query Syntax and the other is LINQ Method Syntax. The LINQ Query Syntax is a new and native implementation as part of the C# 3.0 language. It is not extensible and therefore it has some limitations. The good news is that both types of syntax can be mixed in the same query and get the same results.
var db = new FacebookDataContext();
var friendsIDs = from friend in db.friend_info where friend.uid1 ==
db.uid select friend.uid2;
var friendsIDs2 = db.friend_info.Where(t => t.uid1 == db.uid).Select(t => t.uid2);
Example 2: Nested Queries
FQL supports the use of only one table in each query, this means that table joins are not supported. On the upside, FQL allows the use of more than one table using nested queries and so does LINQ to FQL. In the future, this library should compensate this drawback by transforming LINQ expressions that use the join
clause to nested or multiple FQL queries.
var db = new FacebookDataContext();
var friendIDs = from friend in db.friend_info where friend.uid1 ==
db.uid select friend.uid2;
var friendDetails = from user in db.user where friendIDs.Contains(user.uid) select user;
var friendDetails2 = from user in db.user where
(from friend in db.friend_info where friend.uid1 ==
db.uid select friend.uid2).Contains(user.uid) select user;
Show Me.
Example 3: Retrieving Specific Properties
Retrieving specific properties using LINQ is done using the select
clause, it is an important part in query optimization because it minimizes the data returned from Facebook. The Select
clause usually requires you to have another object in which you can put your selective data, but no worries, Anonymous Types makes the job very simple. Anonymous types allow you to implicitly define a new class with a certain set of read-only properties. In this example, an anonymous type will be created with two properties - Name
and Picture
, both will be of type String
.
var db = new FacebookDataContext();
var friendIDs = from friend in db.friend_info where friend.uid1 ==
db.uid select friend.uid2;
var friendDetails = from user in db.user where friendIDs.Contains(user.uid)
select new { Name = user.name, Picture = user.pic_small };
Show Me.
Example 4: Paging and Sorting
Sorting is performed using the orderby
clause (using query syntax), or the OrderBy
method (using method syntax). Paging is performed using the Skip
and Take
methods, note that paging does not have a LINQ Query Syntax alternative. Paging is also an important part in query optimization because it limits the number of rows that will be returned from Facebook.
var db = new FacebookDataContext();
var friendIDs = from friend in db.friend_info where friend.uid1 ==
db.uid select friend.uid2;
var friendDetails = (from user in db.user where friendIDs.Contains(user.uid)
orderby user.name select new { Name = user.name,
Picture = user.pic_small }).Skip(4).Take(5);
Show Me.
The facebook.Web Namespace
The facebook.Web
namespace contains some useful classes designed to help with Facebook Integration. However, they are not crucial to the LINQ to FQL infrastructure. For example, when you use the FacebookDataContext
empty constructor, a contextual facebook.API
object will be used. This context is accessible through the FacebookContext.Current
property or the FacebookContext.Get(httpContext)
method, and contains all the relevant Facebook contextual information such as the currently logged in user and the application info. The default application that will be used by the FacebookDataContext
class is defined in your web.config file.
<appSettings>
<add key="Facebook.Linq.ApplicationID" value="xxxxxxxx" />
<add key="Facebook.Linq.APIKey" value="xxxxxxxxxxxxxxxxxxxxxx" />
<add key="Facebook.Linq.Secret" value="xxxxxxxxxxxxxxxxxxxxxx" />
</appSettings>
For a full support of the FacebookContext
, you should also register the FacebookCallback
handler in your web.config file. The FacebookCallback
handler automates the redirection to and callback from Facebook, it also automates the login process.
<system.web>
<httpHandlers>
<add verb="*" path="FacebookCallback.ashx" type="Facebook.Web.FacebookCallback,
Facebook.Linq, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null" validate="false"/>
</httpHandlers>
</system.web>
Show Me.
Using this technique, it is easier to verify that the user is logged in, if he's not, he will be redirected to the Facebook login page.
if (FacebookContext.Current.TryAuthenticating(true))
{
var db = new FacebookDataContext();
}
Show Me.
Custom Facebook Models
While the FqlDataContext
is a generic implementation designed to work with any model, the FacebookDataContext
class derives from it, and implements a specific Facebook model. This gives you the flexibility to create your own Facebook models and map them to FQL using custom attributes. This library actually supports the same custom attributes as the native LINQ to SQL implementation, these are TableAttribute
and ColumnAttribute
. For example, consider this custom, more user friendly Facebook model:
[Table(Name="album")]
public class Album
{
[Column(Name="aid", IsPrimaryKey=true)]
public long ID { get; set; }
[Column(Name="cover_pid")]
public long CoverPictureID {get;set;}
}
public class FacebookFriendlyDataContext : FqlDataContext
{
public FqlTable<Album> Albums
{
get
{
return GetTable<Album>();
}
}
}
Performance Guidelines
The LINQ to FQL is a powerful tool and should be used with care. It is important to remember that your application is not communicating with a local database, but a remote service. Any data that your application receives through this pipe travels through the internet as an XML message. So, it is very important to try and get only the data that your application really needs. The most common ways to do that are:
- Select only the columns you need to process or show.
- Filter your queries using the LINQ to FQL infrastructure and not in memory.
- Use paging - if you're not planning to show or process the entire query result, do not retrieve it all.
Points of Interest
Useful Links
Future Versions
- Complete coverage of the Facebook model
- Data Store API support (Personal Facebook databases)
- Associations support (userid=>user)
- Entity Caching
History
- 9th March, 2009: Initial post
- 10th March, 2009: Updated source code
- 13th May, 2009: Updated article
- 21st May, 2009: Updated article