使用标量函数实现 EF Core 的实用方法

一.介绍

在构建应用程序时,您可能使用标量函数在数据库端实现一些逻辑。在 SQL 中,标量函数是一种对单个值或少量输入值进行操作并始终返回单个值作为输出的函数。这些函数本质上是可重复使用的代码块,用于对数据执行计算或操作。

以下是标量函数的主要特征。

  • 标量函数只有一个输出。无论标量函数有多少个输入,它始终都会产生一个输出值。
  • 标量函数用于以各种方式转换或修改数据。这可能涉及计算、字符串操作、日期/时间操作等。
  • 通过将复杂的逻辑封装在函数中,标量函数可以简化 SQL 查询并使其更具可读性和可维护性。
  • SQL 附带一组用于常见操作的预定义标量函数。您还可以创建自己的自定义标量函数来满足特定需求。

本教程将演示如何将标量 SQL 函数迁移到数据库以及如何使用 Entity Framework Core(EF Core)调用它。

二.要求

在软件开发中,编码不是第一步。我们应该有一些要求,开发应该从分析这些要求开始。我们计划使用 SQL Server 2019的AdventureWorks2019 数据库,我们需要创建一个函数来计算由其 ID 标识的特定销售报价的总单价。我们将使用 Sales.SalesOrderDetail 表及其 UnitPrice 和 SalesOfferId 列。

三.入门

我更喜欢直接使用 SQL IDE(如 Microsoft SQL Server Management Studio)实现/编写 SQL 函数,然后将其复制到 Visual Studio 进行迁移。这是我们的函数。

CREATE OR ALTER FUNCTION [dbo].[ufn_GetTotalUnitPriceBySalesOfferId]
(@specialOfferId INT
)
RETURNS DECIMAL(16,2)
AS
BEGINDECLARE @result DECIMAL(16,2);SELECT @result = SUM(UnitPrice)FROM Sales.SalesOrderDetail AS SODWHERE SOD.SpecialOfferID = @specialOfferId;RETURN @result;
END

我们将与 EF Core 一起实现基本的 Asp.net Core Web API 项目。创建一个新的 Asp.net Core Web API 项目(在我们的存储库中称为 EfCoreWithScalarFunctionsAPI)。我们计划使用 EF Core,因此我们需要安装与 EF Core 相关的包。打开工具 -> nuget 包管理器 -> 包管理器控制台并输入以下命令。

Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools

要从 Visual Studio 迁移 ufn_GetTotalUnitPriceBySalesOfferId,我们只需生成一个空的迁移文件。为此,只需键入 add-migration Initial 并按回车键。它应该会生成一个空的迁移文件。现在我们需要更新它。最后它应该是这样的。

using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EfCoreWithScalarFunctionsAPI.Migrations
{/// <inheritdoc />public partial class Initial : Migration{/// <inheritdoc />protected override void Up(MigrationBuilder migrationBuilder){migrationBuilder.Sql(@"CREATE FUNCTION ufn_GetTotalUnitPriceBySalesOfferId(@specialOfferId as int)RETURNS DECIMAL(16,2) ASBEGINDECLARE @result as decimal(16,2);SELECT @result = SUM(Unitprice)FROM Sales.SalesOrderDetail AS SODWHERE SOD.SpecialOfferID = @specialOfferId;RETURN @result;END");}/// <inheritdoc />protected override void Down(MigrationBuilder migrationBuilder){migrationBuilder.Sql(@"DROP FUNCTION dbo.ufn_GetTotalUnitPriceBySalesOfferId");}}
}

我们的 SQL 迁移文件已准备就绪,但我们没有任何引用数据库的连接字符串。

这是我们的 appsettings.json 文件。

{"Logging": {"LogLevel": {"Default": "Information","Microsoft.AspNetCore": "Warning"}},"ConnectionStrings": {"AdventureWorksDb": "Data Source=.;Initial Catalog=AdventureWorks2019;Integrated Security=SSPI;TrustServerCertificate=TRUE;"},"AllowedHosts": "*"
}

在项目的根目录中添加一个名为 Database 的文件夹,并添加具有以下内容的 AdventureWorksDbContext。

using Microsoft.EntityFrameworkCore;
namespace EfCoreWithScalarFunctionsAPI.Database
{public class AdventureWorksDbContext : DbContext{public DbSet<SalesOrderDetail> SalesOrderDetails { get; set; }public decimal GetTotalUnitPriceBySpecialOfferId(int salesOfferId)=> throw new System.NotImplementedException();public AdventureWorksDbContext(DbContextOptions<AdventureWorksDbContext> dbContextOptions): base(dbContextOptions){ }protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.HasDbFunction(typeof(AdventureWorksDbContext).GetMethod(nameof(GetTotalUnitPriceBySpecialOfferId), new[] { typeof(int) })).HasName("ufn_GetTotalUnitPriceBySalesOfferId");base.OnModelCreating(modelBuilder);}}
}

我们的 AdventureWorksDbContext 指定了一个名为 GetTotalUnitPriceBySpecialOfferId 的方法。它没有任何实现,因为在运行时它将映射到我们的标量函数。为了正确地与我们的函数交互,我们应该重写 OnModelCreating 方法并在那里构造我们的函数。在模型配置期间,此方法被称为 Entity Framework Core (EF Core),以允许您定义 C# 类如何映射到数据库架构。

四.在 OnModelCreating 中

  1. **modelBuilder.HasDbFunction(…):**此行配置数据库函数(UDF - 用户定义函数),EF Core 可以在构建查询时将其转换为等效的 SQL 函数调用。第一个参数(type of(AdventureWorksDbContext))指定包含 UDF 方法(GetTotalUnitPriceBySpecialOfferId)的类。
  2. .GetMethod(name of (GetTotalUnitPriceBySpecialOfferId), new[]{type (int) }):检索该类中特定方法的反射信息。
  3. **(GetTotalUnitPriceBySpecialOfferId)的名称:**以字符串形式获取方法的名称。
  4. **new[] {type (int) }:**创建一个数组,指定 UDF 采用 int(整数)参数。
  5. **.HasName(“ufn_GetTotalUnitPriceBySalesOfferId”):**这将配置 EF Core 在生成的 SQL 查询中将用于 UDF 的名称。此处,它设置为“ufn_GetTotalUnitPriceBySalesOfferId”(假设这是数据库中 UDF 的实际名称)。
  6. **base.OnModelCreating(modelBuilder);:**这将调用基类的 OnModelCreating 实现,它可能包含特定于您的应用程序的附加模型配置。

最后,此代码告诉 EF Core 将 AdventureWorksDbContext 类中的自定义方法 (GetTotalUnitPriceBySpecialOfferId) 识别为数据库函数。当我们在 LINQ 查询中使用此方法时,EF Core 将使用提供的名称 (“ufn_GetTotalUnitPriceBySalesOfferId”) 将其转换为等效的 SQL 调用。这允许您直接在数据库查询中利用 C# 逻辑。

唯一缺少的项目是我们的 SalesOrderDetail 模型。

using System.ComponentModel.DataAnnotations.Schema;
namespace EfCoreWithScalarFunctionsAPI.Database
{[Table("SalesOrderDetail", Schema = "Sales")]public class SalesOrderDetail{public int SalesOrderDetailId { get; set; }public int SalesOrderId { get; set; }public int? ProductId { get; set; }public decimal UnitPrice { get; set; }public decimal UnitPriceDiscount { get; set; }public decimal LineTotal { get; set; }public int SpecialOfferId { get; set; }}
}

最后,我们需要更新我们的 Program.cs 来识别我们的数据库连接。

现在从 Nuget 包管理器控制台运行 update-database 命令,它应该将我们的标量函数迁移到 AdventureWorks2019 数据库。

五.使用标量函数

为了调用我们新创建的函数,让我们创建一个新的控制器(AdventureWorksController),内容如下。

using EfCoreWithScalarFunctionsAPI.Database;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.EntityFrameworkCore;
namespace EfCoreWithScalarFunctionsAPI.Controllers
{[ApiController][Route("api/[controller]")]public class AdventureWorksController : ControllerBase{private readonly AdventureWorksDbContext _adventureWorksDbContext;public AdventureWorksController(AdventureWorksDbContext adventureWorksDbContext){_adventureWorksDbContext = adventureWorksDbContext;}[HttpGet]public async Task<ActionResult<IEnumerable<SalesOrderDetail>>> GetSalesOrderInformationAsync(){var response = await (from sod in _adventureWorksDbContext.SalesOrderDetailswhere _adventureWorksDbContext.GetTotalUnitPriceBySpecialOfferId(sod.SpecialOfferId) > 10_000select sod).Take(10).ToListAsync();return Ok(response);}}
}

此代码定义了一个名为 AdventureWorksController 的控制器,用于处理与销售订单详细信息相关的 HTTP 请求。它通过构造函数注入 AdventureWorksDbContext 实例以与数据库交互。

GetSalesOrderInformationAsync 方法是检索销售订单详细信息的异步​​操作。它使用 LINQ 查询 SalesOrderDetails 表。

查询筛选详细信息,其中自定义函数 GetTotalUnitPriceBySpecialOfferId 返回关联的 SpecialOfferId 的总单价超过 10,000。然后,它使用 Take(10) 将结果限制为前 10 名。最后,将检索到的详细信息异步转换为列表,并使用 Ok(response) 返回成功的 HTTP 响应(状态代码 200)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/51246.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

商品中心关于缓存热key的解决方案

缓存热key一旦被击穿&#xff0c;流量势必会打到数据库&#xff0c;如果数据库崩了&#xff0c;游戏直接结束。 从两点来讨论&#xff1a;如何监控、如何解决。 如何监控 通过业务评估&#xff1a;比如营销活动推出的商品或者热卖的商品。基于LRU的命令&#xff0c;redis-cl…

doccano安装与使用

1.安装 &#xff08;1&#xff09;创建虚拟环境 conda create -n doccano conda activate doccano &#xff08;2&#xff09;安装doccano pip install doccano &#xff08;3&#xff09;doccano初始化 doccano init doccano createuser --username admin --password pa…

Java整理20

1、数据校验 Validation数据校验&#xff08;1&#xff09;实现org.springframework.validation.Validator接口&#xff0c;在代码中调用这个类&#xff08;2&#xff09;按照BeanValidation方式来校验&#xff0c;通过注解方式&#xff08;3&#xff09;基于方法实现校验&…

chk是什么文件格式 chk文件怎么恢复正常 chkdsk文件损坏怎么修复

在使用电脑和移动存储设备时&#xff0c;有时我们会发现磁盘中出现了大量的chk文件。这些chk文件无法打开&#xff0c;也无法得知其原本内容。那么&#xff0c;这些chk文件是什么呢&#xff1f;又该如何将chk文件恢复正常呢&#xff1f; chk文件是什么&#xff1f; 在我们查看…

Cocos Creator2D游戏开发-(2)Cocos 常见名词

场景&#xff08;Scene): 它一个容器&#xff0c;容纳游戏中的各个元素&#xff0c;如精灵&#xff0c;标签&#xff0c;节点对象。它负责着游戏的运行逻辑&#xff0c;以帧为单位渲染这些内容。就是你理解到的那个场景; 个人理解就是一个画面, 一个游戏不同的关卡,会有不同的…

【前端 12】js事件绑定

JavaScript 事件绑定 在Web开发中&#xff0c;事件绑定是实现用户与网页交互的重要机制。JavaScript 提供了多种方式来绑定和处理事件&#xff0c;使得开发者能够灵活地控制网页的行为。本文将详细介绍JavaScript中事件绑定的两种主要方式&#xff0c;并通过实例演示如何应用这…

Python+Pytest+Allure+Yaml+Pymysql+Jenkins+GitLab运行原理

PythonPytestAllureYamlPymysqlJenkinsGitLab运行原理逻辑及调用关系 GitLab代码仓&#xff1a; Jenkins工作空间&#xff1a; 代码&#xff1a; 测试报告展示&#xff1a;

<Python><paddle>基于python使用百度paddleocr实现车牌识别

前言 paddleocr是百度飞桨的一个文字识别库&#xff0c;准确度非常高&#xff0c;基于其文字识别的基础&#xff0c;将其用于车牌识别。这个识别的准确度是相当高的。 环境配置 系统&#xff1a;windows 平台&#xff1a;visual studio code 语言&#xff1a;python 库&#…

计算机网络知识点面试总结4

#来自ウルトラマンゼロ&#xff08;赛罗&#xff09; 1 传输层提供的服务 1.1 功能 传输层向它上面的应用层提供通信服务&#xff0c;它属于面向部分的最高层&#xff0c;同时也是用户功能中的最底层。 为运行在不同主机上的进程之间提供了逻辑通信。 传输层的功能&#xff1…

基于Gunicorn + Flask + Docker的高并发部署策略

标题&#xff1a;基于Gunicorn Flask Docker的高并发部署策略 引言 随着互联网用户数量的增长&#xff0c;网站和应用程序需要能够处理越来越多的并发请求。Gunicorn 是一个 Python WSGI HTTP 服务器&#xff0c;Flask 是一个轻量级的 Web 应用框架&#xff0c;Docker 是一…

react中如何避免父子组件同时渲染(memo的使用)

1.需求说明 react的渲染机制是父子组件同时渲染&#xff0c;不管子组件是否有变化只要父组件重新渲染了子组件就跟着重新渲染。为了避免不必要的消耗&#xff0c;我们可以使用memo钩子函数 2.使用memo前展示 import { memo,useState } from "react"function Son()…

20240728 每日AI必读资讯

Google Gemini 聊天机器人更新 可以免费使用Gemini 1.5 Flash 1. 引入Gemini 1.5 Flash模型&#xff1a; • 提供更快和更高质量的响应。 • 提升推理和图像理解能力。 • 上下文窗口扩大到 32Ktokens&#xff0c;允许进行更长的对话和处理更复杂的问题。 • 即将支持通过 Goo…

【数据结构】常用数据结构的介绍:理解与应用

文章目录 前言一、介绍二、使用场景三、总结 前言 在计算机科学中&#xff0c;数据结构是我们组织和存储数据的方式&#xff0c;它可以帮助我们高效地执行各种操作&#xff0c;如搜索、插入和删除。从数组和链表&#xff0c;到树和图&#xff0c;不同的数据结构有着不同的优点…

深入理解计算机系统 CSAPP 练习题12.4

我们每次都用read_set初始化ready_set是因为我们每次都处理read_set里的描述符,这是我们希望服务器做的事情.每次一有描述符3或描述符0,select函数会更新ready_set ,我们判断更新后ready_set的情况.然后干对应的事. 由此可以看到select函数的神奇之处,它把一个复杂的事情简单化…

Intel电脑CPU的选择

酷睿 i5/i7/i9 系列至强 Xeon 系列应用场景家用消费级电脑企业服务器工作站PCIe通道数 16X 最多识别到2张显卡&#xff0c;且每张降速为8X 64X 最多支持8张显卡同时使用 内存信道2通道8通道内存容量最大128GB最大6TB工作时长不建议长期不间断连续使用专为365*24不断电使用而设…

Kafka使用案例

1、Kafka 生产者&#xff08;Producer&#xff09;示例 #include <iostream> #include <string> #include <librdkafka/rdkafkacpp.h>class ExampleDeliveryReportCb : public RdKafka::DeliveryReportCb { public:void dr_cb (RdKafka::Message &messa…

centos7安装redis数据库步骤

文章目录 前言步骤1、下载redis并解压到指定路径2、make 和 make install3、配置redis.conf4、制作启动脚本5、授权并启动 前言 我安装了很多次redis&#xff0c;包括redis安装、redis安装、或者使用ansible等自动化构建安装&#xff0c;但是直接用安装包安装还是比较少。 今…

软件测试---网络基础、HTTP

一、网络基础 &#xff08;1&#xff09;Web和网络知识 网络基础TCP/IP 使用HTTP协议访问Web WWW万维网的诞生 WWW万维网的构成 &#xff08;2&#xff09;IP协议 &#xff08;3&#xff09;可靠传输的TCP和三次握手策略 &#xff08;4&#xff09;域名解析服务DNS &#xff0…

【51单片机仿真】基于51单片机设计的广告机系统仿真源码原理图设计文档

效果: 摘要 该系统基于51单片机,通过LED点阵显示字符和简单图案,并实现按键控制。系统可以用于广告机,通过两个按键实现暂停/继续显示和显示方向切换功能。系统包含硬件电路设计和软件编程两部分。 目录 第1章 绪论 第2章 系统分析与总体设计 第3章 系统的硬件结构实现 …

Vue3-拉开序幕的setup

Vue3 中的 setup 是一个新的配置项&#xff0c;值是一个函数。 export default {name: App,setup: function () {} } </script> 和 Vue2 中的 data 一样&#xff0c;我也可以将 setup 简写成为 export default {name: App,setup() {} } setup函数的使用 与 Vue2 不一样…