.net如何优雅的使用EFCore实例详解

 更新时间:2022年11月28日 16:04:37   作者:BruceNeter  
这篇文章主要为大家介绍了.net如何优雅的使用EFCore实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

正文

EFCore是微软官方的一款ORM框架,主要是用于实体和数据库对象之间的操作。功能非常强大,在老版本的时候叫做EF,后来.net core问世,EFCore也随之问世。

本文我们将用一个控制台项目Host一个web服务,并且使用本地Mysql作为数据库,使用EFCore的Code First模式进行数据操作。

DBSet清除计划

以前使用EF/EFCore的开发者应该都记得,需要在DBContext里写好多DBSet,一个表对应一个DBSet,然后在其他地方操作这些DBSet对相关的表进行增删改查。作为一个开发,这些重复操作都是我们希望避免的,我们可以利用反射机制将这些类型通过框架自带的方法循环注册进去。

1.EF实体继承统一的接口,方便我们反射获取所有EF实体,接口可以设置一个泛型,来泛化我们的主键类型,因为可能存在不同的表的主键类型也不一样。

统一的EF实体接口

public interface IEFEntity<TKey>
{
    public TKey Id { get; set; }
}

统一的接口实现类

public abstract class AggregateRoot<TKey> : IEFEntity<TKey>
{
    public TKey Id { get; set; }
}

用户实体类

public class User : AggregateRoot<string>
{
    public string UserName { get; set; }
    public DateTime Birthday { get; set; }
    public virtual ICollection<Book> Books { get; set; }
}

2.利用反射获取某个程序集下所有的实体类

public class EFEntityInfo
{
    public (Assembly Assembly, IEnumerable<Type> Types) EFEntitiesInfo => (GetType().Assembly, GetEntityTypes(GetType().Assembly));
    private IEnumerable<Type> GetEntityTypes(Assembly assembly)
    {
        //获取当前程序集下所有的实现了IEFEntity的实体类
        var efEntities = assembly.GetTypes().Where(m => m.FullName != null
                                                        && Array.Exists(m.GetInterfaces(), t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEFEntity<>))
                                                        && !m.IsAbstract && !m.IsInterface).ToArray();
        return efEntities;
    }
}

3.DBContext实现类中OnModelCreating方法中注册这些类型

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //循环实体类型,并且通过Entity方法注册类型
    foreach (var entityType in Types)
    {
        modelBuilder.Entity(entityType);
    }
    base.OnModelCreating(modelBuilder);
}

至此为止所有的实体类都被注册到DBContext中作为DBSets,再也不需要一个个写DBSet了,可以用过DbContext.Set<User>()获取用户的DBSet。

IEntityTypeConfiguration(表配置)

用数据库创建过表的同学都知道,在设计表的时候,可以给表添加很多配置和约束,在Code First模式中,很多同学都是在对象中通过注解的方式配置字段。如下就配置了用户名是不能为NULL和最大长度为500

[Required]
[MaxLength(500)]
public string UserName { get; set; }

也有的同学在DbContext中的OnModelCreating方法配置

modelBuilder.Entity<User>().Property(x => x.UserName).IsRequired();

这两种方法,前者入侵行太强,直接代码耦合到实体类中了,后者不够清楚,把一大堆表的配置写在一个方法里,当然了很多人说可以拆分不同的方法或者使用注释分开。但是!不够优雅!
我们可以使用IEntityTypeConfiguration接口实现我们所想的优雅的表配置。
1.创建一个配置基类,继承自IEntityTypeConfiguration,做一些通用的配置,比如设置主键,一般都是id啦,还有软删除等。

public abstract class EntityTypeConfiguration<TEntity, TKey> : IEntityTypeConfiguration<TEntity>
       where TEntity : AggregateRoot<TKey>
{
    public virtual void Configure(EntityTypeBuilder<TEntity> builder)
    {
        var entityType = typeof(TEntity);
        builder.HasKey(x => x.Id);
        if (typeof(ISoftDelete).IsAssignableFrom(entityType))
        {
            builder.HasQueryFilter(d => EF.Property<bool>(d, "IsDeleted") == false);
        }
    }
}

2.创建用户实体/表独有的配置,比如设置用户名的最大长度,以及seed一些数据

public class UserConfig : EntityTypeConfiguration<User, string>
{
    public override void Configure(EntityTypeBuilder<User> builder)
    {
        base.Configure(builder);
        builder.Property(x => x.UserName).HasMaxLength(50);
        //mock一条数据
        builder.HasData(new User()
        {
            Id = "090213204",
            UserName = "Bruce",
            Birthday = DateTime.Parse("1996-08-24")
        });
    }
}

当然还有很多配置可以设置,比如索引,导航属性,唯一键等。如下图书实体

public class BookConfig : EntityTypeConfiguration<Book, long>
{
    public override void Configure(EntityTypeBuilder<Book> builder)
    {
        base.Configure(builder);
        builder.Property(x => x.Id).ValueGeneratedOnAdd(); //设置book的id自增
        builder.Property(x => x.BookName).HasMaxLength(500).IsRequired();
        builder.HasIndex(x => x.Author);//作者添加索引
        builder.HasIndex(x => x.SN).IsUnique();//序列号添加唯一索引
        builder.HasOne(r => r.User).WithMany(x=>x.Books)
            .HasForeignKey(r => r.UserId).IsRequired();//导航属性,本质就是创建外键,虽然查询很方便,生产中不建议使用!!!
    }
}

3.DBContext中应用配置

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasCharSet("utf8mb4 ");
    var (Assembly, Types) = _efEntitysInfo.EFEntitiesInfo;
    foreach (var entityType in Types)
    {
        modelBuilder.Entity(entityType);
    }
    //只需要将配置类所在的程序集给到,它会自动加载
    modelBuilder.ApplyConfigurationsFromAssembly(Assembly);
    base.OnModelCreating(modelBuilder);
}

Repository(仓储)

这个不过分介绍,特别是基于http的微服务中基本都有这个。

1.创建一个仓储基类,对于不同的实体,创建一样的增删改查方法。

简单写几个查询的方法定义。

public interface IAsyncRepository<TEntity, Tkey> where TEntity : class
{
    IQueryable<TEntity> All();
    IQueryable<TEntity> All(string[] propertiesToInclude);
    IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> filter);
    IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> filter, string[] propertiesToInclude);
}

2.创建仓储实现类,将DBContext注入到构造中

public class GenericRepository<TEntity, Tkey> : IAsyncRepository<TEntity, Tkey> where TEntity : class
{
    protected readonly LibraryDbContext _dbContext;
    public GenericRepository(LibraryDbContext dbContext)
    {
        _dbContext = dbContext;
    }
    ~GenericRepository()
    {
        _dbContext?.Dispose();
    }
    public virtual IQueryable<TEntity> All()
    {
        return All(null);
    }
    public virtual IQueryable<TEntity> All(string[] propertiesToInclude)
    {
        var query = _dbContext.Set<TEntity>().AsNoTracking();
        if (propertiesToInclude != null)
        {
            foreach (var property in propertiesToInclude.Where(p => !string.IsNullOrWhiteSpace(p)))
            {
                query = query.Include(property);
            }
        }
        return query;
    }
}

Autofac

1.注入DBContext到Repository的构造方法中,并且注入Repository

public class EFCoreEleganceUseEFCoreModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        base.Load(builder);
        builder.RegisterModule<EFCoreEleganceUseDomainModule>(); //注入domain模块
        builder.RegisterGeneric(typeof(GenericRepository<,>))//将dbcontext注入到仓储的构造中
                .UsingConstructor(typeof(LibraryDbContext))
                .AsImplementedInterfaces()
                .InstancePerDependency();
        builder.RegisterType<WorkUnit>().As<IWorkUnit>().InstancePerDependency();
    }
}

2.Domain注入EFEntityInfo

public class EFCoreEleganceUseDomainModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<EFEntityInfo>().SingleInstance();
    }
}

数据库配置

1.注入DBContext,从配置文件读取数据库配置,然后根据开发/生产环境做一些特殊处理

var mysqlConfig = hostContext.Configuration.GetSection("Mysql").Get<MysqlOptions>();
var serverVersion = new MariaDbServerVersion(new Version(mysqlConfig.Version));
services.AddDbContextFactory<LibraryDbContext>(options =>
{
    options.UseMySql(mysqlConfig.ConnectionString, serverVersion, optionsBuilder =>
    {
        optionsBuilder.MinBatchSize(4);
        optionsBuilder.CommandTimeout(10);
        optionsBuilder.MigrationsAssembly(mysqlConfig.MigrationAssembly);//迁移文件所在的程序集
        optionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
    }).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
    //开发环境可以打开日志记录和显示详细的错误
    if (hostContext.HostingEnvironment.IsDevelopment())
    {
        options.EnableSensitiveDataLogging();
        options.EnableDetailedErrors();
    }
});

项目架构和源码

项目只是一个demo架构,并不适用于生产,主程序是一个控制台项目,只需要引用相关的包和模块,就可以启动一个web host.

全部代码已经全部上传到github:https://github.com/BruceQiu1996/EFCoreDemo该项目是一个可以启动运行的基于.net6的控制台项目,启动后会启动一个web host和一个swagger页面。

以上就是.net如何优雅的使用EFCore实例详解的详细内容,更多关于.net使用EFCore的资料请关注脚本之家其它相关文章!

相关文章

  • 引用全局程序集缓存内的程序集的方法

    引用全局程序集缓存内的程序集的方法

    把程序集安装到GAC,但开发时VS并不会加载GAC,所以你没有办法去添加引用。
    2008-11-11
  • 代码实现打印功能(asp.net+javascript)

    代码实现打印功能(asp.net+javascript)

    页面实现打印的效果代码,分为服务器端和客户端单个即可,客户端的比较不错,本站也是类似的方法。
    2009-05-05
  • 利用ASP.Net Core中的Razor实现动态菜单

    利用ASP.Net Core中的Razor实现动态菜单

    这篇文章主要介绍了如何利用ASP.Net Core中的Razor实现动态菜单功能,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2022-04-04
  • VS2015下简单使用EF框架的方法

    VS2015下简单使用EF框架的方法

    这篇文章主要为大家详细介绍了VS2015下简单使用EF框架的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • ASP.NET中内嵌页面代码的一个问题

    ASP.NET中内嵌页面代码的一个问题

    如果页面继承了一个父页面,而且当前页面用new覆盖了父页面的某个属性
    2010-02-02
  • .NET Core Dapper操作mysql数据库的实现方法

    .NET Core Dapper操作mysql数据库的实现方法

    这篇文章主要介绍了.NET Core Dapper操作mysql数据库的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • .NET连接池的问题详解

    .NET连接池的问题详解

    这篇文章主要介绍了.NET连接池的问题详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • 打造自己的.NET Core项目模板

    打造自己的.NET Core项目模板

    这篇文章主要介绍了打造自己的.NET Core项目模板,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • asp.net利用ashx文件实现文件的上传功能

    asp.net利用ashx文件实现文件的上传功能

    这篇文章主要介绍了asp.net利用ashx文件实现文件的上传功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • .net邮箱发布邮箱信息的实例

    .net邮箱发布邮箱信息的实例

    本文介绍了.net邮箱发布邮箱信息的实例代码,需要的朋友可以参考一下
    2013-03-03

最新评论