asp.net core webapi+efcore

发布于:2025-04-21 ⋅ 阅读:(19) ⋅ 点赞:(0)

简洁的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类

  public class User
  {
      public int Id { get; set; }
      public string Name { get; set; }
      public string Email { get; set; }
  }
 public class Product
 {
     public int Id { get; set; }
     public string Name { get; set; }
     public decimal Price { get; set; }
 }

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

 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文件夹,创建创存模式的接口和实现类

 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();
 }
   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方法中添加如下代码。

 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 

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,在里面创建接口和实现类,也有把接口和类放到不同的文件夹(名称空间)。此时存储对象就需要放在业务层。控制器中使用服务接口。

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

然后注入服务。

builder.Services.AddScoped(typeof(IUserService), typeof(UserService));

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

 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;

添加了一个测试方法:

  [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序列化类。你也限制一下泛型参数。

  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类。

 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类

 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的配置方法中添加配置语句。该功能的详细意义需要自己详细了解。

optionsBuilder.UseLazyLoadingProxies();

(2)上下文池化

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

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库。 


网站公告

今日签到

点亮在社区的每一天
去签到