代码改变世界

基于 Entity Framework FluentAPI 思想的强类型的 Lambda Expressions 实体映射

2013-02-06 10:36  音乐让我说  阅读(1612)  评论(2编辑  收藏  举报

可能你还感兴趣:

1. C# Lambda 表达式学习之(一):得到一个类的字段(Field)或属性(Property)名,强类型得到

2. C# Lambda 表达式学习之(二):LambdaExpression 实战练习

3. 基于 Entity Framework FluentAPI 思想的强类型的 Lambda Expressions 实体映射

4. C# Lambda 表达式学习之(三):动态构建类似于 c => c.Age == null || c.Age > 18 的表达式 

5. C# Lambda 表达式学习之(四):动态构建类似于 c => c.Age == 2 || c.Age == 5 || c => c.Age == 17 等等一个或多个 OrElse 的表达式

 

最近在优化一套自己以前的通用数据层访问的小“架构”,主要是根据数据库中的表来建立对应的 Repository, 起初 Entities 层的每个类名、属性名都要跟数据库中的表名、字段名一模一样,比如数据库中有一个 Category 表,里面有 Id、CateName 两个字段,那么建立的 C# 实体也必须是 :

/// <summary>
/// 产品种类
/// </summary>
public partial class Category
{
    public Category()
    {

    }

    /// <summary>
    /// 种类的 Id
    /// </summary>
    public int Id { get; set; }

    /// <summary>
    /// 种类名称
    /// </summary>
    public string CateName { get; set; }
}

后来,同事说这样可能不是很好, 很有可能数据库中的表名、字段名不与 C# 实体完全相同,能不能像 Entity Framework CodeFirst 中一样,通过建立 Map 文件来映射,我仔细想了想,觉得有道理,后来就有了下面这样的改进,基本上算实现了,且往下看。
1. 不管数据库中的表名和字段名怎样,我们先建立一个 Category 类。

/// <summary>
/// 产品种类
/// </summary>
public partial class Category
{
    public Category()
    {

    }

    /// <summary>
    /// 种类的 Id
    /// </summary>
    public int Id { get; set; }

    /// <summary>
    /// 种类名称
    /// </summary>
    public string CateName { get; set; }
}

2. 建立一个 CategoryMap 类,继承自 EntityMappingConfiguration<Category> 类。

关于 EntityMappingConfiguration<T> 类,后面会介绍。

 

/// <summary>
/// 产品种类表与数据库中的表的映射关系配置
/// 规则01: 该类中可以设置表名(TableName)、主键列(HasKey)、属性与数据库中列的映射(MapProperty)。
/// 规则02: 可以不设置表名,如果不设置则默认实体的类名就是数据库中的表名。
/// 规则03: 可以不指定主键列,默认会以 Id 或 [类名+Id] 的列作为主键,如果都不存在,则抛出异常。
///         如果同时存在 Id 和 [类名+Id] 字段,则以 Id 作为主键。
/// 规则04: 可以不指定属性与数据库中列的映射,如果不设置则默认实体的属性名就是数据库中表的列名。
/// </summary>
public class CategoryMap : EntityMappingConfiguration<Category>
{
    public CategoryMap()
    {
        this.TableName = "MyCategory";
        this.HasKey(c => c.Id);
        this.MapProperty(c => c.Id, "myId");
        this.MapProperty(c => c.CateName, "myCateName");
    }
}

PS:可以不建立 CategoryMap 类,如果不建立,那么默认 Category 类对应数据库中的 Category 表,且字段 Id、CateName 和数据库中一模一样。

3. 在 Global.asax 中的 Application_Start 事件中注册它

public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        GlobalEntityMappingHelper.RegisterEntityMapping();
    }
}

public class GlobalEntityMappingHelper
{
        
    public static void RegisterEntityMapping()
    {
        new CategoryMap();
        // 下面可以注册其它的实体配置...
        // ...
    }
}

PS:一定要注册 CategoryMap 类,要不然不会生效,这和 Entity Framework FluentAPI 思想保持一样。

4. 这样就可以在相应的地方使用它了,建立一个 index.aspx 的文件,在后台代码中:

protected void Page_Load(object sender, EventArgs e)
{
    if(!IsPostBack)
    {
        InitData();
    }
}

protected void InitData()
{
    string tableName = EntityMappingConfiguration<Category>.GetTableName();
    string primaryKeyName = EntityMappingConfiguration<Category>.GetPrimaryKeyName();
    string cateName = EntityMappingConfiguration<Category>.GetProperty(c => c.CateName);

    this.ltMessage.Text = string.Format(@"数据库表名:{0}<br/>
        数据库主键列:{1}<br/>
        属性 ""CateName"" 所对应的数据库列名:{2}<br/>",
        tableName, primaryKeyName, cateName);
}

效果如下:

附录:EntityMappingConfiguration<T> 的代码如下:

 

/// <summary>
/// 实体映射配置基类
/// </summary>
/// <typeparam name="TEntityType">实体的类型</typeparam>
public abstract class EntityMappingConfiguration<TEntityType>
    where TEntityType : class, new()
{
    private static readonly Dictionary<string, string> _dicTableNames = new Dictionary<string, string>();
    private static readonly Dictionary<string, string> _dicPrimaryKey = new Dictionary<string, string>();
    private static readonly Dictionary<string, string> _dicArrayMember = new Dictionary<string, string>();
    private const string SEPARATOR = "=";

    #region private or protected 方法

    /// <summary>
    /// 设置该实体类与之相对应的数据库中的表名
    /// </summary>
    protected string TableName
    {
        set
        {
            if(string.IsNullOrEmpty(value))
            {
                throw new ArgumentNullException("赋予的表名不能为空!");
            }
            string className = typeof(TEntityType).FullName;
            _dicTableNames.Add(className, value);
        }
    }

    /// <summary>
    /// 设置主键列与之对应实体的属性
    /// </summary>
    /// <typeparam name="TKey">主键列的类型</typeparam>
    /// <param name="keyExpression">指定主键列的 Lambda 表达式</param>
    protected void HasKey<TKey>(Expression<Func<TEntityType, TKey>> keyExpression)
    {
        if (keyExpression == null)
        {
            throw new ArgumentNullException();
        }
        MemberExpression memberExpression;
        InspectIsMemberExpression<TKey>(keyExpression, true, out memberExpression);
        string className = typeof(TEntityType).FullName;
        _dicPrimaryKey.Add(className, GetCacheKey(memberExpression));
    }

    /// <summary>
    /// 设置数据表中的列与之对应实体的属性
    /// </summary>
    /// <typeparam name="TProperty">属性的类型</typeparam>
    /// <param name="propertyExpression">指定实体中的属性的 Lambda 表达式</param>
    /// <param name="dbColumnName">数据表的列名</param>
    protected void MapProperty<TProperty>(Expression<Func<TEntityType, TProperty>> propertyExpression, string dbColumnName)
    {
        if (propertyExpression == null)
        {
            throw new ArgumentNullException();
        }
        if (string.IsNullOrEmpty(dbColumnName))
        {
            throw new ArgumentNullException();
        }
        MemberExpression memberExpression;
        InspectIsMemberExpression<TProperty>(propertyExpression, true, out memberExpression);
        _dicArrayMember.Add(GetCacheKey(memberExpression), dbColumnName);
    }

    #endregion

    #region 静态方法

    private static bool InspectIsMemberExpression<TProperty>(Expression<Func<TEntityType, TProperty>> express, bool throwExOnNull, out MemberExpression memberExpression)
    {
        if (express == null)
        {
            throw new ArgumentNullException("express");
        }
        memberExpression = express.Body as MemberExpression;
        if (memberExpression == null)
        {
            if (throwExOnNull)
            {
                throw new ArgumentException("请为类型 \"" + typeof(TEntityType).FullName + "\" 的指定一个字段(Field)或属性(Property)作为 Lambda 的主体(Body)。");
            }
            return false;
        }
        return true;
    }

    private static string GetCacheKey(PropertyInfo pInfo)
    {
        return GetCacheKey(pInfo.DeclaringType.FullName, pInfo.PropertyType.FullName, pInfo.Name);
    }

    private static string GetCacheKey(MemberExpression memberExpression)
    {
        return GetCacheKey(memberExpression.Member.DeclaringType.FullName, memberExpression.Type.FullName, memberExpression.Member.Name);
    }

    private static string GetCacheKey(string classFullName, string propertyFullName, string propertyName)
    {
        return classFullName + SEPARATOR + propertyFullName + SEPARATOR + propertyName;
    }


    /// <summary>
    /// 得到实体的属性所对应的数据库中表的列名
    /// </summary>
    /// <typeparam name="TProperty">属性的类型</typeparam>
    /// <param name="propertyExpression">指定实体中的属性的 Lambda 表达式</param>
    /// <returns></returns>
    public static string GetProperty<TProperty>(Expression<Func<TEntityType, TProperty>> propertyExpression)
    {
        if (propertyExpression == null)
        {
            throw new ArgumentNullException();
        }
        MemberExpression memberExpression;
        InspectIsMemberExpression<TProperty>(propertyExpression, true, out memberExpression);
        string dbColumnName;
        _dicArrayMember.TryGetValue(GetCacheKey(memberExpression), out dbColumnName);
        if (string.IsNullOrEmpty(dbColumnName))
        {
            return memberExpression.Member.Name;
            //throw new NotSupportedException(string.Format("您还没有给类 \"{0}\" 的属性 \"{1}\" 执行 Map 操作!", typeof(TEntityType).FullName, memberExpression.Member.Name));
        }
        return dbColumnName;
    }

    /// <summary>
    /// 得到表名
    /// </summary>
    /// <returns></returns>
    public static string GetTableName()
    {
        Type currentType = typeof(TEntityType);
        string classFullName = currentType.FullName;
        string mappingConfigTableName;
        _dicTableNames.TryGetValue(classFullName, out mappingConfigTableName);
        if (string.IsNullOrEmpty(mappingConfigTableName))
        {
            return currentType.Name;
            //throw new NotSupportedException(string.Format("您还没有给类 \"{0}\" 设置 TableName 属性!", classFullName));
        }
        return mappingConfigTableName;
    }

    private static PropertyInfo GetDefaultPrimaryKey(Type currentType)
    {
        PropertyInfo pInfo = currentType.GetProperty("Id");
        return pInfo ?? currentType.GetProperty(currentType.Name + "Id");
    }

    /// <summary>
    /// 得到主键列名
    /// </summary>
    /// <returns></returns>
    public static string GetPrimaryKeyName()
    {
        Type currentType = typeof(TEntityType);
        string classFullName = currentType.FullName;
        string mappingConfigKey;
        _dicPrimaryKey.TryGetValue(classFullName, out mappingConfigKey); // 尝试得到主键 Property
        if (string.IsNullOrEmpty(mappingConfigKey))
        {
            PropertyInfo pInfo = GetDefaultPrimaryKey(currentType);
            if (pInfo == null)
            {
                throw new NotSupportedException(string.Format("您还没有给类 \"{0}\" 执行 HasKey 操作!", classFullName));
            }
            mappingConfigKey = GetCacheKey(pInfo);
            _dicPrimaryKey.Add(classFullName, mappingConfigKey);
        }
        string dbColumnName;
        _dicArrayMember.TryGetValue(mappingConfigKey, out dbColumnName); // 尝试得到主键 Property 所配置的数据库字段的名称
        if (string.IsNullOrEmpty(dbColumnName))
        {
            // 为空说明没有配置,那么就取这个主键 Property 的名称
            dbColumnName = mappingConfigKey.Split(new[] { SEPARATOR }, StringSplitOptions.None)[2];
            _dicArrayMember.Add(mappingConfigKey, dbColumnName);
            //string[] temp = mappingConfigKey.Split(new[] { SEPARATOR }, StringSplitOptions.None);
            //throw new NotSupportedException(string.Format("您还没有给类 \"{0}\" 的主键列 \"{1} {2}\" 执行 MapProperty 操作!", classFullName, temp[1], temp[2]));
        }
        return dbColumnName;
    }

    #endregion

}

 快过年了,提前给大家拜年,祝大家新年快乐,恭喜发财,哈哈哈。。。。

谢谢浏览!