Introduction
I had some free time at last, so I implemented a LINQ extension to Flickr
, so you can query for photos by tags, creation date, user id or title.
The implementation was really easy because I didn't have to use the Flickr
APIs directly as I found a good open source library for this called FlickrNet
which I used as my infrastructure. This is a sample of a query:
var f = from i in new PhotoQuery("SampleAppKey")
where i.Tags == "silverkey"
select i;
foreach(var x in f)
Console.WriteLine("Title <{0}>, Url {1}", x.Title, x.Url);
In the last example, I retrieved all the photos tagged with "silverkey
".
Note, you will need to create Flickr
API AppKey
, it is free. Just visit this page and register.
How To Implement This ?
The point is implementing the IQueryable<T>
interface so you can plug your logic in LINQ automatically. Let's look at the PhotoQuery
class:
public class PhotoQuery : IQueryable<FlickrPhoto>
This is the signature of the PhotoQuery
class. It implements the IQueryable
interface, which has two methods, CreateQuery
, Execute
, and because it implements IEnumerable<T>
, you have to implement GetEnumerator()
, and also IEnumerable
and IQueryable
are implemented implicitly in IQueryable<T>
.
public class PhotoQuery : IQueryable<FlickrPhoto>
{
string AppKey;
Expression _expression;
string _tags;
string _title;
string _photoId;
DateTime? _dateAdded;
string _userId;
public PhotoQuery(string AppKey)
{
this.AppKey = AppKey;
}
#region IQueryable<FlickrPhoto> Members
public IQueryable<T> CreateQuery<T>(Expression expression)
{
this._expression = expression;
return (IQueryable<T>)this;
}
public T Execute<T>(Expression expression)
{
throw new NotImplementedException();
}
#endregion
#region IEnumerable<FlickrPhoto> Members
IEnumerator<FlickrPhoto> IEnumerable<FlickrPhoto>.GetEnumerator()
{
return (IEnumerator<FlickrPhoto>)((IEnumerable)this).GetEnumerator();
}
#endregion
#region IQueryable Members
public IQueryable CreateQuery(Expression expression)
{
return CreateQuery<FlickrPhoto>(expression);
}
public Type ElementType
{
get { return typeof(FlickrPhoto); }
}
public object Execute(Expression expression)
{
throw new NotImplementedException();
}
public Expression Expression
{
get { return System.Expressions.Expression.Constant(this); }
}
#endregion
#region IEnumerable
IEnumerator IEnumerable.GetEnumerator()
{
MethodCallExpression methodCall = _expression as MethodCallExpression;
if ((methodCall == null) || (methodCall.Method.Name != "Where"))
throw new NotImplementedException();
foreach (var param in methodCall.Parameters)
{
ParseExpression(param);
}
return QueryPhotoBLOCKED EXPRESSION;
}
#endregion
}
As you can see, we didn't implement all the functions. We just implemented IQueryable<T>.CreateQuery
, IEnumerable.GetEnumerator
. Basically the CreateQuery
method just assigns the passed expression parameter to the MemberExpression
and returns this cased as IQueryable
. The parsing logic is in the IEnumerable.GetEnumerator
, currently because this is just a prototype I implement where the ParseExpression
method contains the parsing logic:
private void ParseExpression(Expression param)
{
switch (param.NodeType)
{
case ExpressionType.AndAlso:
AndAlso(param as BinaryExpression);
break;
case ExpressionType.Lambda:
ParseExpression(((LambdaExpression)param).Body);
break;
case ExpressionType.MethodCall:
MethodCall(param as MethodCallExpression);
break;
default:
break;
}
}
private void MethodCall(MethodCallExpression methodCallExpression)
{
switch (methodCallExpression.Method.Name)
{
case "op_Equality":
if (methodCallExpression.Parameters[0].NodeType ==
ExpressionType.MemberAccess)
{
MemberExpression memberExpr =
methodCallExpression.Parameters[0] as MemberExpression;
if (memberExpr.Expression.Type == typeof(FlickrPhoto))
{
if (methodCallExpression.Parameters[1].NodeType ==
ExpressionType.Constant)
{
ConstantExpression constant =
methodCallExpression.Parameters[1] as ConstantExpression;
switch (memberExpr.Member.Name)
{
case "Title":
_title = constant.Value.ToString();
break;
case "Tags":
_tags = constant.Value.ToString();
break;
case "PhotId":
_photoId = constant.Value.ToString();
break;
case "DateAdded":
_dateAdded = (DateTime?)constant.Value;
break;
case "UserId":
_userId = constant.Value.ToString();
break;
default:
break;
}
}
}
}
break;
default:
break;
}
}
private void AndAlso(BinaryExpression binaryExpression)
{
ParseExpression(binaryExpression.Left);
ParseExpression(binaryExpression.Right);
}
The important part is in the MethodCall
function which parses expressions of type MethodCallExpression
. Currently it handles the MemberAccess
which is assigning values to properties, then it makes a switch on all the properties which can be used as a query condition like the Title, DateAdded, Tags, UserId
, etc., and saves the value passed in the query in member variables declared in the PhotoQuery
class itself. The last part which is executing this query is implemented in this function:
private IEnumerator<FlickrPhoto> QueryPhotoBLOCKED EXPRESSION
{
Flickr flickr = new Flickr(AppKey);
PhotoSearchOptions options = new PhotoSearchOptions();
options.Tags = _tags;
options.Text = _title;
options.UserId = _userId;
if (_dateAdded.HasValue)
options.MinUploadDate = _dateAdded.Value;
Photos photos = flickr.PhotosSearch(options);
var photoList = new List<FlickrPhoto>(photos.PhotoCollection.Count);
foreach (Photo p in photos.PhotoCollection)
{
photoList.Add(new FlickrPhoto(p));
}
return photoList.GetEnumerator();
}
This function is called at the end of the IEnumerable<T>.GetEnumerator()
after parsing the query expression. Here is where I use the FlickrNet
library to search for photos from Flickr
. Using the PhotoSearchOptions
object, I assign the parsed query values stored in the member variables _tags
, _title
, _userId
, etc., and then perform the search. The LINQ to Flickr
code is attached with this post. Have fun.
History
- 23rd October, 2007: Initial post to The Code Project
This article was originally posted to my blog.