asp.net core webapi+efcore

简洁的restfull风格

目前c#提供了多种风格的web编程,因为微软有自己的前端,所以集成了很多内容,不过基于现在编程前后端分离的模式,webapi是合适的。

webapi

目前网络上有很多介绍,不反复说这个了。在建立控制器时,使用最多就是httpget和httppost,其它意义不大。

实操例子

准备库

在visual studio建立webapi项目,默认即可。我使用的数据库是postgresql,下载对应的组件。

我用到的所有库:Microsoft.EntityFrameworkCore,Microsoft.EntityFrameworkCore.Tools,Microsoft.EntityFrameworkCore.Proxies,Npgsql.EntityFrameworkCore.PostgreSQL。其它都会项目创建自动有。

Microsoft.EntityFrameworkCore.Tools:介绍代码优先会用到。

Microsoft.EntityFrameworkCore.Proxies:介绍efcore优化用到。

Npgsql.EntityFrameworkCore.PostgreSQL:PostgreSQL数据库连接驱动

项目编程

(1)创建models文件夹,创建user类,Product类

cs 复制代码
  public class User
  {
      public int Id { get; set; }
      public string Name { get; set; }
      public string Email { get; set; }
  }
cs 复制代码
 public class Product
 {
     public int Id { get; set; }
     public string Name { get; set; }
     public decimal Price { get; set; }
 }

(2)创建PlatDbContext文件夹,创建AppDbContext类,数据库访问。

cs 复制代码
 public class AppDbContext : DbContext
 {
     public DbSet<Product> Products { get; set; }

     public DbSet<User> Users { get; set; }

     public AppDbContext(DbContextOptions<AppDbContext> options)
         : base(options) { }

     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
     {
         //配置连接数据库字符串
         string connectionString = "Host=localhost;Username=postgres;Password=123456;Database=plat";
         //指定使用SqlServer数据库进行连接
         optionsBuilder.UseNpgsql(connectionString);
       //  optionsBuilder.UseLazyLoadingProxies();
     }
     protected override void OnModelCreating(ModelBuilder modelBuilder)
     {
         modelBuilder.Entity<Product>()
           //  .ToTable("Products")
             .HasKey(p => p.Id);

         modelBuilder.Entity<Product>()
             .Property(p => p.Name)
             .HasMaxLength(100);
     }
 }

(3) 创建Repository文件夹,创建创存模式的接口和实现类

cs 复制代码
 public interface IRepository<T> where T : class
 {
     Task<IEnumerable<T>> GetAllAsync();
     Task<T> GetByIdAsync(int id);
     Task AddAsync(T entity);
     Task UpdateAsync(T entity);
     Task DeleteAsync(int id);
     Task<bool> SaveChangesAsync();

     //只读
     Task<IEnumerable<T>> GetAllAsNoTrackingAsync();

     Task<int> ExecuteSqlAsync(string sql);
     Task<IDbContextTransaction> GetTransactionAsync();
 }
cs 复制代码
   public class Repository<T> : IRepository<T> where T : class
   {
       private readonly AppDbContext _context;
       private readonly DbSet<T> _dbSet;

       public Repository(AppDbContext context)
       {
           _context = context;
           _dbSet = _context.Set<T>();
       }

       public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();

       public async Task<T> GetByIdAsync(int id) => await _dbSet.FindAsync(id);

       public async Task AddAsync(T entity) 
       {   await _dbSet.AddAsync(entity);
           await SaveChangesAsync();
       }

       public async Task UpdateAsync(T entity)
       {

           _dbSet.Update(entity);
           await SaveChangesAsync();
       }

       public async Task DeleteAsync(int id)
       {
           var entity = await _dbSet.FindAsync(id);
           if (entity != null)
           {
               _dbSet.Remove(entity);
               await SaveChangesAsync();
           }
       }

       public async Task<IEnumerable<T>> GetAllAsNoTrackingAsync()
       {
         return  await _dbSet.AsNoTracking<T>().ToListAsync();
       }
     
       public async Task<int> ExecuteSqlAsync(string sql)
       {
           return await _context.Database.ExecuteSqlRawAsync(sql);
       }
       public async Task<IDbContextTransaction> GetTransactionAsync()
       {
         return  await _context.Database.BeginTransactionAsync();
       }

       public async Task<bool> SaveChangesAsync() => await _context.SaveChangesAsync() > 0;
   }

(4)配置EF,在main方法中添加如下代码。

cs 复制代码
 builder.Services.AddDbContext<AppDbContext>(options =>
 options.UseNpgsql().LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Command.Name }));
// builder.Services.AddDbContextPool<AppDbContext>(options => options.UseNpgsql(), poolSize: 80);
 //注意设置最大连接数,一旦超过默认配置的连接池最大数量,会回退到按需创建实例的行为

 builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));

(5) 创建控制器UserController

cs 复制代码
namespace WebPlat.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UserController : ControllerBase
    {
        public  UserController(IRepository<User> repository,ILogger<UserController> logger)
        {
            this.repository = repository;
            this.logger = logger;

        }
        IRepository<User> repository;
        ILogger<UserController> logger;

        [HttpGet]
        [Route("GetUser")]
        public IEnumerable<User> GetUser()
        {
            // 返回所有用户的逻辑
            return new List<User>
        {
            new User { Id = 1, Name = "Alice", Email = "alice@example.com" },
            new User { Id = 2, Name = "Bob", Email = "bob@example.com" }  
            // ... 其他用户数据  
        };
        }

        // GET api/Test/5  
        [HttpGet]
        [Route("GetMyUser")]
        public User GetMyUser(int id)
        {
            // 根据ID返回单个用户的逻辑  
            return new User { Id = 1, Name = "Alice", Email = "alice@example.com" };
        }

        [HttpPost("AddUser")]
        public bool AddUser([FromBody] User user)
        {
            repository.AddAsync(user);
            return true;
        }
       
    }
}

(6) 服务层(简单的程序不需要)

如果你有很复杂的业务处理,那么还需要一个服务层,需要创建文件夹Services,在里面创建接口和实现类,也有把接口和类放到不同的文件夹(名称空间)。此时存储对象就需要放在业务层。控制器中使用服务接口。

cs 复制代码
 public interface IUserService
 {
     Task<User> GetUserByIdAsync(int id);
     Task<IEnumerable<User>> GetAllUsersAsync();
     Task<User> CreateUserAsync(User user);
     Task UpdateUserAsync(int id, User user);
     Task DeleteUserAsync(int id);
 }
cs 复制代码
 public class UserService : IUserService
 {
     private readonly IRepository<User>  repository; // 假设有一个 ApplicationDbContext 作为数据上下文

   
     public UserService(IRepository<User> repository)
     {
         this.repository = repository;
     }

     

     public async Task<User> GetUserByIdAsync(int id)
     {
         return await repository.GetByIdAsync(id);
     }

     public async Task<IEnumerable<User>> GetAllUsersAsync()
     {
         return await repository.GetAllAsync();
     }

     public async Task<User> CreateUserAsync(User user)
     {
         await repository.AddAsync(user);
         return user;
     }

     public async Task UpdateUserAsync(int id, User user)
     {
         await repository.UpdateAsync(id, user);
     }

     public async Task DeleteUserAsync(int id)
     {
         await repository.DeleteAsync(id);
     }
 }

然后注入服务。

cs 复制代码
builder.Services.AddScoped(typeof(IUserService), typeof(UserService));

不好意思我偷了一下懒,不提倡。写了一个反射方法注入服务。

cs 复制代码
 public static void AddService(IServiceCollection services)
 {
     // 获取当前程序集
     Assembly assembly = Assembly.GetExecutingAssembly();

     // 指定要搜索的命名空间
     string targetNamespace = "WebPlat.Services";

     // 获取所有类型
     Type[] types = assembly.GetTypes();

     // 过滤出特定命名空间下的所有类型
     var filteredTypes = types.Where(t => t.Namespace == targetNamespace).Where(t=>t.IsClass);

     foreach (var type in filteredTypes)
     {
         Type[] interfaces= type.GetInterfaces();
         var interfacesTypes=  interfaces.Where(t => t.Namespace == targetNamespace);//可以不要,只是更加精确
         if (interfaces.Length > 0)
         {
             foreach(Type interfacetype in interfacesTypes)
             { 
                 services.AddScoped(interfacetype, type);

             }
           
         }


     }
 }

在main方法注入服务就变成了调用该方法。

所以控制器中就变了一点,为了演示没有删除之前的。

public UserController(IRepository<User> repository,IUserService service, ILogger<UserController> logger)

{

this.repository = repository;

this.logger = logger;

this.userService = service;

}

IRepository<User> repository;

ILogger<UserController> logger;

IUserService userService;

添加了一个测试方法:

cs 复制代码
  [HttpPost("CreateUser")]
  public bool CreateUser([FromBody] UserVo user)
  {
      // repository.AddAsync(user);
     string json= JsonUtil.ToJson(user);
      User user1= JsonUtil.ToObject<User>(json);
      user1= CopyUtil<UserVo, User>.Trans(user);
      userService.CreateUserAsync(user1);
      return true;
  }

有一点不一样?因为如果有复杂业务,一般来说前端的数据和最后数据库的数据可能不一样,就会专门有和前端交互的数据结构,就是dao层。有时候还有中间的实体层等。

既然有了2个不一样的类结构,是不是要赋值?c#浅拷贝,深拷贝了解一下。

这里一般主要的几种:c#自己的序列化(二进制,xml,json),手动写代码赋值,第三方等。

通用的就是序列化,目前主要是json,其它两种就别给自己找事了。

json序列化目前推荐使用第三方Newtonsoft.Json,不要用微软的,除非你自己闲的慌喜欢尝试。

因此又建立一个工具文件夹Utils,创建json序列化类。你也限制一下泛型参数。

cs 复制代码
  public class JsonUtil
  {
      public  static  string ToJson<T>(T obj)
      {
          string json = JsonConvert.SerializeObject(obj);
          return json;
      }
      public static T ToObject<T>(string json)
      {
          T obj = JsonConvert.DeserializeObject<T>(json);
          return obj;
      }
  }

这样就可以通过json复制对象,json复制对象类似反射,效率相对有点低,但是一般的程序应该是可以的,可以接受。

顺便提供一个优化的复制对象方法,表达式树。创建CopyUtil类。

cs 复制代码
 public static class CopyUtil<TIn, TOut>

 {

     private static readonly Func<TIn, TOut> cache = GetFunc();

     private static Func<TIn, TOut> GetFunc()

     {

         ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");

         List<MemberBinding> memberBindingList = new List<MemberBinding>();

         foreach (var item in typeof(TOut).GetProperties())

         {

             if (!item.CanWrite)

                 continue;
             if(typeof(TIn).GetProperty(item.Name)==null)
                 {
                 continue;
             }

             MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));

             MemberBinding memberBinding = Expression.Bind(item, property);

             memberBindingList.Add(memberBinding);

         }

         MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());

         Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[] { parameterExpression });

         return lambda.Compile();

     }

     public static TOut Trans(TIn tIn)

     {

         return cache(tIn);

     }

 }

你可能不懂?不懂没有关系,反正就是复制属性。以前我研究过emit,写了一个通用方法,嗯效率很高,不过看起来有点反人类,这个表达式树比较通人性一点。两者原理一样,都是在内存中已经编译生成了。

EF的代码优先

下载完库,运行项目可以正常执行以后,准备生成数据库的表,这里我是介绍一下操作,不详细讲解ef的使用方法,ef的使用,在model上可以设置各种属性,我不详细介绍,太大。

(1)数据库

安装好postgresql数据库,客户端连接,建立好数据库:plat。

(2)生成数据库表

在visual studio的工具菜单中打开"程序包管理器控制台",然后执行命令:Add-Migration InitialCreate 执行成功后,会在项目中生成一个文件夹和类,不用管。

然后再执行命令: Update-database

以上成功则会在数据库生成表,如图:

补充

项目中创建一个全局筛选器,可以做很多有用的工作。例如项目的错误处理。

(1)项目中创建文件夹Filter,建立HttpExceptionFilterAttribute类

cs 复制代码
 public class HttpExceptionFilterAttribute : System.Web.Http.Filters.ExceptionFilterAttribute
 {
     public override void OnException(HttpActionExecutedContext actionExecutedContext)
     {
         if (actionExecutedContext.Exception is Exception ex)
         {
             actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(
                 HttpStatusCode.InternalServerError, ex.Message);
         }
     }
 }

EF优化

EFcore速度一直是比较有争议,不过目前最新版本感觉已经基本可以了,这里把最主要的优化简单介绍。

(1)延迟加载

启用延迟配置,在AppDbContext的配置方法中添加配置语句。该功能的详细意义需要自己详细了解。

cs 复制代码
optionsBuilder.UseLazyLoadingProxies();

(2)上下文池化

需要再配置上下文时使用池化。用该语句替换一般的配置。

cs 复制代码
builder.Services.AddDbContextPool<AppDbContext>(options => options.UseNpgsql(), poolSize: 80);

(3)查询只读,启用AsNoTracking.

就是只查询不跟踪,在项目中就是仓存模式的GetAllAsNoTrackingAsync方法。

目前主要的优化就是上面几种,当然对于程序来说,你可以根据业务优化,在查询中启用缓存的方式等。还有一些其它库,例如:Z.EntityFramework.Plus.EFCore。该第三方库可以研究。

这里说一下批量添加,批量添加目前很多数据库支持批量sql语句,例如:

insert into(ID,name)value(1,'ton') value(2,'j') ;这样的语句,可以充分使。

还有很多数据库提供了bulkcopy,支持ado.net方式,所以有了efcore后,原始基础的ado.net还是精华,需要了解。

最后的总结

这里只是webapi,其实不喜欢代码优先或者数据库优先这种模式。个人觉得数据库还是数据库,代码还是代码,传统的数据库设计创建,程序开发就是程序,这里model会少很多属性,数据库表会很准确。目前这样的方式出现,很多小企业都是程序员自己弄数据库,开发程序,看起来会少很多工作,其实不好。还是分离好点,还有助于检测。

另外你是winfrom和WPF等桌面系统,也建议可以使用后台服务webapi。目前已经不需要用IIS部署了。这样显示端是web或桌面都无所谓。至于性能问题,可以在某些功能下使用TCP或者grpc。

桌面系统访问建议使用httpclient库。

相关推荐
止语Lab30 分钟前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活31 分钟前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困36 分钟前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn36 分钟前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户2986985301442 分钟前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908401 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金1 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛1 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒2 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端
卷无止境2 小时前
提升 Python 代码健壮性的方法大盘点
后端·python