What I'm building is not something very unique. In a nutshell I'm creating a small FourSquare like service running in Azure using ASP.NET MVC 4(Web Api) and Entity Framework 5 (with Spatial support). So I'm using SQL Azure and not one of the NoSQL databases like MongoDB or CouchDB. Partly because I'm more fluent/familiar with .NET, partly to see what the development experience is (refactoring, deploying, testing) and partly to see how it will stack up against eg. node.js/MongoDB.
Now let's see some code.
/// <summary>
/// Return the nearest locations relative from the given longitude/latitude
/// </summary>
/// <param name="longitude">Longitude</param>
/// <param name="latitude">Latitude</param>
/// <param name="maxresults">Optional maximum results, default is 2</param>
/// <param name="radius">Optional maximum radius in kilometres, default is 50 km</param>
/// <returns></returns>
public JsonEnvelope Get(string longitude, string latitude, int maxresults = 2, int radius = 50)
{
var pointTxt = string.Format("POINT({0} {1})", longitude, latitude);
var locations = (from s in locationEntityRepository.GetAll
orderby s.Coordinates.Distance(DbGeography.FromText(pointTxt))
where s.Coordinates.Distance(DbGeography.FromText(pointTxt)) / 1000 <= radius
select new Location
{
Id = s.Id,
Name = s.Name,
LocationType = s.LocationType,
Address = s.Address,
Longitude = s.Coordinates.Longitude.Value,
Latitude = s.Coordinates.Latitude.Value,
Distance = (s.Coordinates.Distance(DbGeography.FromText(pointTxt)).Value) / 1000
})
.Take(maxresults).ToList();
// Bad bad bad. But EF/Linq doesn't let us do Includes when using subqueries... Go figure
foreach (var location in locations)
{
location.Checkins = AutoMapper.
Mapper.
Map<List <Checkin>, List<LocationCheckinsJsonViewModel>>
(checkinRepository.GetCheckinsForLocation(location.Id).ToList());
}
// AutoMapper.Mapper.Map<Checkin, CheckinViewModel>(dbCheckin);
var jsonBuilder = new JsonResponseBuilder();
jsonBuilder.AddObject2Response("locations", locations);
return jsonBuilder.JsonEnvelope;
}
A couple of things I think I need to clarify. The locationEntityRepository.GetAll
looks like this.
public IQueryable<LocationEntity> GetAll
{
get { return _context.Locations; }
}
public IQueryable<LocationEntity> GetAllIncluding(params Expression<Func<LocationEntity, object>>[] includeProperties)
{
IQueryable<LocationEntity> query = _context.Locations;
foreach (var includeProperty in includeProperties) {
query = query.Include(includeProperty);
}
// var tmp = query.ToList();
return query;
}
Now the code really smells funky. Ideally I want to be able to use an GetAllIncluding(c => c.Checkins)
instead of the GetAll
method, and to be able to use AutoMapper
to map within the LINQ projection.
I know it's by design that Include + LINQ/EF returns null by design when using subqueries. And using automapper in a LINQ/EF query should be done with the Project().To<>
, but that doesn't work when using .ForMember
.
So the challenge is to make the code more efficient (less SQL and easy to maintain when changes to my JSON structures are needed. Remember, we're trying to beat node.js/MongoDB here ;) Should I bother, or leave it as is?