• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

愚公系列2023年02月 .NET/C#知识点-使用控制台手搭webapi框架

武飞扬头像
愚公搬代码
帮助1


前言

WebAPI是一种协议,用于允许网络应用程序(如浏览器)与网络服务器(如Web服务器)之间进行通信。它可以用于处理数据,访问数据库,处理图像和视频,以及进行其他高级功能。

本文涉及的知识量巨大主要有如下:

  • EFCore
  • Autofac
  • Serilog
  • Swagger
  • 非常多底层知识

一、使用控制台手搭webapi框架

1.配置文件

appsettings.Development.json

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Default": "Information",
        "System": "Information",
        "Microsoft": "Information"
      }
    },
    "WriteTo": [
      { "Name": "Console" },
      {
        "Name": "Async",
        "Args": {
          "configure": [
            {
              "Name": "File",
              "Args": {
                "outputTemplate": "Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff} LogLevel:{Level} Class:{SourceContext} Message:{Message}{Exception}{NewLine}",
                "rollingInterval": "4"
              }
            }
          ]
        }
      }
    ]
  },
  "Mysql": {
    "ConnectionString": "Server=127.0.0.1;Port=3306;database=testlib;uid=root;pwd=sa12345;",
    "Version": "8.0.20",
    "MigrationAssembly": "EFCoreEleganceUse.EF.Migrations"
  }
}

学新通

2.控制台配置

using Autofac;
using Autofac.Extensions.DependencyInjection;
using EFCoreEleganceUse.EF.Mysql;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;

internal class Program
{
    async static Task Main(string[] args)
    {
        //设置当前文件夹
        Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
        //注入相关服务启动程序
        var host = CreateHostBuilder(args).Build();
        host.UseSwagger();
        host.UseSwaggerUI();
        host.MapControllers();
        host.UseAuthentication();
        host.UseAuthorization();

        await host.RunAsync();
    }

    public static WebApplicationBuilder CreateHostBuilder(string[] args)
    {
        //配置参数和环境
        var hostBuilder = WebApplication.CreateBuilder(options:new WebApplicationOptions() 
        {
            Args = args,
            EnvironmentName = Environments.Development
        });

        //使用日志和aotufac
        hostBuilder.Host.UseSerilog((context, logger) =>//Serilog
        {
            string date = DateTime.Now.ToString("yyyy-MM-dd");//按时间创建文件夹
            logger.ReadFrom.Configuration(context.Configuration);
            logger.Enrich.FromLogContext();
            logger.WriteTo.File($"Logs/{date}/", rollingInterval: RollingInterval.Hour);//按小时分日志文件
        }).UseServiceProviderFactory(new AutofacServiceProviderFactory()).UseEnvironment(Environments.Development);
        //生产下需要通过命令行参数或者配置文件设置环境:开发,测试,生产

        hostBuilder.Host.ConfigureServices((hostContext, services) =>
        {
            //注入mysql,生产中应该放置在应用层
            var mysqlConfig = hostContext.Configuration.GetSection("Mysql").Get<MysqlOptions>();
            var serverVersion = new MariaDbServerVersion(new Version(mysqlConfig.Version));
            services.AddDbContext<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();
                }
            });
            services.AddControllers();
            services.AddEndpointsApiExplorer();
            services.AddSwaggerGen();
        });

        //注册模块
        hostBuilder.Host.ConfigureContainer<ContainerBuilder>((hcontext, containerBuilder) =>
        {
            //生产中由应用层聚合各种基础设置等模块,最后交由Host程序注册应用层模块
            containerBuilder.RegisterModule<EFCoreEleganceUseEFCoreModule>();
        });

        return hostBuilder;
    }
}

学新通
控制台配置最主要的是LibraryDbContext和EFCoreEleganceUseEFCoreModule,下面着重详解

二、EFCore框架DBSet配置详解

1.实体统一配置

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

接口如下:

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

学新通

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

学新通

2.实体继承统一接口

public class User : AggregateRoot<string>
{
    public string UserName { get; set; }
    public DateTime Birthday { get; set; }
    public virtual ICollection<Book> Books { get; set; }
}
public class Book : AggregateRoot<long>
{
    public string BookName { get; set; }
    public string Author { get; set; }
    public double Price { get; set; }
    public string Publisher { get; set; }
    public string SN { get; set; } //图书序列号
    public string? UserId { get; set; }
    public virtual User? User { get; set; } //租借该书的用户
}

学新通

3.获取程序集所有类

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;
    }
}

学新通

4.批量注入模型类到EF中

using EFCoreEleganceUse.Domain.Entities;
using Microsoft.EntityFrameworkCore;

namespace EFCoreEleganceUse.EF.Mysql
{
    /// <summary>
    /// 图书馆数据库上下文
    /// </summary>
    public class LibraryDbContext : DbContext
    {
        private readonly EFEntityInfo _efEntitysInfo;

        public LibraryDbContext(DbContextOptions options, EFEntityInfo efEntityInfo) : base(options)
        {
            _efEntitysInfo = efEntityInfo;
            ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        }

        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);
        }
    }
}

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

三、EFCore框架表配置详解

1.配置基类,

创建一个配置基类,继承自IEntityTypeConfiguration,做一些通用的配置,比如设置主键,软删除等。

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.实体表统一配置

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(false);//导航属性,本质就是创建外键,虽然查询很方便,生产中不建议使用!!!
    }
}
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")
        });
    }
}

学新通

3.DBContext中应用配置

using EFCoreEleganceUse.Domain.Entities;
using Microsoft.EntityFrameworkCore;

namespace EFCoreEleganceUse.EF.Mysql
{
    /// <summary>
    /// 图书馆数据库上下文
    /// </summary>
    public class LibraryDbContext : DbContext
    {
        private readonly EFEntityInfo _efEntitysInfo;

        public LibraryDbContext(DbContextOptions options, EFEntityInfo efEntityInfo) : base(options)
        {
            _efEntitysInfo = efEntityInfo;
            ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        }

        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);
        }
    }
}

学新通

四、仓储配置

1.仓储基类

public interface IAsyncRepository<TEntity, Tkey> where TEntity : class
{
    // query
    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.仓储实现类

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;
    }

    public virtual IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> filter)
    {
        return Where(filter, null);
    }

    public virtual IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> filter, string[] propertiesToInclude)
    {
        var query = _dbContext.Set<TEntity>().AsNoTracking();

        if (filter != null)
        {
            query = query.Where(filter);
        }

        if (propertiesToInclude != null)
        {
            foreach (var property in propertiesToInclude.Where(p => !string.IsNullOrWhiteSpace(p)))
            {
                query = query.Include(property);
            }
        }

        return query;
    }
}

学新通

五、Autofac配置

1.注入DBContext到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.数据库迁移

官方文档如下:https://learn.microsoft.com/zh-cn/ef/core/managing-schemas/migrations/managing?tabs=vs
学新通

Add-Migration InitialCreate -OutputDir Your\Directory
Update-Database

学新通

2.Users控制器

[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
    private readonly ILogger<UsersController> _logger;
    //生产中可以在应用层下创建service层,聚合各种实体仓储
    private readonly IAsyncRepository<User, string> _userAsyncRepository;

    public UsersController(ILogger<UsersController> logger, IAsyncRepository<User, string> userAsyncRepository)
    {
        _logger = logger;
        _userAsyncRepository = userAsyncRepository;
    }

    [HttpGet]
    [AllowAnonymous]
    public async Task<ActionResult> Get()
    {
        return Ok(await _userAsyncRepository.All().ToListAsync());
    }
}

学新通
相关源码:https://download.csdn.net/download/aa2528877987/87474302

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhggbccg
系列文章
更多 icon
同类精品
更多 icon
继续加载