使用标量函数实现 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://xiahunao.cn/news/3281405.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Java面试——Tomcat

优质博文&#xff1a;IT_BLOG_CN 一、Tomcat 顶层架构 Tomcat中最顶层的容器是Server&#xff0c;代表着整个服务器&#xff0c;从上图中可以看出&#xff0c;一个Server可以包含至少一个Service&#xff0c;用于具体提供服务。Service主要包含两个部分&#xff1a;Connector和…

Java实现数据库图片上传(包含从数据库拿图片传递前端渲染)-图文详解

目录 1、前言&#xff1a; 2、数据库搭建 &#xff1a; 建表语句&#xff1a; 3、后端实现&#xff0c;将图片存储进数据库&#xff1a; 思想&#xff1a; 找到图片位置&#xff08;如下图操作&#xff09; 图片转为Fileinputstream流的工具类&#xff08;可直接copy&#…

系统学习渗透测试:从零到精通的全面指南

渗透测试&#xff0c;作为网络安全领域的一项重要技术&#xff0c;旨在通过模拟黑客攻击来评估计算机系统的安全性。对于想要系统学习渗透测试的人来说&#xff0c;这既是一条充满挑战的道路&#xff0c;也是一次深入了解网络安全的宝贵机会。本文将从基础知识、技能提升、实战…

【释放品牌魅力,开启营销新篇章】—— 短视频矩阵营销系统源码

【释放品牌魅力&#xff0c;开启营销新篇章】—— 短视频矩阵营销系统在这个数字化高速发展的时代&#xff0c;您是否还在为品牌曝光度不足、营销效果不佳而苦恼&#xff1f;来吧&#xff0c;让我们一起探索全新的解决方案——短视频矩阵营销系统&#xff01; 在这个数字化高速…

NC 缺失的第一个正整数

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定一个无重…

AI初学者必看: 什么是大型语言模型 (LLM)?

介绍 “人工智能&#xff08;AI&#xff09;”一词于 1956 年问世&#xff0c;如今已为大家所熟知。然而&#xff0c;在 ChatGPT 迅速流行之前&#xff0c;AI 的使用和讨论大多局限于科学研究或虚构电影。如今&#xff0c;AI 尤其是生成式 AI 已成为大家热议的话题。 初学者生…

详解校门外的树(树状数组)

前言 在看之前建议先看一下 【学习笔记】详解树状数组-CSDN博客 题目 思路 建立两个树状数组,维护左括号与右括号。 假设有一个长度为10的数轴&#xff0c;我们要将区间[ 2 , 5 ]中种树&#xff0c;这时&#xff0c;我们将 2 处放一个左括号 ” ( ” ,5处放一个 ” )” &…

3DMAX神经网络插件Neuron使用方法详解

3DMAX神经网络插件Neuron使用方法 3DMAX神经网络插件Neuron&#xff0c;从一系列样条曲线创建具有分支结构的几何体。适用于如神经网络、血管、树枝等形状的3D建模。 【适用版本】 3dMax2016及更高&#xff08;不仅限于此范围&#xff09; 【安装方法】 Neuron插件无需安装&a…

【C++】跳转语句-continue语句

continue语法特点&#xff1a; 中止循环后会继续执行下面循环&#xff08;除了continue所跳出的那些执行操作不会执行&#xff09; 这也是额continue语句和break语句最大的区别 break是直接跳出循环不再执行下面步骤 #include<iostream> using namespace std;int main…

收集树中的金币

提示 1 定义一个点的度数为其邻居个数。如果一个点的度数为 1&#xff0c;那么这个点叫做叶子节点&#xff0c;例如示例 2 的 3,4,6,7 都是叶子节点。 如果叶子节点没有金币&#xff0c;我们有必要移动到叶子节点吗&#xff1f;没有必要。 那么可以先把这些没有金币的叶子节点…

等保学习干货|等保测评2.0技术中间件自查阶段,零基础入门到精通,收藏这一篇就够了

0x01 前言 以下是根据我国网络安全体系制订的一系列保护流程进行的等级保护测评。该测评针对已有和将上线的业务服务的基础设施&#xff08;系统、数据库、中间件等&#xff09;&#xff0c;执行一系列检查以确保安全合规。本次先行分享学习等保中的技术自查阶段知识&#xff…

Android GreenDao 升级 保留旧表数据

Android GreenDao 升级 保留旧表数据 大川的川关注IP属地: 北京 0.2052019.08.05 11:54:36字数 270阅读 363 瓦力和伊娃 GreenDao升级库版本号之后&#xff0c;以前的旧数据没有了&#xff0c;为啥&#xff0c;因为GreenDao在升级的时候会删除旧库&#xff0c;创建新库&#…

【超详细含图】Ubuntu系统忘记root密码的解决方法

1.启动或者重启Ubuntu长按shift进入grub菜单&#xff1b; 选第二个&#xff0c;按住e进入 2.选择recovery mode进入Recovery Menu界面&#xff0c; 选择root Drop to root shell prompt* 3.修改root密码操作&#xff1a; #passwd 输入新密码&#xff1a;# 再输入一遍密码&…

LLM之本地部署GraphRAG(GLM-4+Xinference的embedding模型)(附带ollma部署方式)

前言 有空再写 微软开源的GraphRAG默认是使用openai的接口的&#xff08;GPT的接口那是要money的&#xff09;&#xff0c;于是就研究了如何使用开源模型本地部署。 源码地址&#xff1a;https://github.com/microsoft/graphrag 操作文档&#xff1a;https://microsoft.git…

springBoot+protobuf(全程Protocol Buffers协议)简单入门

了解Protocol Buffers协议 Protocal Buffers是google推出的一种序列化协议&#xff0c;用于结构化的数据序列化、反序列化。 官方解释&#xff1a;Protocol Buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法&#xff0c;它可用于&#xff08;数据&#xff09;通…

鸿蒙(API 12 Beta2版)NDK开发【使用Node-API接口进行异步任务开发】

使用Node-API接口进行异步任务开发 场景介绍 napi_create_async_work是Node-API接口之一&#xff0c;用于创建一个异步工作对象。可以在需要执行耗时操作的场景中使用&#xff0c;以避免阻塞主线程&#xff0c;确保应用程序的性能和响应性能。例如以下场景&#xff1a; 文件…

入门 PyQt6 看过来(案例)17~ 表格

PyQt6提供了两种用于有规律地呈现更多数据的控件&#xff0c;一种是表格结构的控件(QTableView)&#xff0c;另一种是树形结构的控件(QTreeView)。表格控件属于QTableView类&#xff0c;QTableWidget继承于QTableView。 1 QTableView 表格控件 QTableView控件中QStandItemMod…

IT人求职就业手册:如何在数字时代脱颖而出

&#x1f482; 个人网站:【 摸鱼游戏】【网址导航】【神级代码资源网站】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

【CodinGame】趣味算法(教学用) CLASH OF CODE -20240731

文章目录 正文闰年偶数和密码塔楼高度 写在最后END 正文 闰年 import sys import math# Auto-generated code below aims at helping you parse # the standard input according to the problem statement.a int(input()) b int(input()) count0 for i in range(a, b 1):if…

DELL服务器RAID配置详细教程

DELL服务器RAID配置教程 在启动电脑的时候按CTRLR 进入 RAID 设置见面如下图 名称解释&#xff1a; Disk Group&#xff1a;磁盘组&#xff0c;这里相当于是阵列&#xff0c;例如配置了一个RAID5&#xff0c;就是一个磁盘组 VD(Virtual Disk)&#xff1a; 虚拟磁盘&#xff…