《ASP.NET Core技术内幕与项目实战》精简集-EFCore2.10:EFCore结合使用Dapper

本节内容,部分为补充内容。主要NuGet包:

  • Dapper(Dapper操作数据库)
  • System.Data.SqlClient(连接数据库)
  • Microsoft.EntityFrameworkCore.SqlServer(ORM的SqlServer数据库提供者)
  • Microsoft.EntityFrameworkCore.Tools(ORM迁移工具)
  • Swashbuckle.AspNetCore(生成WebApi接口文档)

 

一、EFCore和Dapper的区别

1、无论是EFCore,还是Dapper,底层都是通过ADO.NET访问数据库。区别在于,EFCore需要先将LINQ翻译成对应数据库的T-SQl语句,而Dapper直接输入T-SQL语句。

2、由于EFCore,增加了LINQ至SQL的翻译步骤,如果复杂一些的LINQ语句,机翻的结果可能不是最优的SQL语句,同时,EFCore会对实体类状态进行跟踪,所以性能会稍低于Dapper。但是EFCore抹平了数据库差异,我们可以使用统一的LINQ语句进行多种数据库的操作,同时借助编译器的智能提示,写起来也更加流畅。

3、尽管EFCore的LINQ语句写起来非常的流畅,但对于需要关联多表的复杂查询或报表,并不适合使用LINQ。对于这种情况,有两个选择,一是使用视图+EFCore;二是直接使用Dapper。现代应用开发的经验,都要求尽量保持数据库的干净,避免使用存储过程、外键、视图等,这些功能都应该在程序端来实现,所以推荐使用Dapper。

4、EFCore+Dapper,是一套比较理想的组合方案。EFCore做增、删、改和简单查询(.NET7已经出批量操作了),Dapper做复杂查询或报表查询。当然,在VS中直接写SQL是不太适合的,我们还是要借助数据库IDE,编写和调试SQL,推荐开源免费的DBeaver。

 

二、Dapper常用操作

1、安装Nuget包:Dapper和System.Date.SqlClient

2、实体类、DbContext、配置映射、数据库迁移、种子数据的增加等,和EFCore的操作一样,略过

3、在WepApi的控制器中,进行Dapper的CRUD操作

4、以下案例中涉及的实体类及其DbContext的映射配置:

//文章类和评论类组成聚合,一对多双向导航
//作者类为一个单独的聚合,和文章类没有导航关系
//在文章类中创建AuthorId属性,使两个文章类和作者类建议关系

//文章类
public class Article
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public List<Comment> Comments { get; set; } = new List<Comment>();
    public int AuthorId { get; set; } 
}

//评论类
public class Comment
{
    public int Id { get; set; }
    public string Message { get; set; }
    public Article Article { get; set; }
    public int ArticleId { get; set; }
}

//作者类
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; }
}

//文章类和作者类为非导航关系,需要创建一个新的实体类来映射查询返回数据
public class ArticleWithAuthor
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public string AuthorName { get; set; } 
}


//文章、评论和作者类的DbContext映射关系配置
//Article映射配置(Article-Comments聚合的一端)
modelBuilder.Entity<Article>(b =>
{
    b.ToTable("T_Articles");
    b.Property(a => a.Title).IsRequired().HasMaxLength(50).IsUnicode();
    b.Property(a => a.Content).IsRequired().HasMaxLength(500).IsUnicode();
 });

//Comment映射配置(Article-Comments聚合的多端)
modelBuilder.Entity<Comment>(b =>
{
    b.ToTable("T_Comments");
    b.Property(c => c.Message).IsRequired().HasMaxLength(500).IsUnicode();
    b.HasOne<Article>(c=>c.Article).WithMany(a=>a.Comments).HasForeignKey(c=>c.ArticleId);
});

//Author映射-另外一个聚合,不和Article设置导航关系
modelBuilder.Entity<Author>(b =>
{
    b.ToTable("T_Authors");
    b.Property(c => c.Name).IsRequired().HasMaxLength(50);
});

 

 

5、Dapper的增改删操作

[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
    //依赖注入配置服务IConfiguration
    private readonly IConfiguration config;
    public TestController(IConfiguration config)
    {
        this.config = config;
    }

    //插入数据
    [HttpPost]
    public async Task<ActionResult<string>> AddAuthor(string name)
    {
        //创建数据库连接对象SqlConnection
        using var connection = new SqlConnection(config.GetConnectionString("DefaultConnection"));

        //SQL查询语句,插入值@Name为占位参数,调用Dapper扩展方法ExecuteAsync时再赋值
        var sql = "INSERT INTO T_Authors VALUES(@Name)";

        //ExecuteAsync是Dapper的扩展方法,用于执行数据库的增删改命令
        //通过匿名对象new{},给SQL语句中的占位参数赋值
        var result = await connection.ExecuteAsync(sql, new { Name = name});
        return Ok($"成功插入{result}条数据");
    }

    //更新数据
    [HttpPut]
    public async Task<ActionResult<string>> UpdateAuthorName(string oldName, string newName)
    {
        using var connection = new SqlConnection(config.GetConnectionString("DefaultConnection"));

        var sql = "UPDATE T_Authors SET Name = @NewName WHERE Name = @OldName";
        var result = await connection.ExecuteAsync(sql, new { NewName = newName, OldName = oldName });
        return Ok($"成功更新{result}条数据");
    }

    //删除数据
    [HttpDelete]
    public async Task<ActionResult<string>> DeleteAuthorById(int id)
    {
        using var connection = new SqlConnection(config.GetConnectionString("DefaultConnection"));

        var sql = "DELETE FROM T_Authors WHERE Id = @Id";
        var result = await connection.ExecuteAsync(sql, new { Id = id });
        return Ok($"成功删除{result}条数据");
    }
}

 

6、Dapper的SQL查询操作

public class TestController : ControllerBase
{
    ......

    //简单查询
    [HttpGet]
    public async Task<ActionResult<List<Article>>> GetAllArticles()
    {
        ......
        var articles = await connection.QueryAsync<Article>("SELECT * FROM T_Articles");
        return Ok(articles);
    }

    //带参数查询
    [HttpGet]
    public async Task<ActionResult<List<Article>>> GetArticleById(int id)
    {
        ......
        var article = await connection.QueryAsync<Article>("SELECT * FROM T_Articles ta WHERE ta.Id = @Id", new {Id = id});
        if (!article.Any())
        {
            return BadRequest("请求的文章不存在!");
        }
        return Ok(article);        
    }

    //关联查询:非导航关系实体,需要创建一个ArticleWithAuthor实体,与查询语句结果进行一一映射
    [HttpGet]
    public async Task<ActionResult<List<ArticleWithAuthor>>> GetAllArticlesWithAuthor()
    {
        ......
        var sql = "SELECT ta.Id ,ta.Title ,ta.Content ,ta2.Name AS AuthorName FROM T_Articles ta LEFT JOIN T_Authors ta2 ON ta.AuthorId = ta2.Id ";

        var articlesWithAuthor = await connection.QueryAsync<ArticleWithAuthor>(sql);
        if (!articlesWithAuthor.Any())
        {
            return BadRequest("请求的文章不存在!");
        }
        return Ok(articlesWithAuthor);
    }

    //关联查询:双向导航一对多
    [HttpGet]
    public async Task<ActionResult<List<Article>>> GetAllArticlesWithComments()
    {
        ......
        var sql = "SELECT * FROM T_Articles ta INNER JOIN T_Comments tc ON ta.Id = tc.ArticleId ";

        var articlesWithComments = await connection.QueryAsync<Article, Comment, Article>(sql, (article, comment) =>
        {
            article.Comments.Add(comment);
            return article;
        });
        if (!articlesWithComments.Any())
        {
            return BadRequest("请求的文章不存在!");
        }
        return Ok(articlesWithComments);
    }
}

代码解读:以上代码都比较简单,主要是针对导航关系的关联查询会复杂一点,主要对这段代码进行说明

①Dapper的提供了Query的非泛型、泛型、同步和异步方法
②Query泛型方法有最多7个重载,QueryAsync<T1,T2,...TN,TReturn>,用于最多7张表的关联查询。其中T1...TN为关联的数据表(实体),TReturn为指定哪个实体作为返回值
③QueryAsync方法的主要参数:

  • 第一个:sql字符串,必填
  • 第二个:泛型委托Func<T1,T2,...TN,TReturn>,必填。其中T1,...TN,和LINQ方法的参数差不多,可以理解为迭代的泛型参数的对象,方法必须返回TRturn对象
  • 第三个参数为占位参数设置,如果有占位参数,则必填
  • 其它参数比较少用,包括是否缓存、执行存储过程等,一般不需要设置,保存默认值即可

 

特别说明:
1、本系列内容主要基于杨中科老师的书籍《ASP.NET Core技术内幕与项目实战》及配套的B站视频视频教程,同时会增加极少部分的小知识点
2、本系列教程主要目的是提炼知识点,追求快准狠,以求快速复习,如果说书籍学习的效率是视频的2倍,那么“简读系列”应该做到再快3-5倍

 

posted @ 2022-11-06 18:57  functionMC  阅读(906)  评论(0编辑  收藏  举报