扩展NerdDinner:探索不同的数据库选项


[原文发表地址]Extending NerdDinner: Exploring Different Database Options

[原文发表时间] May 20, 2010 12:57 PM

最初的NerdDinner范例程序很简单。两个范例其实都很简单。或许这是个新的Northwind,因为它能很好的处理ASP.NET MVC模式。然而,想要完成用户们想做的事情,NerdDinner 范例程序可能并不足够完美或理想化。

幸运的是,社会上有很多能人已经对NerdDinner产生过分歧,而且对其做过一些有趣的研究。这些示例通常针对一个特定的情景。所以大可不必与主干部分合并,尽管如此它们还是具有一定的教育意义。

五类NerdDinners –每个访问数据都不同

我最先是在Linq To SQL上完成NerdDinner的,因为 L2S快速,简单,有一个对象和表之间1:1​​的关系,而且坦白说来,我并没有觉得Entity Framework 3.5Fast forward to .NET 4 and the Entity Framework 4 is pretty nice.升级至.NET 4和Entity Framework 4有多好。The current NerdDinner v2 sample ( available in the Trunk on the NerdDinner Codeplex Site, click Source Control ) uses Entity Framework 4.目前NerdDinner V2范例程序( Codeplex网站上的NerdDinner主干可用,请单击源代码管理)使用了Entity Framework 4.。

I heard, however, that Chris Sells was interested in exploring a ASP.NET MVC sample that accessed databases via the various ways you’ll find in the wild:我听说,Chris Sells 对开发ASP.NET MVC模式范例很感兴趣。此范例通过以下外部方式访问数据库:

* ADO.NET Connected (DataReaders) ADO.NET连接(DataReaders)

* ADO.NET Disconnected (DataSets) ADO.NET断开连接(数据集)

* LINQ to SQL LINQ to SQL

* LINQ to Entities (Entity Framework) LINQ to Entities(实体框架)

Chris曾与来自Useable ConceptsNick Muhonen合作过,Nick创建了四个范例程序。他曾在http://msdn.com/data网站上发表了一篇很详细的文章。

这些范例程序是关于VS2010和ASP.NET MVC 2的。我们来对比分析一下。我将去年Ayende专门为了教会我NHibernate而创建的一个范例程序也囊括其中,并把它作为第五个范例。对此,我非常感谢Ayende牺牲大量的时间为团队所做的一切,同时也非常感谢Ayende对微软如此诚恳,认真的态度。

ADO.NET连接(DataReaders

难以置信!还有人用这个吗?答案很明显“是的”。它们运行的很快,许多生成DALs(数据访问层)在其中心位置都有一个DataReader。

下面是一个略微修改过的IDinnerRepository,它在第一个范例程序中使用过。请注意:它没有使用IQueryable。I understand that it would be ideal to have a single IDinnerRepository interface and have all these samples share it, but these database access techniques differed so greatly that I’m told they gave up as it made the rest of the code smell (more) just to meet that one goal.我明白,要是拥有一个单独的IDinnerRepository目标接口,并使得所有的范例程序都能共享的话就好了。但这些数据库访问技术区别太大,所以我被告知此方法行不通,因为它会更改其它的代码以使它更符合那个目标。

   1: public interface IDinnerRepository
   2: {
   3:      //Data Access Methods
   4:      IEnumerable<Dinner> FindAllDinners();
   5:      IEnumerable<Dinner> FindByLocation(float latitude, float longitude);
   6:      IEnumerable<Dinner> FindUpcomingDinners();
   7:      Dinner GetDinner(int id);   
   8:      void AddDinner(Dinner dinner); 
   9:      void UpdateDinner(Dinner dinner); 
  10:      void DeleteDinner(int id);
  11:      void AddDinnerRsvp(int dinnerID, RSVP rsvp);
  12: }

你之前可能见过与这些类似的代码。最起码它并不手动串联SQL!It could also be a sproc.它也可能是存储过程。 The pattern remains.该模式依然存在。

   1: public IEnumerable<Dinner> 
   2: FindByLocation(float latitude, 
   3: float longitude)
   4: {
   5: using (var connection = 
   6: new 
   7: SqlConnection(this.connectionString))
   8: {
   9: var commandText =
  10: @"
  11: select d.DinnerID, d.Title, d.EventDate, d.[Description], 
  12: d.HostedBy, 
  13: d.ContactPhone, d.[Address], d.Country, d.Latitude, 
  14: d.Longitude 
  15: from Dinners d
  16: inner join dbo.NearestDinners(@Latitude,@Longitude) nd 
  17: on
  18: d.DinnerID = nd.DinnerID
  19: where @CurrentDate <= d.EventDate
  20: order by d.DinnerID
  21: select r.RsvpID, r.DinnerID, r.AttendeeName from RSVP 
  22: r
  23: inner join Dinners d on
  24: d.DinnerID = r.DinnerID
  25: inner join dbo.NearestDinners(@Latitude,@Longitude) nd 
  26: on
  27: d.DinnerID = nd.DinnerID
  28: where @CurrentDate <= d.EventDate
  29: order by r.DinnerID, r.RsvpID
  30: ";
  31: var command = new 
  32: SqlCommand(commandText, connection);
  33: var parameters = new[]{
  34: new SqlParameter{ParameterName = 
  35: "Latitude", DbType = DbType.Double, Value = 
  36: latitude},
  37: new SqlParameter{ParameterName = 
  38: "Longitude", DbType = DbType.Double, Value = 
  39: longitude},
  40: new SqlParameter{ParameterName = 
  41: "CurrentDate", DbType = DbType.Date, Value = 
  42: DateTime.Now}};
  43: command.Parameters.AddRange(parameters); 
  44: connection.Open();
  45: return GetDinners(command);
  46: }
  47: }

这是描述私有方法GetDinners的一个代码段,它会完成拆解DataReader的工作,将它转换成对象。

   1: private List<Dinner> GetDinners(SqlCommand 
   2: command)
   3: {
   4: var returnDinners = new 
   5: List<Dinner>();
   6: using (var reader = 
   7: command.ExecuteReader())
   8: {
   9: //Project first result set into a collection of Dinner 
  10: Objects
  11: while (reader.Read())
  12: {
  13: var dinner = new Dinner()
  14: {
  15: DinnerID = 
  16: (int)reader["DinnerID"],
  17: Title = 
  18: (string)reader["Title"],
  19: Description = 
  20: (string)reader["Description"],
  21: Address = 
  22: (string)reader["Address"],
  23: ContactPhone = 
  24: (string)reader["ContactPhone"],
  25: Country = 
  26: (string)reader["Country"],
  27: HostedBy = 
  28: (string)reader["HostedBy"],
  29: EventDate = 
  30: (DateTime)reader["EventDate"],
  31: Latitude = 
  32: (double)reader["Latitude"],
  33: Longitude = 
  34: (double)reader["Longitude"]
  35: };
  36: returnDinners.Add(dinner);
  37: }
  38: //...

这非常经典。多年来,我一直用CodeSmith 和 T4等工具生成了大量的代码。生成的代码是我见过的最好的。另外,如果这些代码是手写的,很多代码可能会因为复制-粘贴的错误变得七零八落。还有,如果字符串中有半数以上的代码是以另外一种语言编写的,那么就算是编译器也帮不了你了。

ADO.NET断开连接(数据集)

我曾经把数据集称作撒旦之子,而把毁坏数据集的人称作圣人。我一直坚持着这样的称呼。它们有到处泄露信息的渠道, IDinnerRepository接口就是个例子。大家好好看看吧!

   1: public interface IDinnerRepository {
   2: //Data Access Methods
   3: IEnumerable<NerdDinnerDataSet.DinnerRow> 
   4: FindAllDinners();
   5: IEnumerable<NerdDinnerDataSet.DinnerRow> 
   6: FindByLocation(float latitude, 
   7: float longitude);
   8: IEnumerable<NerdDinnerDataSet.DinnerRow> 
   9: FindUpcomingDinners();
  10: NerdDinnerDataSet.DinnerRow 
  11: GetDinner(int id);
  12: void AddDinner(NerdDinnerDataSet.DinnerRow 
  13: dinner);
  14: void DeleteDinner(NerdDinnerDataSet.DinnerRow 
  15: dinner);
  16: void 
  17: AddDinnerRsvp(NerdDinnerDataSet.DinnerRow dinner, 
  18: NerdDinnerDataSet.RSVPRow rsvp);
  19: void DeleteRsvp(NerdDinnerDataSet.RSVPRow 
  20: rsvp);
  21: // Persistence Method
  22: void Save();
  23: //Object factory methods
  24: NerdDinnerDataSet.DinnerRow CreateDinnerObject();
  25: NerdDinnerDataSet.RSVPRow CreateRsvpObject();
  26: }

在此,我向正规表达式笑话的原创者表示歉意。我会将它指派在新的()中。

假设你现在碰到一个难题,你决定通过ADO.NET 数据集来解决。这样的话,会出现两个问题。。。-

FindByLocation现在看起来怎么样?

   1: public IEnumerable<NerdDinnerDataSet.DinnerRow> 
   2: FindByLocation(float latitude, 
   3: float longitude)
   4: {
   5: var now = DateTime.Now;
   6: var dinnerTableAdapter = new 
   7: DinnerTableAdapter();
   8: var rsvpTableAdapter = new 
   9: RSVPTableAdapter();
  10: dinnerTableAdapter.FillByLocation(nerdDinnerDataSet.Dinner, 
  11: latitude, longitude, now);
  12: rsvpTableAdapter.FillByLocation(nerdDinnerDataSet.RSVP, 
  13: latitude, longitude, now);
  14: return 
  15: this.nerdDinnerDataSet.Dinner;
  16: }

被创建的TableAdapters是DataSetDesigner的一部分。下面是VS2010上的截图。

DataSet Designer

Adapters填满了由行组成的DataTables。这使得Repository 泄露到了Controller中。而“Model”则变成了DinnerRow。然后又泄露(不当)到了页面视图类型中。。。。请等待。。。。System.Web.Mvc.ViewPage<NerdDinner.Models.NerdDinnerDataSet.DinnerRow>.

如果你想使用数据集,行或者数据表,其实使用一个好的视图模式投影更为重要。我个人一般不用数据集,只有在旧代码处才碰到。请尽量避免使用—比起数据集,我更情愿用DataReader.

LINQ to SQL
记住,LINQ to SQL是在物理表与数据库的列和所创建的对象之间一对一的映射。很多人偏向把它作为DAL(数据访问层)来使用。它可以创建对象,从自动生成的对象中提取数据,再将其转换成智能业务对象。这样,下层开发人员就看不到生成的L2S对象。其他人则是一直都在使用。对于简单的范例,我过去是直接用LINQ to SQL。我现在仍然用它做一些小(小于5页)的项目。最近,我一直在用EF4,因为它很容易。总之,下面是最新的经过合理修改过的接口。
   1: public interface IDinnerRepository {
   2: IQueryable<Dinner> FindAllDinners();
   3: IQueryable<Dinner> 
   4: FindByLocation(float latitude, 
   5: float longitude);
   6: IQueryable<Dinner> FindUpcomingDinners();
   7: Dinner GetDinner(int id);
   8: void Add(Dinner dinner);
   9: void Delete(Dinner dinner);
  10:  
  11: void Save();
  12: }

然后,执行FindByLocation:

   1: public IQueryable<Dinner> 
   2: FindByLocation(float latitude, 
   3: float longitude) {
   4: var dinners = from dinner in 
   5: FindUpcomingDinners()
   6: join i in 
   7: db.NearestDinners(latitude, longitude) 
   8: on dinner.DinnerID equals i.DinnerID
   9: select dinner;
  10: return dinners;
  11: }

注意这里的"NearestDinners"方法。我任务这是个非常聪明的做法。数据库有个标量值函数叫做DistanceBetween,可用来计算两个lat-longs(感谢Rob Conery!)的距离,还包括NearestDinners.表值函数。它们就像LINQ to SQL的视图点函数,可以被涵盖在LINQ to SQL 查询中,如下所示:

image

干净利落。

LINQ to Entities(实体框架)

实体框架4使用和上面相同的接口和FindByLocation:

   1: public IQueryable<Dinner> 
   2: FindByLocation(float latitude, 
   3: float longitude) {
   4: var dinners = from dinner in 
   5: FindUpcomingDinners()
   6: join i in 
   7: NearestDinners(latitude, longitude) 
   8: on dinner.DinnerID equals i.DinnerID
   9: select dinner;
  10: return dinners;
  11: }

你可能会注意到一个小的差异,NearestDinners并没有与“db”对象(数据背景)分离,因为它使用LINQ to SQL。相反,为了保持相同的整洁的查询结构,这些都是辅助方法。一个是EdmFunction标签函数,是标量函数的映射。NearestDinner则可以直接在代码中实现。

   1: [EdmFunction("NerdDinnerModel.Store", 
   2: "DistanceBetween")]
   3: public static double 
   4: DistanceBetween(double lat1, 
   5: double long1, double lat2, 
   6: double long2)
   7: {
   8: throw new 
   9: NotImplementedException("Only call through LINQ 
  10: expression");
  11: }
  12: public IQueryable<Dinner> 
  13: NearestDinners(double latitude, 
  14: double longitude)
  15: {
  16: return from d in 
  17: db.Dinners
  18: where DistanceBetween(latitude, longitude, d.Latitude, 
  19: d.Longitude) < 100
  20: select d;
  21: }

不用担心NotImplementedException,该方法在LINQ to Entities Expression使用时会自动映射到数据库属性的DistanceBetween函数。

我希望看到更好的EF中对TVFs支持。我也想探索是否有一个更好的办法列在这里。实体框架也支持多个数据库,这样你就可以获取一个Oracle提供商或MySQl提供商等。

因此,你可用四种不同的数据库实现NerdDinner。最后但也同样重要的是Ayende为教我NHibernate而写的一个范例。由于我的疏忽,没有把它进行对比。

NHibernate

这个范例是去年用NHibernate 2.1在VS2008的ASP.NET MVC 2写的。我很想看到一个使用更新技术的升级版本。

Hibernat有一个“会话”的概念,是为了满足Web程序的需要而设的。有一个配置文件(或流利配置),其中包含所有属性和连接字符串(类似于EF中的EDMX文件或L2S中的DBML)。这些都是在Global.asax.中设置的。会话在BeginRequest中创建,在EndRequest.中处理。

   1: public MvcApplication()
   2: {
   3: BeginRequest += (sender, args) => CurrentSession = 
   4: SessionFactory.OpenSession();
   5: EndRequest += (sender, args) => 
   6: CurrentSession.Dispose();
   7: }

这是FindByLocation方法。这个例子不是100%合理,因为NHibernate版本不支持我一直谈论到的自定义函数。我想看看最新的方法是否支持,所以我更新了这个帖子。然而,这确实能让你洞察它的灵活性,因为它通过一行紧凑的代码来允许内联SQL,设置两个参数,并返回到一整行代码的整数列表。

   1: public IQueryable<Dinner> 
   2: FindByLocation(float latitude, 
   3: float longitude)
   4: {
   5: // note that this isn't as nice as it can be, since linq 
   6: for nhibernate 
   7: // doesn't support custom SQL functions right now
   8:  
   9: var matching = session.CreateSQLQuery("select 
  10: DinnerID from dbo.NearestDinners(:latitude, :longitude)")
  11: .SetParameter("longitude", 
  12: longitude)
  13: .SetParameter("latitude", 
  14: latitude)
  15: .List<int>();
  16: return from dinner in 
  17: FindUpcomingDinners()
  18: where matching.Any(x => x == dinner.DinnerID)
  19: select dinner;
  20: }

一个更好的让NHibernate变得很突出的例子就是下面这些更为典型的东西:

   1: public IQueryable<Dinner> 
   2: FindUpcomingDinners()
   3: {
   4: return from dinner in 
   5: FindAllDinners()
   6: where dinner.EventDate >= DateTime.Now
   7: orderby dinner.EventDate
   8: select dinner;
   9: }

你会注意到,LINQ to NHibernate非常好用,舒适,而且看起来就是你期望的类型。

就像EF和LINQ to SQL,有一个映射文件,解释表、列和数据类型如何映射到真正的对象。虽然据了解,目前还没有一个可视化编辑器。我相信,可以在代码中用很顺畅的方式来阐释。因此如果你是NHibernate用户,请告知我更多的阐释方法,随后我会更新帖子。

   1: <?xml 
   2: version="1.0" 
   3: encoding="utf-8" ?>
   4: <hibernate-mapping 
   5: xmlns="urn:nhibernate-mapping-2.2" 
   6: assembly="NerdDinner" 
   7: namespace="NerdDinner.Models">
   8: <class 
   9: name="Dinner" 
  10: table="Dinners" 
  11: lazy="false">
  12: <id 
  13: name="DinnerID">
  14: <generator 
  15: class="identity"/>
  16: </id>
  17: <property 
  18: name="Title"/>
  19: <property 
  20: name="EventDate"/>
  21: <property 
  22: name="Description"/>
  23: <property 
  24: name="HostedBy"/>
  25: <property 
  26: name="ContactPhone"/>
  27: <property 
  28: name="Address"/>
  29: <property 
  30: name="Country"/>
  31: <property 
  32: name="Latitude"/>
  33: <property 
  34: name="Longitude"/>
  35: <bag 
  36: name="RSVPs" 
  37: cascade="all-delete-orphan" 
  38: inverse="true">
  39: <key 
  40: column="DinnerID"/>
  41: <one-to-many 
  42: class="RSVP"/>
  43: </bag>
  44:  
  45: </class>
  46: <class 
  47: name="RSVP" 
  48: table="RSVP" 
  49: lazy="false">
  50: <id 
  51: name="RsvpID">
  52: <generator 
  53: class="identity"/>
  54: </id>
  55: <property 
  56: name="AttendeeName"/>
  57: <many-to-one 
  58: name="Dinner"
  59: column="DinnerID"/>
  60: </class>
  61: </hibernate-mapping>

这个映射文件其实被标注为嵌入资源,并通过NHibernate运行时进行访问。如果你试着这样做的话,奇迹就会发生。NHibernate’s之所以声名鹊起在于它支持众多不同的数据库,比如SQL服务器,Oracle, MySQL,等等。还有些运行NHibernate的支持项目和库,这些都给你额外的控制,或不同的方式来实现你的意图。

结语

.对NET的数据库访问有很多选择。你会遇到以前的DataReaders或高度优化的代码。但是没有理由不能将它隐藏在Repository下,然后很愉快的使用。LINQ to SQL非常好用,轻盈,快速,并在.NET 4种有很多bug修复程序。但是它们正朝着实体框架努力。另外,实体框架4的*方式*比EF 3.5好,所在大的项目中我会使用它,也没碰到什么大麻烦。NHibernate很成熟,,很多团队都在积极地开发它。

在我看来,如果你正用.NET进行数据访问,你应该使用实体框架4或NHibernate。

四种 数据库-样式 示例

nddatafoursamples

NHibernate 示例

Open NerdDinner- NHibernate 2.2.zip

Open NerdDinner- NHibernate 2.2.zip

相关链接


Comments (0)

Skip to main content