NBearV3 Step by Step教程——IoC篇

版本

1.2 [2006-11-12]

简介

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

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

目标

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

代码

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

时间

<45分钟。

正文

Step 1 下载NBearV3最新版本

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

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

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

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

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

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

2.4 sln中新建一个名叫websiteASP.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按钮,连接上数据库后,从左边的TablesViews列表中分别选择下面这些表或视图(因为这里主要是演示,我们只选择和CategoryProduct相关的几个表和视图):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 Systemusing NBear.Common.Design设置namespaceEntityDesigns。并将刚才从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 实体和属性名称我们改造完了,下面还可以给设计实体添加一点关联。我们可以注意到,CategoryProduct是一个明显的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属性的ProductProductName正序排序,同时,该属性延迟载入(即到第一次访问该属性才载入)。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 我们还需要设置websiteWeb.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.IoCService。我们先要为ServiceInterfaces工程添加到dist\NBear.Common.dlldist\NBear.IoC.dll的引用。一个Service由一个接口定义。我们这个Service的功能很简单,就是我们想获得一些需要的CategoryProduct。所以,我们还需要为ServiceInterfaces工程添加到Entities工程的引用。在ServiceInterfaces工程中定义接口ICategoryServiceIProductService如下:

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.IoCService,必须从NBear.IoC.Service.IServiceInterface这个接口继承

6.2 定义完Service接口,我们还需要实现它。在ServiceImpls工程中,添加到EntitiesServiceInterfaces和到dist\NBear.Common.dlldist\NBear.Data.dlldist\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添加到EntitiesServiceInterfacsdist\NBear.Common.dlldist\NBear.IoC.dll的引用。

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

7.2 接着,我们需要在Web.config中配置IoC容器。NBearV3IoC组件使用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.configcastle块中配置的service。这样,当不同的Service实现程序集之间互相调用Service时,只需要互相引用Service InterfacesService的实现代码互相就能避免任何依赖,从而将模块间的耦合度降至最低。

//本文结束

原文地址:https://www.cnblogs.com/teddyma/p/551558.html