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

本文介绍了一种自定义实体映射的方法,允许开发者在C#中灵活地映射数据库表与实体类之间的关系,从而实现更加灵活的数据访问层设计。通过创建Category类及其映射配置类CategoryMap,开发者可以避免实体类与数据库表名完全一致的需求,提高代码的适应性和维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可能你还感兴趣:

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

}

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

谢谢浏览!

转载于:https://www.cnblogs.com/Music/archive/2013/02/06/good-idea-custom-entity-mapping.html

<think>嗯,用户想了解Entity Framework Core中的Fluent API的用法和功能,需要教程、功能详解和示例。首先,我得回忆一下Fluent API在EF Core中的作用。Fluent API主要用于配置模型,相比数据注解,它更灵活,能处理更复杂的映射关系。比如设置主键、外键、索引,或者配置关系,如一对一、一对多等。 根据引用内容,引用[2]提到EF Core更注重Fluent API作为主要的模型定义方式,而引用[3]则给出了一个配置一对多关系的例子。可能需要先介绍Fluent API的基本概念,然后分点讲解常用配置功能,比如实体配置、属性配置、关系配置等,每个部分都要有示例代码。 用户可能需要知道如何在DbContext的OnModelCreating方法中使用modelBuilder,比如modelBuilder.Entity<Post>()这样的结构。然后每个配置项,如主键、列名、是否必需、最大长度等,都要用Fluent API的方法,比如.HasKey(), .Property(), .HasColumnName()等。 另外,关系配置部分比较复杂,需要详细说明HasOne、WithMany、HasForeignKey等方法的使用,可能还需要对比数据注解的方式,说明为什么Fluent API更强大。例如,配置复合主键只能用Fluent API,数据注解无法做到。 索引配置也是一个重点,比如.HasIndex()和.IsUnique(),可以提到EF Core支持索引过滤,比如在软删除中过滤已删除的记录。 还要注意代码示例的正确性,比如在引用[3]中的示例代码里,配置了一对多关系,但可能用户需要更多的例子,比如多对多关系的配置,或者继承策略的配置。 另外,用户可能需要了解Fluent API的优势,比如集中管理配置、避免污染实体类、支持复杂场景等,这些在引用[3]和引用[2]中都有提及。 需要确保回答结构清晰,分点列出,每个功能点配合示例代码,并引用相关的引用来源。比如在讲解索引时,可以引用[3]中的内容,说明EF Core支持Fluent API配置索引。 最后,生成相关问题要考虑用户可能的后续疑问,比如性能优化、与数据注解的比较、复杂关系配置等,参考之前的引用内容,比如引用[1]提到性能优化,可以生成相关问题。</think>### EntityFramework Core Fluent API 使用详解 #### 一、Fluent API 基础概念 Fluent API 是 EF Core 中用于配置实体类和数据库映射的核心工具,通过链式方法调用实现更灵活的模型定义。相较于数据注解,它能处理更复杂的场景(如复合主键、多对多关系)[^2]。 #### 二、核心配置功能及示例 1. **实体配置** ```csharp modelBuilder.Entity<Post>() .ToTable("BlogPosts") // 指定表名 .HasKey(p => p.PostId); // 定义主键 ``` 2. **属性配置** ```csharp modelBuilder.Entity<Post>() .Property(p => p.Title) .HasColumnName("PostTitle") // 自定义列名 .HasMaxLength(100) // 最大长度 .IsRequired(); // 非空约束 ``` 3. **关系配置** - **一对多**(博客-文章)[^3]: ```csharp modelBuilder.Entity<Blog>() .HasMany(b => b.Posts) .WithOne(p => p.Blog) .HasForeignKey(p => p.BlogId); ``` - **一对一**(用户-档案): ```csharp modelBuilder.Entity<User>() .HasOne(u => u.Profile) .WithOne(p => p.User) .HasForeignKey<Profile>(p => p.UserId); ``` 4. **索引配置** ```csharp modelBuilder.Entity<Post>() .HasIndex(p => p.Slug) // 单列索引 .IsUnique(); // 唯一索引 modelBuilder.Entity<Product>() .HasIndex(p => new { p.Category, p.Price }) // 复合索引 .HasFilter("IsDeleted = 0"); // 过滤索引(软删除场景) ``` 5. **继承策略** ```csharp modelBuilder.Entity<Animal>() .UseTphMappingStrategy(); // Table-Per-Hierarchy ``` #### 三、高级功能 - **忽略字段**:`modelBuilder.Entity<Post>().Ignore(p => p.TempData);` - **并发令牌**:`.Property(p => p.RowVersion).IsConcurrencyToken()` - **默认值**:`.Property(p => p.CreateTime).HasDefaultValueSql("GETDATE()")` #### 四、最佳实践 1. **集中配置**:通过`IEntityTypeConfiguration<T>`接口分离配置类 ```csharp public class PostConfiguration : IEntityTypeConfiguration<Post> { public void Configure(EntityTypeBuilder<Post> builder) { builder.HasQueryFilter(p => !p.IsDeleted); // 全局过滤 } } ``` 2. **性能优化**:结合`AsNoTracking()`使用可提升只读查询效率[^1] ```csharp // 示例上下文配置 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } ``` §§ 1. 如何通过Fluent API实现多对多关系配置? 2. Fluent API与数据注解的主要区别是什么? 3. EF Core中如何配置全局查询过滤器? 4. 使用Fluent API时如何管理大型项目的配置复杂度? 5. Fluent API支持哪些类型的数据库约束配置? 参考文献: : 禁用EF Core更改跟踪的查询优化方法 : EF Core与EF6的架构对比 : Fluent API实现关系映射的代码示例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值