Extending NerdDinner
Extending NerdDinner
You've likely seen code like this before. At least it's not concatenating the SQL manually! It could also be a sproc. The pattern remains.
public IEnumerable<Dinner> FindByLocation(float latitude, float longitude)
{
using (var connection = new SqlConnection(this.connectionString))
http://www.hanselman.com/blog/ExtendingNerdDinnerExploringDifferentDatabaseOptions.aspx
1/7
10/23/2014
var commandText =
@"
select d.DinnerID, d.Title, d.EventDate, d.[Description], d.HostedBy,
d.ContactPhone, d.[Address], d.Country, d.Latitude, d.Longitude
from Dinners d
inner join dbo.NearestDinners(@Latitude,@Longitude) nd on
d.DinnerID = nd.DinnerID
where @CurrentDate <= d.EventDate
order by d.DinnerID
select r.RsvpID, r.DinnerID, r.AttendeeName from RSVP r
inner join Dinners d on
d.DinnerID = r.DinnerID
inner join dbo.NearestDinners(@Latitude,@Longitude) nd on
d.DinnerID = nd.DinnerID
where @CurrentDate <= d.EventDate
order by r.DinnerID, r.RsvpID
";
var command = new SqlCommand(commandText, connection);
var parameters = new[]{
new SqlParameter{ParameterName = "Latitude", DbType = DbType.Double, Value = latitude},
new SqlParameter{ParameterName = "Longitude", DbType = DbType.Double, Value = longitude},
new SqlParameter{ParameterName = "CurrentDate", DbType = DbType.Date, Value = DateTime.Now}};
command.Parameters.AddRange(parameters);
connection.Open();
return GetDinners(command);
Here's a snippet of the private method, GetDinners, that does the tearing apart of the DataReader and turning it into object(s):
private List<Dinner> GetDinners(SqlCommand command)
{
var returnDinners = new List<Dinner>();
using (var reader = command.ExecuteReader())
{
//Project first result set into a collection of Dinner Objects
//...
while (reader.Read())
{
var dinner = new Dinner()
{
DinnerID = (int)reader["DinnerID"],
Title = (string)reader["Title"],
Description = (string)reader["Description"],
Address = (string)reader["Address"],
ContactPhone = (string)reader["ContactPhone"],
Country = (string)reader["Country"],
HostedBy = (string)reader["HostedBy"],
EventDate = (DateTime)reader["EventDate"],
Latitude = (double)reader["Latitude"],
Longitude = (double)reader["Longitude"]
};
returnDinners.Add(dinner);
}
Pretty classic stuff. I generated TONS of for many years using tools like CodeSmith and T4. Generated code is best not seen. Plus, if you write this by hand, a lot can go around and it's almost always because of copypaste errors. The compiler can't save you if half your code is written in another language tunneled inside a string.
http://www.hanselman.com/blog/ExtendingNerdDinnerExploringDifferentDatabaseOptions.aspx
2/7
10/23/2014
NerdDinnerDataSet.DinnerRow CreateDinnerObject();
NerdDinnerDataSet.RSVPRow CreateRsvpObject();
With apologies to the original creator of the Regular Expression joke, I will co-opt it for this new one:
So you've got a problem, and you've decided ADO.NET DataSets to solve it. So, you've got two problems... - Me
What does FindByLocation look like now?
public IEnumerable<NerdDinnerDataSet.DinnerRow> FindByLocation(float latitude, float longitude)
{
var now = DateTime.Now;
var dinnerTableAdapter = new DinnerTableAdapter();
var rsvpTableAdapter = new RSVPTableAdapter();
The TableAdapters were created as part of the DataSetDesigner. Here' a screenshot from VS2010:
The Adapters fill the DataTables that consist of Rows. This unfortunately leaks out of our Repository into our Controller as our "Model" is now a DinnerRow. That then leaks (inappropriately) into a ViewPage of
type...wait for it...System.Web.Mvc.ViewPage<NerdDinner.Models.NerdDinnerDataSet.DinnerRow>.
http://www.hanselman.com/blog/ExtendingNerdDinnerExploringDifferentDatabaseOptions.aspx
3/7
10/23/2014
If you're going to use DataSets or Rows or DataTables, it's just that much more important that you use a good ViewModel projection. I personally go out of my way to not use DataSets and bump into them only in
legacy code. Try to avoid them - I'd prefer the DataReader example over this one.
LINQ to SQL
Remember that LINQ to SQL is a one to one mapping between the physical tables and columns of the database and the objects it creates. Many folks prefer to use it as a DAL (Data Access Layer) that just happens to
make objects, then pull the data out of the auto-generated objects into smarter business objects, such that the developer downstream never sees the generated L2S objects. Others just use them all through. For simple
samples, I used to use LINQ to SQL straight, and I still do for small (< 5 page) projects, but lately I've been using EF4 as it's just as easy. Anyway, here's the now more sensible modified interface:
public interface IDinnerRepository {
IQueryable<Dinner> FindAllDinners();
IQueryable<Dinner> FindByLocation(float latitude, float longitude);
IQueryable<Dinner> FindUpcomingDinners();
Dinner GetDinner(int id);
void Add(Dinner dinner);
void Delete(Dinner dinner);
}
void Save();
return dinners;
Notice the "NearestDinners" method there. That's a clever thing, I think. The database has a scalar-valued function called DistanceBetween for calculating the distance between two lat-longs
(thanks Rob Conery!) and a table value function called NearestDinners. They look like functions from LINQ to SQL's point of view and can be included in a LINQ to SQL query as seen
above.
4/7
10/23/2014
The one small difference, you may notice, is that NearestDinners isn't hanging off the "db" object (the DataContext) as it was with LINQ to SQL. Instead, in order to maintain the same clean query structure, those are
helper methods. One is an EdmFunction whose signature maps to that scalar function, and NearestDinner is implemented in code directly.
[EdmFunction("NerdDinnerModel.Store", "DistanceBetween")]
public static double DistanceBetween(double lat1, double long1, double lat2, double long2)
{
throw new NotImplementedException("Only call through LINQ expression");
}
public IQueryable<Dinner> NearestDinners(double latitude, double longitude)
{
return from d in db.Dinners
where DistanceBetween(latitude, longitude, d.Latitude, d.Longitude) < 100
select d;
}
Don't worry about that NotImplementedException, when the method is used in a LINQ to Entities Expression it's automatically mapped to the DistanceBetween function in the database as in the attribute.
I'd like to see better support for TVFs in EF, and I need to dig in to see if there's a better way that outlined here. Entity Framework also supports multiple databases, so you can get an Oracle Provider or a MySQL
provider, etc.
So there you have four different database implementations for NerdDinner. Last, but not least, is a sample that Ayende wrote for me to teach me NHibernate, and I would be remiss to not include it in such a
comparison.
NHibernate
This sample was written least year with ASP.NET MVC 2 on VS2008 using NHibernate 2.1. I'd love to see an updated version using even newer techniques.
Hibernate has the concept of a "Session" that lives for the life of a request in a Web Application. There's a config file (or a fluent configuration) that has all the properties and connection strings, (similar to EDMX files
in EF or DBMLs in L2S) and this all gets setup in the Global.asax. The session is created in the BeginRequest and disposed in the EndRequest.
public MvcApplication()
{
BeginRequest += (sender, args) => CurrentSession = SessionFactory.OpenSession();
EndRequest += (sender, args) => CurrentSession.Dispose();
}
Here's the FindByLocation method. This example isn't 100% fair as this version of NHibernate doesn't support those custom functions I've been talking about. I'm going to see if the latest does and update this post.
However, this does give you insight into its flexibility as it allowed inline SQL, set two parameters and returned a list of ints in a very tight single line of code.
public IQueryable<Dinner> FindByLocation(float latitude, float longitude)
{
// note that this isn't as nice as it can be, since linq for nhibernate
// doesn't support custom SQL functions right now
var matching = session.CreateSQLQuery("select DinnerID from dbo.NearestDinners(:latitude, :longitude)")
.SetParameter("longitude", longitude)
.SetParameter("latitude", latitude)
.List<int>();
return from dinner in FindUpcomingDinners()
where matching.Any(x => x == dinner.DinnerID)
select dinner;
}
A better example that lets NHibernate shine would be something more typical like:
public IQueryable<Dinner> FindUpcomingDinners()
http://www.hanselman.com/blog/ExtendingNerdDinnerExploringDifferentDatabaseOptions.aspx
5/7
10/23/2014
You'll note that LINQ to NHibernate is nice and comfortable and looks just like you'd expect it to.
Just like EF and LINQ to SQL, there's a mapping file that explains how tables and columns and dataTypes map to real objects, although there isn't a visual editor as far as know. I believe there are fluent ways to express
this in code, so if you're an NHibernate user, let me know alternative ways to express this and I'll update the post.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NerdDinner" namespace="NerdDinner.Models">
<class name="Dinner" table="Dinners" lazy="false">
<id name="DinnerID">
<generator class="identity"/>
</id>
<property name="Title"/>
<property name="EventDate"/>
<property name="Description"/>
<property name="HostedBy"/>
<property name="ContactPhone"/>
<property name="Address"/>
<property name="Country"/>
<property name="Latitude"/>
<property name="Longitude"/>
<bag name="RSVPs" cascade="all-delete-orphan" inverse="true">
<key column="DinnerID"/>
<one-to-many class="RSVP"/>
</bag>
</class>
<class name="RSVP" table="RSVP" lazy="false">
<id name="RsvpID">
<generator class="identity"/>
</id>
<property name="AttendeeName"/>
<many-to-one name="Dinner"
column="DinnerID"/>
</class>
</hibernate-mapping>
This mapping file is actually marked as an Embedded Resource, and is accessed at runtime by the NHibernate runtime. You just need to make it, and the magic happens for you. NHibernate's claim to fame is support
for lots of different databases like SQL Server, Oracle, MySQL and more. There's also lots of supporting projects and libraries that orbit NHibernate to give you additional control, or different ways to express your
intent.
Conclusion
There's lots of choices for Database Access on .NET. You'll run into DataReaders in older or highly tuned code, but there's no reason it can't be hidden in a Repository and still be pleasant to use. LINQ to SQL is nice,
lightweight and fast and has dozens of bug fixes in .NET 4, but Entity Framework is the way they are heading going forward. Plus, Entity Framework 4 is *way* better than EF 3.5, so I'm using it for any "larger than
small" projects I'm doing and I'm not having much trouble. NHibernate is very mature, actively developed and has a great community around it and it's not going anywhere.
In my opinion, if you're doing database access with .NET you should be using Entity Framework 4 or NHibernate.
Four Database-styles Sample
http://www.hanselman.com/blog/ExtendingNerdDinnerExploringDifferentDatabaseOptions.aspx
6/7
10/23/2014
NHibernate Sample
Related Links
10 How To Videos on Entity Framework from Julie Lerman
Summer of NHibernate Screencast Series
Whitepaper: Data Access Practices Using Microsoft .Net: A Nerdly Comparison
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Sponsored By
Hosting By
Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
http://www.hanselman.com/blog/ExtendingNerdDinnerExploringDifferentDatabaseOptions.aspx
7/7