6种方法教你更好的使用EF Core构建应用程序

Posted webmote

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了6种方法教你更好的使用EF Core构建应用程序相关的知识,希望对你有一定的参考价值。

大家都知道,ORM(实体关系映射模型)能帮助我们快速构建应用程序,而在使用.net 技术栈工作时,一定会首选Microsoft的数据库访问框架Entity Framework Core(EF Core)来构建应用程序,这里我们就谈谈你们中许多人都熟悉的软件原理和模式。

前言、六个原理模式:

  1. 关注点分离–构建正确的体系结构;
  2. 服务层–将数据操作与表示操作分开;
  3. 仓储–选择正确的数据库访问模式;
  4. 依赖注入–将您的数据库代码转换为服务;
  5. 建立业务逻辑–将领域驱动设计与EF结合使用;
  6. 性能调优–如果有需要,让它更快的工作。

关于软件原理和模式,每个人都有自己的看法。

虽然整个软件世界在日新月异的发生变化,但值得庆幸的是,总有一些东西没有变,一些非常聪明的人一直在思考编程科学,而他们总是在向我们推荐模式,我认为这是掌握软件开发的精粹和捷径

从大佬那里直接学习的想法很棒,然而枯燥的理论需要落地才能成佛,因此本文将一些软件思想与我多年的学习结合在一起,希望能从中体会到大佬的要义。

注意:我假设您已经了解实体框架,如果不是,建议您学习下Microsoft的EF Core文档,其中包括许多示例应用程序。

1.关注点分离

什么是关注点分离(SoC)?字都认识,组合起来好难理解。

关注点:字面意思是你关注的地方。可以是类库、包、类、甚至函数。分离:分开,纵向划分或者横向划分,可以按照功能职责划分,也可以按照业务语义划分。

该原则要求您:具有相似或相关功能的代码应组合在一起,可以理解为放置在单独的项目中。这称为凝聚力。使每个小组/项目尽可能独立,每段代码都应该有一个清晰的界面和工作范围,由其他调用者更改其工作方式,而不太可能更改代码,这也被称为低耦合。

网络上大多数EF代码示例都倾向于直接从其使用的任何应用程序类型调用。这并不遵循SoC,也不能真正代表实际应用程序的编写方式,真正符合SOC原则的设计分层如下:
在这里插入图片描述
分层方法对于中小型应用程序非常有效,也非常适合云平台开发,在云平台中可以在负载很大的情况下启动Web应用程序的更多实例负载,在设计上这称为横向扩展或自动扩展。

下图显示了如何将SoC应用于数据库访问代码,所有EF数据库访问代码都以气泡突出显示,气泡的大小与您在每一层中找到的EF代码的数量有关,请注意,ASP.NET Core项目和纯业务逻辑(BizLogic)项目中根本没有EF Core查询/更新代码。
在这里插入图片描述

2.服务层

《Microsoft .NET:企业的架构应用程序》,这本书向我介绍了服务层的使用,有兴趣可以去参考下。

服务层的原则就是边界问题。服务层“在两个接口层之间设置了边界”,但这对我的应用程序有什么帮助?我的理解是服务层作为适配器。

在分层体系结构中,数据库/业务逻辑与表示层之间经常存在数据不匹配的情况。DDD中有类似的观点,数据库和业务逻辑应专注于业务规则,而表示层则是为用户提供良好的用户体验,或者提供标准且简单的API服务。

因此,服务层成为至关重要的层,因为它可以是了解双方并可以在两个世界之间转换数据的层。这样可以使业务逻辑和数据库不受表示需求的干扰。

看看下图
在这里插入图片描述
EF提供了一种构建查询的方法,称为select loading,它可以从每个表中“挑选”相关的列,并将它们组合成一个完全适合用户视图的DTO / ViewModel类。我将这种转换与其他排序,过滤和分页功能一起应用于服务层。代码如下:

public static IQueryable<BookListDto> 
    MapBookToDto(this IQueryable<Book> books)   
{
    return books.Select(p => new BookListDto
    {
        BookId = p.BookId,                      
        Title = p.Title,                        
        Price = p.Price,                        
        PublishedOn = p.PublishedOn,            
        ActualPrice = p.Promotion == null      
                ? p.Price : p.Promotion.NewPrice,         
        PromotionPromotionalText =              
                p.Promotion == null            
                  ? null : p.Promotion.PromotionalText,
        AuthorsOrdered = string.Join(", ",      
                p.AuthorsLink                   
                .OrderBy(q => q.Order)          
                .Select(q => q.Author.Name)),   
        ReviewsCount = p.Reviews.Count,         
        ReviewsAverageVotes = p.Reviews.Select(y =>
                (double?)y.NumStars).Average()
    });
} 

是的,这段代码很复杂,我们需要从许多不同的地方提取数据并同时进行一些计算,当然你可以构建自己的GenericServices库来减少操作的复杂度。

3.仓储

我们可以通过多种不同的方式访问数据库,并从其他应用程序隐藏EF访问层。在下图中,我显示了四种不同的数据访问模式。
在这里插入图片描述
四种类型的数据库访问模式是:

  • 仓储+工作单元(Repo + UOW): 这会将所有EF Core隐藏在为EF提供不同接口的代码后面。您可以用另一个数据库访问框架替换EF,而无需更改调用Repo + UOW的方法。
  • EF仓储: 这是一个仓储模式,它不会像Repo + UOW模式那样尝试隐藏EF代码。EF仓储假定您作为开发人员知道EF的规则,例如使用跟踪的实体并调用SaveChanges进行更新,因此您将遵守它们。
  • 查询对象: 查询对象封装了数据库查询(即数据库读取)的代码。它们保存了查询的整个代码,或者包含了查询部分的复杂查询。查询对象通常使用IQueryable 输入和输出作为扩展方法构建,以便可以将它们链接在一起以构建更复杂的查询。
  • 直接调用EF。这表示您只是将所需的EF代码放在需要它的方法中的情况。

Repo + UOW模式,虽然是很多人推荐的方法,但太重了,不建议使用。直接调用EF,无法分离关注点,一般也不推荐。

因此,在排除了两个极端之后,我建议:

  • 查询对象,通常将大型查询分解为一系列查询对象
  • 对于“创建,更新和删除”,我使用DDD风格的访问方法,即,我在实体类中创建了一个方法来更新属性或关系。这样可以隔离EF代码,并使重构或性能优化变得更加容易。
public class ChangePubDateService : IChangePubDateService
{
    private readonly EfCoreContext _context;
 
    public ChangePubDateService(EfCoreContext context)
    {
        _context = context;
    }
 
    public ChangePubDateDto GetOriginal(int id)    
    {
        return _context.Books
            .Select(p => new ChangePubDateDto      
            {                                      
                BookId = p.BookId,                 
                Title = p.Title,                   
                PublishedOn = p.PublishedOn        
            })                                     
            .Single(k => k.BookId == id);          
    }
 
    public Book UpdateBook(ChangePubDateDto dto)   
    {
        var book = _context.Books.Find(dto.BookId);
        book.PublishedOn = dto.PublishedOn;        
        _context.SaveChanges();                    
        return book;                               
    }
}

4.依赖注入

.NetCore 完全支持依赖注入(DI),你也应该顺应时代了。

  • 将每个数据库访问代码都放入仓储库中。
  • 为每个EF存储库类添加一个接口。
  • 在DI提供程序中针对其接口注册EF存储库类。
  • 然后,您需要将其注入到需要它的前端方法中。
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult ChangePubDate(ChangePubDateDto dto,
   [FromServices]IChangePubDateService service)
   {
      service.UpdateBook(dto);
      return View("BookUpdated",
         "Successfully changed publication date");
}

5.建立业务逻辑

实际应用的构建是为了提供某种服务,范围从在计算机上保存文件到管理核反应堆。

现实世界中每个不同的问题都有一组规则,通常称为业务规则,或更通用的名称,称为领域规则。

太多人提DDD了,你可以查阅大量的实践文章,这里只讲一点:

您要解决的业务问题必须驱动整个开发。

关于EF Core是否适用于DDD方法存在很多争论,因为业务逻辑代码通常与映射到数据库的EF实体类是分开的。我们开发时的一大原则是:“不要与您的框架作斗争,寻求保持领域驱动设计的方法,并在框架处于敌对状态时放弃细节”。

  • 重点考虑业务逻辑,定义好数据库结构,解决领域模型和EF模型的出入。
  • 业务逻辑不应分心,编写业务逻辑本身就很困难,将其与除实体类之外的所有其他应用程序层隔离。当编写业务逻辑时,只需要考虑要解决的业务问题。
  • 业务逻辑应该认为它正在处理内存数据
  • 将数据库访问代码隔离到一个单独的项目中
  • 业务逻辑不应直接调用EF Core的SaveChanges

6.使您的EF代码正常工作,然后需要时优化它。

开发的原则是使其工作!

无论哪种方式,这些原则都表明我们应该将性能调优放到最后,并仅在需要时才调整性能。

快速的在EF中开发复杂的数据库访问-至少比使用ADO.NET或Dapper快五倍。不利的一面是EF并不总是会产生性能最好的SQL命令:有时是因为EF没有提供良好的SQL转换,有时是因为我编写的LINQ代码效率不如我想象的那样。

问题是:这有关系吗?

当然优化是需要代价的,后面展示了如何在一系列阶段中改进,每个阶段都变得越来越复杂,并且花费了更多的开发时间:

  • 通过重新排列或完善EF代码来改善基本的EF命令吗?
  • 将部分或全部EF代码转换为直接SQL命令,计算所得的列,存储过程等吗?
  • 是否可以更改数据库结构(例如对数据库进行非规范化)以提高搜索性能?

规划可能的性能调整

尽管我完全同意过早进行性能调整的想法,但是明智的做法是提前计划您可能的性能调整。

结论

每一种技术都是越来越难,因为学习任何新技术都需要花费一些时间才能使其变得平滑自然。

已经有许多伟大的软件思想家,还有一些伟大的原理和实践。因此,当下次我们想做的更好时,不妨去看看其他人的想法。

哦哦,祝您编码愉快!

以上是关于6种方法教你更好的使用EF Core构建应用程序的主要内容,如果未能解决你的问题,请参考以下文章

C# 数据操作系列 - 5. EF Core 入门

EF Core 6.0 导航属性未加载

PostgreSQL 上的 EF Core 批量删除

使用EF Core 6执行原始SQL查询

使用 NET Core 和 EF Core 构建谓词

从 EF Core 5 迁移到 EF Core 6 时出错