如何创建新模块 DotNetNuke 6 & Entity Framework Code First

博客园原文地址 : 如何创建新模块 DotNetNuke 6 & Entity Framework Code First

Soleil 索引 :

第一部分: 开发新模块 – DNN 6

第二部分: 模块里使用EF Code First

第一部分:开发新模块 - DNN6


Étoile 如果你还不知道DotNetNuke是什么的话,请访问 www.dotnetnuke.com

Étoile 如果你还不知道如何在本地安装DNN 6, 请猛戳 http://codeciel.blogspot.fr/2012/03/how-to-install-dotnetnuke-6-on-local.html

Arc-en-ciel 如果你想要知道如何开发一个你自己的DNN6模块,那么这篇文章就是你需要的。


  • 步骤1:通过DNN6 Host创建desktop module和module definition

从数据库的角度出发,这一步会把新模块的相关信息存入数据库,让DNN可以识别,这样你就可以把这个模块加入任意页面。如果你仔细看了DNN数据库的结构后,你会发现有很多和模块(Module)相关的表 - dbo.DesktopModules, dbo.Modules, dbo.ModuleDefinitions, dbo.TabModules, dbo.PortalDesktopModules. 这儿不会细讲这些表的用途,如果可以的话,有机会我会专门为此写一篇博客。

所以,现在你只要按照下面的步骤就可以创建一个属于你的DNN模块 :

  - 用host账户登入你的DNN6网站

  - 点击Host页面,再进入Extensions页面

  - 单击Manage按钮, 选择Create New Module,你将会看到(如Figure 1所示):

提示: 如果你找不到Manage按钮,请确认你的页面模式(通常在右上角可以看见)是Edit而不是View或Layout。

1

Figure 1 : 创建模块

  - 填好这些文本框后。(最后把"Add Test Page ?"打上勾,以简化步骤)

  - 单击“Create Module”, 然后你就可以看见新的模块已经出现在了“TestEFCodeFirstPage”页面上(如Figure 2 所示)了

2

Figure 2 : 新页面里包含了新模块

 

  • 步骤2:把新模块与VS项目联系起来

  - 现在打开VS2010,创建新项目,如Figure 3 所示。

提示:值得注意的事,项目的名字必须与module的名字相同,即“EFCodeFirst”,开始的时候你可以把这个项目保存到任何位置,因为一旦完成创建之后,我们会把这个文件夹移动到<网站根目录>/DesktopModules里,合并这俩个文件夹。

3

Figure 3 : 创建新vs2010项目

  - 再次用VS2010打开“EFCodeFirst.csproj”. 因为我们还需要配置这个项目,来让它可以顺利的在DNN6下编译和运行。

  - 点击项目属性,然后进入到Compiler页面,修改输出路径为<网站根目录>\bin\

  - 如果你Site的运行环境是.net framework version 2.0.50727.5448的话,请在IIS里修改Site的application pool为ASP.NET V4.0。因为EF Code First是需要.net 4.0的。

  - 添加Dotnetnuke.dll引用,它可以在<网站根目录>\bin\下找到。需要注意的是在属性里将copy local设置为false。

提示:上面这种添加Dotnetnuke.dll引用的方法不是企业开发常用的,或者说是应该避免的才比较正确。因为Dotnetnuke不断在更新,为了保证团队开发时,Dotnetnuke.dll引用都是同一个版本,我们通常会固定一个稳定的版本来开发。具体操作是:存放指定版本Dotnetnuke.dll的文件夹是共享的,所有的dnn引用都是指向这个文件夹的。

  - 重命名web.config文件为web.config.old,因为在网站的根目录已经有了一个web.config。

  - 删除EFView.ascx文件

  - 添加一个新文件,文件类型是Web User Control,还是命名为“EFView.ascx ”,以便DNN能识别。

  - 打开文件EFView.ascx, 我们随便写点东西进去,比如 : “This is my first dnn6 module”

  - 打开文件EFView.ascx.cs,将它的基类换成PortalModuleBase,保存并编译这个项目

  - 刷新页面http://dnn614/TestEFCodeFirstPage.aspx,你将会看到如Figure 4所示 :

4

Figure 4 : 测试页面和行模块


好了,看到这里你就应该知道如何创建一个DNN6模块了。在这个模块里,你可以根据需求,随意开发。下面的第二部分就是,在一个DNN模块里使用Entity Framework Code First进行实战开发。

 

 

 

  

第二部分 : DNN模块里使用EF Code First


Pouce levé 这部分是基于这篇文章: Introduction to Entity Framework Code First


  • 步骤 3* : Entity Framework 简介

在我直接进入正题之前,我想先介绍一下EF。EF是一个.NET平台下的ORM框架,个人觉得做的很不错,之前还接触过另外俩款在.NET平台下的ORM - LLBLGen Pro和NHibernate。LLBLGen Pro自从收费之后,功能也越来越强大,用起来也挺顺手的,之所以没有继续用的原因很简单:我现在这个客户公司,不想花钱买它的licence。NHibernate是从Java版的Hibernate移植过来的,就好比Lucene.NET与Lucene的关系一样,不同的是Lucene.NET早早就进Apache的孵化器了,而NHibernate的生命力还很强大。我没有用NHibernate的原因也很简单,被它的配置文件惊吓住了。比较这三款的优缺点不是这里的重点,每个人的习惯和思考方式不一样,所以喜好自然不同,选一款自己用的顺手又顺心的ORM就可以了。下面开始介绍EF的三种使用方法 :

EF在4.0的时候只提供俩种与实体交互的方法,即数据库优先模型优先。前一种情况下,你在开发你的应用前开始设计数据库。然后你可以用VS自带的实体数据模型设计工具来设计一个虚拟模型,操作很简单:直接从数据库把表拖拽到设计器中(如Figure 5所示)。后一种情况下,我们先用实体数据模型设计工具来手工设计模型(如Figure 6所示),然后指定数据库的连接。但不管是前面哪种情况,你都要使用实体数据模型设计器来生成你的数据模型对象。

 

2-1

Figure 5 : 数据库优先

 

2-2

Figure 6 : 模型优先

 

EF在4.1中引入一种新的模式来生成模型对象 : 代码优先(Code First)。在代码优先这种方式里,你不会遇到任何设计器。先全部用.NET Framework来搭建对象类,然后用一些特定属性和相关技术来生成EF数据模型。(如Figure 7一样)


2-3

Figure 7 : 代码优先


上面大致讲了一下这三种方法,如果你感觉有点混淆,静下心来,仔细看Figure 8,我相信你肯定会清楚不少。在我们这个模块中,我们使用的是最后一种。


2-4

Figure 8 : EF与实体交互的方式


第二部分的目的不仅仅是完成我们的模块,而是一步一步的,让大家弄清楚使用EF Code First的全部过程。接下来,在开发模块,你将处理一个用来表示博客帖子的数据模型,实际上,数据是驻留在SQL Server里一个名叫BlogPosts表中的。通过使用模型对象,你能够从表中读写数据。


  • 步骤 3 :  创建模型

假设我们的数据模型是用来储存博客帖子和博客贴分类的。根据三层架构的思想,我们应该另外创建一个project来专门开发数据模型 :在与“EFCodeFirst”项目同一个solution里创建Library类型的新项目,命名为“EFCodeFirstLib”。这个类库项目里将会包括俩个模型对象,即BlogPost和Category。他们的模型代码如下展示 :

提示: EFCodeFirstLib项目的文件夹应该放在<网站根文件夹>/DesktopModules/, 并且其编译路径应该是<网站根文件夹>/bin/.

public class BlogPost
{
  public Guid Id { get; set; }
  public string Title { get; set; }
  public string Content { get; set; }
  public DateTime PublishDate { get; set; }
  public virtual Category Category { get; set; }
}

public class Category
{
  public Guid Id { get; set; }
  public string Name { get; set; }
  public virtual ICollection<BlogPost> BlogPosts { get; set; }
}

在BlogPost类中,定义了五个属性: Id, Title, Content, PublishDate 和 Category。注意里面的Category是外键。Category类有三个属性:Id, Name 和 BlogPosts。其中的BlogPosts 是一个属性集合,因为可以有很多个博客帖子属于同一个category。需要记住的是,默认情况下,表示主键的属性类型应该是int或Guid。当然,你可以用一些attributes定制主键。我们将在后面讨论。


  • 步骤4 : 创建Data Context

现在模型对象都已经创建好了,让我们开始创建一个Data Context,通过它可以让你查询和处理实体数据,另外,它的基类是DbContext类。下面就是我们在模块中要使用的Data Context:

public partial class BlogContext : DbContext
{
  public BlogContext():base("BlogDb")
  {
  }

  public DbSet<BlogPost> BlogPosts { get; set; }
  public DbSet<Category> Categories { get; set; }
}

BlogContext类继承了DbContext类。在它的构造函数中,调用了基类的构造器并且传递了所需的数据库名称。当你首次运行应用时,将会为你用这个名字创建数据库。如果不传递这个名称,默认创建数据库的名字将会是DbContext类的fully qualified name。在我们这个例子中就是“EFCodeFirstLib.BlogContext”。

BlogContext类定义了俩个公共属性,即BlogPosts和Categories。这些属性的类型是DbSet。DbSet类代表了一个实体集(从数据库角度,就是一个数据库的表),可以执行CRUD(创建,读取,更新,删除)操作


  • 步骤5 : 测试数据库的生成(可选)

我们现在可以测试我们已经写的代码了,当然你也可以选择跳过这一步。

首先在EFCodeFirst项目的“EFView.ascx”中添加一个按钮(button),并且生成相应的按钮点击事件方法。加入我们刚刚创建的EFCodeFirstLib项目的引用。


Figure 9 : 测试页面


然后在按钮点击事件方法中写入以下代码:

using (var db = new BlogContext())
{
  Guid id = Guid.NewGuid();
  var cat = new Category { Id = id, Name = "ASP.NET" };
  var post = new BlogPost { Title="Title1", Content="Hello World!", PublishDate=new DateTime(2011,1,1), Category=cat};
  db.Categories.Add(cat);
  db.BlogPosts.Add(post);
  int i = db.SaveChanges();
}

上面的代码创建了一个BlogContext实例,接着创建了Category和BlogPost类的各自实例。把新创建的实体实例加入到DbSets里面,最后调用SaveChanges()方法。 SaveChanges()方法将持久化加入的实体数据,也就是把实体数据写入数据库相应的表中,并且返回此次操作执行的记录数。

现在我们编译这俩个项目,在浏览器中打开http://dnn614/TestEFCodeFirstPage.aspx. 点击页面中的按钮。 因为我们是第一次运行并点击按钮,EF将会在SQL Express里创建新的数据库和表。库名是BlogDb,因为我们在DataContext的构造里已经指出了;俩个表名分别是BlogPosts和Categories。


Sans titre1

Figure 11: SQLExpress里创建的数据库和表

 

值得注意的是BlogPosts和Categories表里的Id列被设定为主键, 同样BlogPosts表中,有一名叫Category_Id的外键被定义。

 

  • 步骤6 : 每次运行(点击按钮)时重新创建数据库

在测试时,你也许想要在每次运行应用时都创建或重新创建数据库,以便初始化运行环境。同时也想在创建数据库的时候加入一些测试数据。当你修改了你的实体代码时,因为数据库的表也会相应的改变, 数据库也应该重新创建。默认情况下,EF只会在你首次运行时创建数据库,之后应用会一直操作这同一个数据库。这当然不是我们想要的,因为我们的实体代码会随着需求的变更而修改或增加,为了改变这种默认行为,你需要在代码里创建一个database initializer,并且继承DropCreateDatabaseAlways类。如名字暗示的那样,DropCreateDatabaseAlways类总是会重新创建数据库。下面的代码就是我们自己的BlogContextInitializer :

提示:你也可以继承DropCreateDatabaseIfModelChanges类,它只会在Model改变的时候,重新创建数据库。

public class BlogContextInitializer : DropCreateDatabaseAlways<BlogContext>
{
  protected override void Seed(BlogContext context)
  {
    Category cat1 = new Category { Id=Guid.NewGuid(), Name=".NET Framework" };
    Category cat2 = new Category { Id = Guid.NewGuid(), Name = "SQL Server" };
    Category cat3 = new Category { Id = Guid.NewGuid(), Name = "jQuery" };
    context.Categories.Add(cat1);
    context.Categories.Add(cat2);
    context.Categories.Add(cat3);
    context.SaveChanges();
  }
}

BlogContextInitializer 继承了DropCreateDatabaseAlways 基类,并且重写了Seed()方法,方法里写入了一些往数据库里添加样本数据的代码:添加了三个categories。一旦完成了initializer,你需要添加以下代码到按钮点击事件方法里去:

protected void Button1_Click(object sender, EventArgs e)
{
  Database.SetInitializer<BlogContext>(new BlogContextInitializer());
  ...
  ...
}

现在你就会发现每次点击按钮时,数据库都会重新创建,并且填充样本数据到Categories表里。

 

  • 步骤7 : 在Code First里使用已有的数据库

上面的代码里,你允许EF自己创建数据。但在许多情况下,你会想要用已经存在的数据库而不是创建一个新的。又因为我们在DNN里使用EF Code First,所以在任何情况下都不可能全部重新创建DNN使用的数据库(DNN614)。我们不得不使用其它的数据库(比如DNN614_BlogDb)。如何让Code First知道我们想要使用那个数据库呢?只需要在web.config的Connection String里指明就可以了:

<connectionStrings>
  <add name="SiteSqlServer" connectionString="Data Source=(local)\LEGAL;Initial Catalog=DNN614;User ID=login614;Password=xxx" providerName="System.Data.SqlClient" />
  <add name="BlogDb" connectionString="Data Source=(local)\LEGAL;Initial Catalog=DNN614_BlogDb;User ID=lgfr;Password=xxx" providerName="System.Data.SqlClient"/>
</connectionStrings>


Sans titre

Figure 12 : 指明已有的数据库

 

需要注意的地方是,我们在<connectionStrings>里添加的数据库连接字符串的名字,必须和我们在DataContext构造器里指明的一样(“BlogDb”)。

如果你在DataContext没有指明,那么那个数据库连接字符串的名字就必须和DataContext类名一样(“BlogContext”)。

为了让大家更清楚Code First选择数据库连接字符串的流程,我们用这个例子再解释一遍 :

  •   情况一:在DataContext中(BlogContext)指明了名字“BlogDb”

      EF首先会在configuration文件里查找是否有名字为“BlogDb”的数据库连接字符串。

      如果有,EF就会使用这个。如果没有,EF就会在本地的SQL Express中创建一个名为“BlogDb”的新数据库。

  •   情况二:没有在DataContext中(BlogContext)指明

      EF首先也会在configuration文件里查找名为“BlogContext ”的数据库连接字符串。

      如果找到,EF就是使用;否则EF就会在SQL Express里创建一个名为“EFCodeFirstLib.BlogContext”的新数据库。其中EFCodeFirstLib是BlogContext的命名空间。

 

  • 步骤8 : 使用Data Annotations来标明数据表和键的信息

上面的例子中,当创建实体对象对应的数据库和表时,EF使用的是默认的约定。我们也可以用一些data annotations来定制EF的行为。例如下面代码所示,我们添加了一些data annotations属性来标明表名([Table]),主键([Key]) 和外键([ForeignKey])信息。

[Table("BlogPosts")]
public class BlogPost
{
  [Key]
  public Guid Id { get; set; }
  public string Title { get; set; }
  public string Content { get; set; }
  public DateTime PublishDate { get; set; }
  [ForeignKey("Category")]
  public Guid CategoryId { get; set; }
  public virtual Category Category { get; set; }
}

[Table("Categories")]
public class Category
{
  [Key]
  public Guid Id { get; set; }
  public string Name { get; set; }
  public virtual ICollection<BlogPost> BlogPosts { get; set; }
}

 

  • 总结 :

Entity Framework Code First允许我们直接在代码使用EF特性,比如未经任何实体数据模型设计。这种方式下,全部使用C#类来创建实体对象,需要定义一个继承了DbContext类的DataContext类,同时可以使用data annotation属性显式地标明相关信息。默认时,当应用第一次运行时,EF会在SQL Express里创建新的数据库;我们可以通过创建自己的数据库初始化策略(Database Initializer)来改变这种默认行为。也可以使用已经存在的数据库,只需在configuration文件里标明了数据库连接字符串。 

Code First相对与其他两种方式来说,有很大的灵活性。但我觉得在DNN中不太适合使用,因为我不是特别能接受一个DNN网站同时有俩个数据库,维护起立不方便。所以,在现实项目中,我经常使用的方式是Database First。

最后友情提供一下本例中的DNN模块安装包和源代码:安装包  源代码Profitez-le et je vous souhaite un très bon weekend !!


posted @ 2012-03-23 23:05 yan.h 阅读(...) 评论(...) 编辑 收藏