LLBL Gen Pro是个专业的ORM开发工具,官方网站是 http://www.llblgen.com/

LLBL Gen Pro是个支持多种持久层框架的ORM工具,如LLBL Gen Pro Runtime、Entity Framework、NHibernate和LINQ to SQL。其他一些新特性还有:

支持.NET 4.0、模型先行或数据库先行的开发模式、模型视图、项目验证。LLBL Gen Pro有两个主要的组件:一是设计器,

这是一个可视化工具,供开发者创建项目所用;二是运行时,这是与数据库交互的持久层框架,用于执行映射操作。

1 设计实体类型

打开LLBL Gen程序,选择创建新项目

LLBL Gen Pro 设计器使用指南

打开Catelog Explorer,选择从数据库添加关系对象模型

LLBL Gen Pro 设计器使用指南

输入SQL Server实例名称,登陆用户和秘密,点”Test Connection”测试连接

LLBL Gen Pro 设计器使用指南

测试连接成功后,点击Next按钮,进行到下一步,选择数据库表

LLBL Gen Pro 设计器使用指南

如果需要支持存储过程调用,可选择勾选存储过程

LLBL Gen Pro 设计器使用指南

LLBL Gen开始读取系统表信息,供生成实体定义。

在Catelog Explorer点选右键,选择从表定义中生成实体

LLBL Gen Pro 设计器使用指南

如果表名带有简写,可以输入一个有意义容易理解的实体名称

LLBL Gen Pro 设计器使用指南

LLBL Gen不建议的命名方法

1. 在表名前加前缀。比如给客户表名定义成tblCustomer, 用户表定义成tblUser,只要是表都加一个tbl标签。如果是手写SQL语句,这可以区分出表和自定义的实体,但是ORM会对每个生成的实体名称后面加Entity,比如Employee表,默认会对生EmployeeEntity。

为此,LLBL Gen设计了名称剔除(strip)模式,请看下图中的配置属性

LLBL Gen Pro 设计器使用指南

2. 同样的原理,存储过程也不应该加sp_前缀,视图也不应该加vw_。

2 修改实体属性

在Project Exploer窗口中,选择要修改的实体名,点击编辑

LLBL Gen Pro 设计器使用指南

默认情况下,不需要做修改,LLBL Gen会生成合适的数据库与实体映射。但是有以下几种情况需要修改。

2.1 空值类型

数据库字段值允许为空,对应生成的代码类型应该是NULLABLE,通常看到的可空类型。

生成的代码例子

/// <summary> The CreatedDate property of the Entity User<br/><br/>

///Mapped on table field: User.CreatedDate</summary>

/// <remarks>Mapped on table field: "User"."CreatedDate"<br/>

/// Table field type characteristics (type, precision, scale, length): DateTime, 0, 0, 0<br/>

/// Table field behavior characteristics (is nullable, is PK, is identity): true, false, false</remarks>

public virtual Nullable<System.DateTime> CreatedDate

{

get { return (Nullable<System.DateTime>)GetValue((int)UserFieldIndex.CreatedDate, false); }

set { SetValue((int)UserFieldIndex.CreatedDate, value); }

}

如果不想生成这种类型,可在Field mapping窗口中,选择对应的属性名称,去掉Is nullable标签。

LLBL Gen Pro 设计器使用指南

再次生成代码,它生成的属性应该是这样的

/// <summary> The CreatedDate property of the Entity User<br/><br/>

///Mapped on table field: User.CreatedDate</summary>

/// <remarks>Mapped on table field: "User"."CreatedDate"<br/>

/// Table field type characteristics (type, precision, scale, length): DateTime, 0, 0, 0<br/>

/// Table field behavior characteristics (is nullable, is PK, is identity): true, false, false</remarks>

public virtual System.DateTime CreatedDate

{

get { return (Nullable<System.DateTime>)GetValue((int)UserFieldIndex.CreatedDate, false); }

set { SetValue((int)UserFieldIndex.CreatedDate, value); }

}

2.2 自定义属性

LLBL Gen支持为实体增加自定义属性,独立于数据库字段映射之外的属性,相当于代码生成器。

LLBL Gen Pro 设计器使用指南

在生成代码中,会产生额外的属性,它的类型是Dictionary

private static void SetupCustomPropertyHashtables()

{

_customProperties = new Dictionary<string, string>();

_fieldsCustomProperties = new Dictionary<string, Dictionary<string, string>>();

Dictionary<string, string> fieldHashtable;

_fieldsCustomProperties.Add("UserName", fieldHashtable);

fieldHashtable = new Dictionary<string, string>();

fieldHashtable.Add("AllowEditForNewOnly", @"");

}

在运行时,可通过下面的方法,找到自定义属性

// [C#]

Dictionary<string, string> userProperties = UserEntity.CustomProperties;

string description = userProperties ["AllowEditForNewOnly "];

2.3 类型转化器

如果需要对值进行类型转化。比如数据库中存储的是字符串,但要在程序中以bool类型来使用,可设置TypeConverter to use

LLBL Gen Pro 设计器使用指南

强类型编程的好处是可以发现编译时错误,而不是到运行才出现错误。

举例说明,我们常用1或0表示销售单已经过帐或没有过帐,但是在程序中以1或0来设置,缺少编译期间的检查,即使你赋值时给它值3,也不会有编译错误。如果换成bool类型,true表是已经过帐,false表示没有过帐,则可以极大的增加可维护性。

请参考如下的代码,了解类型转换器,进一步的原理请参考.NET组件设计。

Namespace TypeConverters
{
    [Description("Converter with as core type System.Boolean, for mapping a field with a .NET type System.Boolean onto a string database field")]
    public class BooleanStringConverter : TypeConverter
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="BooleanStringConverter"/> class.
        /// </summary>
        public BooleanStringConverter()
        {
        }

        /// <summary>
        /// Returns whether this converter can convert an object of the given type to the type of this converter (Boolean).
        /// </summary>
        /// <param name="context">Ignored</param>
        /// <param name="sourceType">A <see cref="T:System.Type"/> that represents the type you want to convert from.</param>
        /// <returns>
        ///  <see langword="true "/>if this converter can perform the conversion; otherwise, <see langword="false"/>.
        /// </returns>
        /// <remarks>Accepted types are: String</remarks>
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            // any integer type is accepted. No fractional types like float/double.
            switch (sourceType.FullName)
            {
                case "System.String":
                    return true;
                default:
                    return false;
            }
        }

        /// <summary>
        /// Returns whether this converter can convert the object to the specified type.
        /// </summary>
        /// <param name="context">Ignored</param>
        /// <param name="destinationType">A <see cref="T:System.Type"/> that represents the type you want to convert to.</param>
        /// <returns>
        ///  <see langword="true "/>if this converter can perform the conversion; otherwise, <see langword="false"/>.
        /// </returns>
        /// <remarks>Accepted types are: String. True will be converted to 1, false will be
        /// converted to 0.</remarks>
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            // any integer type is accepted. No fractional types like float/double.
            switch (destinationType.FullName)
            {
                case "System.String":
                    return true;
                default:
                    return false;
            }
        }

        /// <summary>
        /// Converts the given object to the type of this converter (Boolean).
        /// </summary>
        /// <param name="context">Ignored</param>
        /// <param name="culture">Ignored</param>
        /// <param name="value">The <see cref="T:System.Object"/> to convert.</param>
        /// <returns>
        /// An <see cref="T:System.Object"/> that represents the converted value, which is of type boolean.
        /// </returns>
        /// <exception cref="T:System.NotSupportedException">The conversion could not be performed.</exception>
        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            bool toReturn = true;
            switch (value.GetType().FullName)
            {
                case "System.String":
                    toReturn = ((string)value == "Y");
                    break;
                case "System.DBNull":
                    toReturn = false;
                    break;
                default:
                    throw new NotSupportedException("Conversion from a value of type '" + value.GetType().ToString() + "' to System.Boolean isn't supported");
            }

            return toReturn;
        }

        /// <summary>
        /// Converts the given value object to the specified type
        /// </summary>
        /// <param name="context">Ignored</param>
        /// <param name="culture">Ignored</param>
        /// <param name="value">The <see cref="T:System.Object"/> to convert.</param>
        /// <param name="destinationType">The <see cref="T:System.Type"/> to convert the <paramref name="value"/> parameter to.</param>
        /// <returns>
        /// An <see cref="T:System.Object"/> that represents the converted value. The value will be 1 if <paramref name="value"/> is true, otherwise 0
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">The <paramref name="destinationType"/> parameter is <see langword="null"/>.</exception>
        /// <exception cref="T:System.NotSupportedException">The conversion could not be performed.</exception>
        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value", "Value can't be null");
            }

            if (!(value is bool))
            {
                throw new ArgumentException("Value isn't of type boolean", "value");
            }

            if ((bool)value)
            {
                switch (destinationType.FullName)
                {
                    case "System.String":
                        return (string)"Y";
                    case "System.DBNull":
                        return (string)"N";

                    default:
                        throw new NotSupportedException("Conversion to a value of type '" + destinationType.ToString() + "' isn't supported");
                }
            }
            else
            {
                switch (destinationType.FullName)
                {
                    case "System.String":
                    case "System.DBNull":
                        return (string)"N";
                    default:
                        throw new NotSupportedException("Conversion to a value of type '" + destinationType.ToString() + "' isn't supported");
                }
            }
        }

        /// <summary>
        /// Creates an instance of the Type that this <see cref="T:System.ComponentModel.TypeConverter"/> is associated with (bool)
        /// </summary>
        /// <param name="context">ignored.</param>
        /// <param name="propertyValues">ignored.</param>
        /// <returns>
        /// An <see cref="T:System.Object"/> of type bool. It always returns 'true' for this converter.
        /// </returns>
        public override object CreateInstance(ITypeDescriptorContext context, System.Collections.IDictionary propertyValues)
        {
            return true;
        }
    }
}

3 生成源代码

设置好属性后,可用生成源代码,F7调出生成窗口

LLBL Gen Pro 设计器使用指南

Template group中Adapter 模式把数据和数据的读写独立成二个部分,SelfServicing则混合两者

Adapter模式下,读取用户的代码,如下所示

UserLogEntity _UserLog = new UserLogEntity(Logno);

using (DataAccessAdapterBase adapter = GetSystemDataAccessAdapter())

{

bool found = adapter.FetchEntity(_UserLog, prefetchPath, null, fieldList);

if (!found) throw new RecordNotFoundException("Invalid UserLog");

}

SelfServicing模式,则是这样的

CustomerEntity customer = new CustomerEntity();

customer.FetchUsingPK("CHOPS");

实际过程中,生成器生成的代码和自己加入的业务逻辑分开存放,当数据库更新字段时,再次生成代码,为了不覆盖已经加入的代码,常常要把自己的逻辑放到代码生成器生成的代码之外。

LLBL Gen Pro 设计器使用指南

在我的项目中,我是将Database Generic和Database Specific两个项目的代码放在一起,在同一个项目中进行编译。

LLBL Gen Pro 设计器使用指南

框架中大量依赖于这个设置,对生成的实体代码进行反射,读取元数据。

 

4 MySQL 快速开发

4.1 MySQL 安装配置

安装MySQL, 使用查询分析器连接数据库服务器

LLBL Gen Pro 设计器使用指南

创建数据库CTU, 并且创建新表Agent

LLBL Gen Pro 设计器使用指南

给数据表Agent添加三笔数据,Jack,Tony,Charles

LLBL Gen Pro 设计器使用指南

数据库设计的任务到此为止,记得给每个表添加主键。这里设置的主键是Name

4.2 LLBL Gen 设计实体模型

启动LLBL Gen 3.1, 创建新的项目,注意选择Target为LLBL Gen框架

LLBL Gen Pro 设计器使用指南

在Category Explorer窗口中,右键,添加新连接

LLBL Gen Pro 设计器使用指南

请在context菜单中选择Reverse-engineer Tables to Entity Definitions

LLBL Gen Pro 设计器使用指南

这些表就会自动mapping到实体集合中

LLBL Gen Pro 设计器使用指南

从图中可以看到,已经发现了Agent表

如果需要进一步的编辑属性与字段的映射关系,请用Edit 菜单,打开窗体

LLBL Gen Pro 设计器使用指南

在这里,可以设置关系,设置映射的名称。

F7,可以开始生成代码

LLBL Gen Pro 设计器使用指南

默认情况下,会生成2个项目文件

LLBL Gen Pro 设计器使用指南

从名称中可以看出,一个是数据库无关的,另一个是做数据访问的。以此的原理是我们在生成数据库时,选择Adapter模式,而不是SelfServicing模式.

4.3 启动程序

在Visual Studio中打开生成的代码,并且添加测试项目(Console)

解决方案浏览器中现在有三个项目Main是我们用来测试数据访问的

LLBL Gen Pro 设计器使用指南

再来看一下Main方法,只有简单的几行程序,也就是一个最简单的测试程序

UserEntity user=new UserEntity("Jack")
DataAccessAdapter adapter=new DataAccessAdapter();
adapter.FetchEntity(user);
string title=user.Description;

它会生成SQL语句 SELECT Name , Postion , Description FROM agent

 

LLBL Gen支持NHibernate,ADO.NET Entity Framework,可以考虑用它来作为ORM框架,实现三层架构中的数据访问层。

相关文章: