NBearV3 Step by Step教程——IoC篇

版本

1.2 [2006-11-12]

简介

本教程演示如何基于NBearV3的IoC模块开发一个Web应用程序的基本过程。本教程同时演示使用NBear.Tools.DbToEntityDesign.exe工具从现有的数据库生成设计实体的过程。

注:在阅读本文之前,建议读者先阅读《NBearV3 Step by Step教程——ORM篇》以掌握NBearV3中有关ORM的基本知识。

目标

通过本教程,读者应能够掌握使用NBearV3的IoC模块的基本过程,以及使用NBear.Tools.DbToEntityDesign.exe工具,对已有数据库结构的项目,使用NBearV3的ORM组件进行数据持久化的过程。

代码

本教程演示创建的所有工程和代码,包含于可以从sf.net下载的NBearV3最新源码zip包中的tutorials/IoC_Tutorial目录中。因此,在使用本教程的过程中如有任何疑问,可以直接参考这些代码。

时间

<45分钟。

正文

Step 1 下载NBearV3最新版本

1.1 访问http://sf.net/projects/nbear,下载NBearV3的最新版本到本地目录。

1.2 将下载的zip文件解压至C:/,您将看到,加压后的NBearV3目录中包括:dist、doc、cases、src等目录。其中,在本教程中将会使用的是dist目录,该目录下包含所有release编译版本的dll和exe。

Step 2 创建应用程序解决方案

2.1 打开VS2005开发环境,新建一个空的解决方案sln。

2.2 向sln中添加两个新建的C#类库工程,两个类库工程的名称分别为EntityDesigns和Entities,删除IDE自动创建的Class1.cs文件。

2.3 向sln中再添加两个新建的C#类库工程,两个类库工程的名称分别为ServiceInterfaces和ServiceImpls,删除IDE自动创建的Class1.cs文件。

2.4 向sln中新建一个名叫website的ASP.NET Web应用程序,为website添加一个Web.config文件。

Step 3 设计实体、关系及元数据

3.1 运行dist/ NBear.Tools.DbToEntityDesign.exe,在Connection String文本框中输入下面的连接子串:

Server=(local);Database=Northwind;Uid=sa;Pwd=sa

我们将从SQL Server 2000自带的演示数据库Northwind,为我们需要的某些表和视图生成设计实体。

点击Connect按钮,连接上数据库后,从左边的Tables和Views列表中分别选择下面这些表或视图(因为这里主要是演示,我们只选择和Category和Product相关的几个表和视图):Categories, Products, Products by Category。点击Generate Entities Design按钮,在代码生成文本框内的任意位置右键单击鼠标,并选择Copy to ClipBoard,这将能把所有的代码复制到剪贴板。

3.2 为EntityDesigns工程添加到dist目录下的NBear.Common.Design.dll的引用,因为每一个设计实体接口必须继承自NBear.Common.Design.Entity这个接口。在EntitiyDesigns工程中新建一个代码文件EntityDesigns.cs,添加using System和using NBear.Common.Design设置namespace为EntityDesigns。并将刚才从DbToEntityDesign复制的代码粘贴至该文件中。

此时,EntityDesigns.cs文件中的内容应该象下面这样:

using  System;
using  NBear.Common.Design;

namespace  EntityDesigns
{
    
public interface Categories : Entity
    
{
        [PrimaryKey]
        
int CategoryID get; }
        [SqlType(
"nvarchar(15)")]
        
string CategoryName getset; }
        [SqlType(
"ntext")]
        
string Description getset; }
        
byte[] Picture getset; }
    }


    
//
}

您会注意到,生成的代码中,数据库中的表或视图名称对应设计实体的名称,字段名称对应设计实体的属性名称,如果名称中包含空格,空格会被转换为_nbsp_。同时,是主键的标字段对应的属性,会自动包含PrimaryKey这个Attribute,而所有的文本类型的字段,则会自动包含从数据库中继承的数据类型和长度。另外,所有从视图生成的实体接口会包含ReadOnly这个Attribute,代表,该实体是一个只能用来从数据库取数据,不能用来保存数据到数据库的实体。

3.3 下面,我们可以对这些生成的代码做一下改造,让我们看着更舒服。比如,用_nbsp_代表空格多少影响视觉审美,我们可以给设计实体添加MappingName这个Attribute,来修改实体接口名称,但是,保证实体还是对应数据库中的这个表或视图。例如,对于Products_nbsp_by_nbsp_Category视图,我们可以将它修改为下面的样子:

    [ReadOnly]
    [MappingName(
" Products by Category " )]
    
public   interface  ProductsByCategory : Entity
    
{
        [SqlType(
"nvarchar(15)")]
        
string CategoryName get; }
        [SqlType(
"nvarchar(40)")]
        
string ProductName get; }
        [SqlType(
"nvarchar(20)")]
        
string QuantityPerUnit get; }
        
short UnitsInStock get; }
        
bool Discontinued get; }
    }

我们可能同样不喜欢实体名称是复数的英文单词,所以,我们也可以象下面这样把Categories实体的名称改为Category:

    [MappingName( " Categories " )]
    
public   interface  Category : Entity
    
{
        [PrimaryKey]
        
int CategoryID get; }
        [SqlType(
"nvarchar(15)")]
        
string CategoryName getset; }
        [SqlType(
"ntext")]
        
string Description getset; }
        
byte[] Picture getset; }
    }

我们可以用同样的方法,修改所有的实体名称,和属性名称。属性名称同样支持MappingName这个Attribute。我们将所有的实体名称的复数改为单数,并去掉_nbsp_。现在,所有的设计实体应该象下面这个样子:

using  System;
using  NBear.Common.Design;

namespace  EntityDesigns
{
    [MappingName(
"Categories")]
    
public interface Category : Entity
    
{
        [PrimaryKey]
        
int CategoryID get; }
        [SqlType(
"nvarchar(15)")]
        
string CategoryName getset; }
        [SqlType(
"ntext")]
        
string Description getset; }
        
byte[] Picture getset; }

        [Query(Where
="{CategoryID} = @CategoryID", OrderBy="{ProductName}", LazyLoad=true)]
        [Contained]
        Product[] Products
        
{
            
get;
            
set;
        }

    }


    [MappingName(
"Products")]
    
public interface Product : Entity
    
{
        [PrimaryKey]
        
int ProductID get; }
        [SqlType(
"nvarchar(40)")]
        
string ProductName getset; }
        
int SupplierID getset; }
        
int CategoryID getset; }
        [SqlType(
"nvarchar(20)")]
        
string QuantityPerUnit getset; }
        
decimal UnitPrice getset; }
        
short UnitsInStock getset; }
        
short UnitsOnOrder getset; }
        
short ReorderLevel getset; }
        
bool Discontinued getset; }

        [Query(Where
="{CategoryID} = @CategoryID", LazyLoad=false)]
        Category Category
        
{
            
get;
            
set;
        }

    }


    [ReadOnly]
    [MappingName(
"Products by Category")]
    
public interface ProductsByCategory : Entity
    
{
        [SqlType(
"nvarchar(15)")]
        
string CategoryName get; }
        [SqlType(
"nvarchar(40)")]
        
string ProductName get; }
        [SqlType(
"nvarchar(20)")]
        
string QuantityPerUnit get; }
        
short UnitsInStock get; }
        
bool Discontinued get; }
    }

}

3.4 实体和属性名称我们改造完了,下面还可以给设计实体添加一点关联。我们可以注意到,Category和Product是一个明显的1对多关联。因此,我们可以像下面这样,为Category实体添加一个Products属性,1对多关联到Product表。

    [MappingName( " Categories " )]
    
public   interface  Category : Entity
    
{
        [PrimaryKey]
        
int CategoryID get; }
        [SqlType(
"nvarchar(15)")]
        
string CategoryName getset; }
        [SqlType(
"ntext")]
        
string Description getset; }
        
byte[] Picture getset; }

        [FkQuery(
"Category", OrderBy="{ProductName}", Contained=true, LazyLoad=true)]
        Product[] Products
        
{
            
get
;
            
set
;
        }

    }

如果您看过之前的ORM教程,您应该能理解加粗的代码的意思。它表示Category实体的CategoryID属性和Product实体的CategoryID关联,Products属性的Product按ProductName正序排序,同时,该属性延迟载入(即到第一次访问该属性才载入)。Contained同时代表Products属性在Category保存或删除时,会自动级联保存或删除。

3.5 我们同时也可以给Product添加到Category的引用,因为,在查看一个Product信息时,查看相关的Category是非常常见的。注意,此时我们可以删掉Product中原来的CategoryID属性,将它合并到Category属性中:

 

    [MappingName( " Products " )]
    
public   interface  Product : Entity
    
{
        [PrimaryKey]
        
int ProductID get; }
        [SqlType(
"nvarchar(40)")]
        
string ProductName getset; }
        
int SupplierID getset; }
        [SqlType(
"nvarchar(20)")]
        
string QuantityPerUnit getset; }
        
decimal UnitPrice getset; }
        
short UnitsInStock getset; }
        
short UnitsOnOrder getset; }
        
short ReorderLevel getset; }
        
bool Discontinued getset; }

        [FkReverseQuery(LazyLoad 
= true)]
        [MappingName(
"CategoryID"
)]
        Category Category
        
{
            
get
;
            
set
;
        }

    }

注意,我们没有添加ContainedAttribute,因为,我们并不希望一个Category跟随他的一个Product的更新级联更新。同时,我们将Category属性设置为非延迟载入(即实例化Product的同时就载入Category属性的数据)。

这里要特别引起注意的是,当两个实体互相关联,或者,多个实体循环关联时,千万注意不要同时将互相关联的属性全都设为LazyLoad=fasle,否则将可能导致循环载入,造成程序死循环。在NBearV3的后续有版本将会引入缓存机制来解决这个问题,但是在这之前,请大家自己注意小心避免。

Step 4 从实体设计代码生成实体代码、实体配置文件

4.1 至此,实体的设计就完毕了。编译EntityDesigns工程。下面我们将从设计实体生成实际的实体代码和配置文件。注意,这里和之前的ORM教程不同的是,我们不生成数据库创建脚本,而直接使用一个已经存在的数据库Northwind。

4.2 运行dist目录中的NBear.Tools.EntityDesignToEntity.exe工具,载入EntityDesigns工程编译生成的EntityDesigns.dll。

4.3 点击Generate Entities按钮,将生成的代码保存到Entities工程中的一个名叫Entities.cs的新代码文件。并为Entities工程添加到dist/NBear.Common.Common.dll的引用。

4.4 点击Generate Configuration按钮,将生成的代码保存到website工程下的名为EntityConfig.xml的新文件中。

Step 5 使用实体及NBear.Data.Gateway访问数据库

5.1 现在我们就可以使用前面生成的实体了。我们先要让website工程引用Entities工程,以及dist/NBear.Data.dll。

5.2 我们还需要设置website的Web.config文件,添加一个entityConfig section以包含EntityConfig.xml这个实体配置文件,并设置数据库连接字串。下面是设置完的Web.config,注意,粗体的部分都是我们添加的代码(注意修改数据库登录密码。):

<? xml version="1.0" ?>
< configuration >
 
< configSections >
    
< section  name ="entityConfig"  type ="NBear.Common.EntityConfigurationSection, NBear.Common"   />
 
</ configSections >
 
< entityConfig >
    
< includes >
      
< add  key ="Sample Entity Config"  value ="~/EntityConfig.xml"   />
    
</ includes >
 
</ entityConfig >
 
< appSettings />
 
< connectionStrings >
    
< add  name =" Northwind"  connectionString ="Server=(local);Database=Northwind;Uid=sa;Pwd=sa"  providerName ="NBear.Data.SqlServer.SqlDbProvider" />
 
</ connectionStrings >
 
< system .web >
        
< compilation  debug ="false"   />
        
< authentication  mode ="Windows"   />
 
</ system.web >
</ configuration >

5.3 好了,到目前为止,实体设置和配置完毕了。下面我们将开始讨论IoC模块的使用。

Step 6 定义Service接口和Service实现

6.1 下面我们开始定义一个基于NBear.IoC的Service。我们先要为ServiceInterfaces工程添加到dist/NBear.Common.dll和dist/NBear.IoC.dll的引用。一个Service由一个接口定义。我们这个Service的功能很简单,就是我们想获得一些需要的Category和Product。所以,我们还需要为ServiceInterfaces工程添加到Entities工程的引用。在ServiceInterfaces工程中定义接口ICategoryService和IProductService如下:

using  System;
using  NBear.IoC.Service;
using  Entities;

namespace  ServiceInterfaces
{
    
public interface ICategoryService : IService
    
{
        Category[] GetAllCategories();
        Category GetCategoryByID(
int categoryID);
    }

}

using  System;
using  NBear.IoC.Service;
using  Entities;

namespace  ServiceInterfaces
{
    
public interface IProductService
    
{
        Product[] GetAllProducts();
        Product GetProductByID(
int productID);
    }

}

注意,基于NBear.IoC的Service,必须从NBear.IoC.Service.IServiceInterface这个接口继承

6.2 定义完Service接口,我们还需要实现它。在ServiceImpls工程中,添加到Entities,ServiceInterfaces和到dist/NBear.Common.dll,dist/NBear.Data.dll和dist/NBear.IoC.dll的引用,分别实现这两个接口如下:

using  System;
using  NBear.Common;
using  NBear.Data;
using  Entities;
using  ServiceInterfaces;

namespace  ServiceImpls
{
    
public class CategoryService : ICategoryService
    
{
        
ICategoryService Members
    }

}

using  System;
using  NBear.Common;
using  NBear.Data;
using  Entities;
using  ServiceInterfaces;

namespace  ServiceImpls
{
    
public class ProductService : IProductService
    
{
        
IProductService Members
    }

}

 

Step 7 配置Service,使用ServiceFactory,调用Service

7.1 编译ServiceImpls。我们就可以准备在website中使用Service了。为website添加到Entities、ServiceInterfacs、dist/NBear.Common.dll、dist/NBear.IoC.dll的引用。

注意,这里无需为website添加到ServiceImpls的引用。想想为什么?如果website依赖ServiceImpls的实现代码,那么就不叫IoC了。IoC的核心是基于容器的依赖注入。换句话说,我们只需要在配置文件中指定一个Service的接口和对应的实现类的位置,从而使得Service的调用者与Service的实现者松散耦合。但是,为了能让我们的website能根据配置文件找到Service的实现类,我们还是需要复制ServiceImpls编译输出的ServiceImpls.dll到website的Bin目录中

7.2 接着,我们需要在Web.config中配置IoC容器。NBearV3的IoC组件使用castle作为IoC容器,因此,可以使用标准的castle配置与法进行配置。不过一般,只需要使用下面这样最简单的语法就行了:

<? xml version="1.0" ?>
< configuration >
    
< configSections >
        
< section  name ="entityConfig"  type ="NBear.Common.EntityConfigurationSection, NBear.Common" />
       
<section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
   
</ configSections >
    
< entityConfig >
        
< includes >
            
< add  key ="Sample Entity Config"  value ="~/EntityConfig.xml" />
        
</ includes >
    
</ entityConfig >
  
<castle>
    
<components>
      
<!--You can use standard castle component decleration schema to define service interface impls here-->
      
<component id="category service" service="ServiceInterfaces.ICategoryService, ServiceInterfaces" type="ServiceImpls.CategoryService, ServiceImpls"/>
      
<component id="product service" service="ServiceInterfaces.IProductService, ServiceInterfaces" type="ServiceImpls.ProductService, ServiceImpls"/>
    
</components>
  
</castle>
  
< appSettings />
    
< connectionStrings >
        
< add  name ="Northwind"  connectionString ="Server=(local);Database=Northwind;Uid=sa;Pwd=sa"  providerName ="NBear.Data.SqlServer.SqlDbProvider" />
    
</ connectionStrings >
    
< system .web >
        
< compilation  debug ="true" >
            
< assemblies >
                
< add  assembly ="System.Transactions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
                
< add  assembly ="System.Data.OracleClient, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
                
< add  assembly ="System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" /></ assemblies ></ compilation >
        
< authentication  mode ="Windows" />
    
</ system.web >
</ configuration >

注意加粗的部分,这里我们添加了一个castle的配置块,并且配置了两个我们定义的Service和他们对应的实现。 

7.3 接着,在Default.aspx.cs文件中的PageLoad中,添加下面的代码,访问Service。

注意,所有的Service只需简单地从ServiceFactory.GetService<ServiceType>()得到。然后就能使用了。

     protected   void  Page_Load( object  sender, EventArgs e)
    
{
        ServiceFactory factory 
= ServiceFactory.Create();

        IProductService ps 
= factory.GetService<IProductService>();
        Product[] products 
= ps.GetAllProducts();
        WriteLine(
string.Format("Got all products, {0} in total.", products.Length));

        ICategoryService cs 
= factory.GetService<ICategoryService>();
        Category[] categories 
= cs.GetAllCategories();
        WriteLine(
string.Format("Got all categories, {0} in total.", categories.Length));

        WriteLine(
"In each category:");
        
foreach (Category item in categories)
        
{
            WriteLine(
string.Format("ID={0}, Name={1}, Products in category: {2}.", item.CategoryID, item.CategoryName, item.Products.Length));
        }

    }


    
private   void  WriteLine( string  str)
    
{
        Response.Write(Server.HtmlEncode(str) 
+ "<br /><br />");
    }

正文结束。

附录

1 关于ServiceFactory.Create()

在website中,您一定注意到,我们使用NBear.IoC.Service.ServiceFactory.Create()方法获得了ServiceFactory实例,并通过他获得Service接口的实现类实例。

在实际的项目中,您也无需在一个统一的地方定义全局的ServiceFactory实例引用,可以在每一个需要ServiceFactory实例的地方直接调用ServiceFactory.Create(),因为ServiceFactory.Create()内部实际上使用了Singleton模式,它总是返回的唯一的一个ServiceFactory实例

同时,除了在website中通过ServiceFactory访问service之外,在某一个Service的实现代码中,也可以访问ServiceFactory.Create(),从而访问另一个同样在Web.config的castle块中配置的service。这样,当不同的Service实现程序集之间互相调用Service时,只需要互相引用Service Interfaces,Service的实现代码互相就能避免任何依赖,从而将模块间的耦合度降至最低。

//本文结束

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值