# 9.9、存储过程、视图、函数操作

## 存储过程

**在Hoa Framework v1.4.0 版本新增了 切面上下文功能，也就是下面的代码不再推荐使用，见** [**9.12、切面上下文（TangentDbContext）**](https://monksoul.gitbook.io/hoa/shujukucaozuoshinan/qiemianshagnxiawen)**章节。**

### 无需配置 DbSet 方式调用存储过程（推荐）

正常情况下，存储过程也需要和实体一样，需要配置 `DbSet` 和 [无键实体类型](https://docs.microsoft.com/zh-cn/ef/core/modeling/keyless-entity-types?tabs=data-annotations)，而且只能返回指定存储过程指定的返回实体模型，这样比较繁琐。（[见 DbSet 方式](https://monksoul.gitbook.io/hoa/shujukucaozuoshinan/cunchuguochengshituhanshucaozuo#pei-zhi-dbset-fang-shi-tiao-yong-cun-chu-guo-cheng)）

所以，Hoa Framework 提供了更加简便的方式。**支持返回任意数据类型**。代码如下：

```csharp
// 返回存储过程实体模型
_testRepository.SqlProcedureQuery<PROC_Entity>(DbConsts.Procedure.PROC_NAME_KEY, new (){});

// 返回任意类型
_testRepository.SqlProcedureQuery<int>(DbConsts.Procedure.PROC_NAME_KEY, new (){})
```

这种方式最大的好处就是可使用任何仓储对象进行执行存储过程，是**极速开发推荐方式**。

### 配置 `DbSet` 方式调用存储过程

**在Hoa Framework v1.4.0 版本新增了 切面上下文功能，也就是下面的代码不再推荐使用，见** [**9.12、切面上下文（TangentDbContext）**](https://monksoul.gitbook.io/hoa/shujukucaozuoshinan/qiemianshagnxiawen)**章节。**

由于存储过程是[无键实体类型](https://docs.microsoft.com/zh-cn/ef/core/modeling/keyless-entity-types?tabs=data-annotations)，所以，需要手动配置 `DbSet` 和 创建存储过程返回值 `Entity` 模型。

在 Hoa Framework 框架中，需要手动配置在 `Hoa.EntityFramework.Core.ManualHoaDbContext.cs` 文件中，避免被 `Database First` 生成器每次生成后覆盖掉。代码如下：

```csharp
using Microsoft.EntityFrameworkCore;
using System;
using Hoa.Core;

namespace Hoa.EntityFrameworkCore
{
    public partial class HoaDbContext
    {
        // PROC_Entity 是存储过程返回值对应的实体模型
        public virtual DbSet<PROC_Entity> PROC_Entities { get; set; }

        partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
        {
            // 配置存储过程无键实体模型
            modelBuilder.Entity<PROC_Entity>(entity =>
            {
                entity.HasNoKey();
                // 通常我们需要将存储过程名称作为常量定义起来
                // 这里的存储过程名称配置在 Hoa.Core.DbConsts.cs 文件中
                entity.ToView(DbConsts.Procedure.PROC_NAME_KEY);
            });
        }
    }
}
```

通过上述配置后，即可通过 `IRepository<PROC_Entity>` 获得存储过程操作仓储对象，然后调用 `FromSql` 方式调用存储过程。如：

```csharp
// DbConsts.Procedure.PROC_NAME_KEY 仓储过程名称
// 第二个参数为存储过程的参数
_procEntityRepository.FromSql(DbConsts.Procedure.PROC_NAME_KEY, new (){});
```

这种方式只能返回指定存储过程指定的返回实体模型。

### 存储过程参数说明

由于存储过程存在参数且参数名可能和参数模型不一致，所以 Hoa Framework 内置了强大存储过程参数配置特性。

默认情况下，我们可以通过 `匿名对象` 作为存储过程的参数，这种做法的好处就是无需创建一个 `存储过程参数模型`，但是缺点也显而易见，就是必须严格按照存储过程定义的参数排序进行传入。而且`匿名对象属性名`也必须和存储过程参数名一一对应。

所以，Hoa Framework **建议开发者使用`强类型模型`作为存储过程参数模型**，这样无需考虑排序，又能指定存储过程参数名称。

#### :flag\_black: 示例一：参数模型和存储过程名字一致

```csharp
_testRepository.SqlProcedureQuery<PROC_Entity>("存储过程名"
                , new ProcModel(){ Name = "Hoa", Age = 27 });
```

#### :flag\_black: 示例二：指定存储过程的实际参数，在存储过程参数模型中设置

```csharp
using Hoa.DbManager.Attributes;
using System;
using System.Collections.Generic;
using System.Text;

namespace Hoa.Core.Utilities.Entities
{
    public class ProcModel
    {
        // 可以指定存储过程实际参数名
        [ProcedureParameter("ACCOUNT_NAME")]
        public string Name { get; set; }
        
        public string Age { get; set; }
    }
}

```

这种方式可维护性强，也利于拓展。

## 视图

视图是一张虚拟表，也是[无键实体类型](https://docs.microsoft.com/zh-cn/ef/core/modeling/keyless-entity-types?tabs=data-annotations)，所以，需要手动配置 `DbSet` 和 创建视图返回值 `Entity` 模型，这一点和存储过程配置无异。

### 配置视图`DbSet`&#x20;

在 Hoa Framework 框架中，需要手动配置在 `Hoa.EntityFramework.Core.ManualHoaDbContext.cs` 文件中，避免被 `Database First` 生成器每次生成后覆盖掉。代码如下：

```csharp
using Microsoft.EntityFrameworkCore;
using System;
using Hoa.Core;

namespace Hoa.EntityFrameworkCore
{
    public partial class HoaDbContext
    {
        // VIEW_Entity 是视图返回值对应的实体模型
        public virtual DbSet<VIEW_Entity> VIEW_Entities { get; set; }

        partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
        {
            // 配置视图无键实体模型
            modelBuilder.Entity<PROC_Entity>(entity =>
            {
                entity.HasNoKey();
                // 通常我们需要将视图名称作为常量定义起来
                // 这里的视图配置在 Hoa.Core.DbConsts.cs 文件中
                entity.ToView(DbConsts.TableView.VIEW_NAME_KEY);
            });
        }
    }
}
```

通过上述配置后，即可通过 `IRepository<VIEW_Entity>` 获得视图仓储对象，之后就可以使用**仓储中除了 `增删改` 以外的操作了**！**通常视图只用作查询和权限控制**。如：

### 视图使用

```csharp
// 查询操作
_tableViewRepository.Entity.Where(u => u.Id == 1 && u.Name.Contains("Hoa"));

_tableViewRepository.Entity
    .Where(u => u.Void == 0)
    .WhereIf(!string.IsNullOrEmpty(name),u => u.Name.Contains(name))
    .WhereIf(age > 18,u => u.Age == age)
    .Where(u => u.Field > 20);
```

更多视图仓储的操作可查看[ 9.5、查询操作](https://monksoul.gitbook.io/hoa/shujukucaozuoshinan/chaxuncaozuo) 文档。

## 函数

**在Hoa Framework v1.4.0 版本新增了 切面上下文功能，也就是下面的代码不再推荐使用，见** [**9.12、切面上下文（TangentDbContext）**](https://monksoul.gitbook.io/hoa/shujukucaozuoshinan/qiemianshagnxiawen)**章节。**

数据库中函数的调用非常简单，无需配置 `DbSet` 模型，也无需创建特定的 `IRepository<>` 仓储对象。

### 函数直接调用

```csharp
_repository.SqlScalarFunctionQuery<返回值类型>("方法名称",new FunctionModel{});

// 还可以指定返回值列名
_repository.SqlScalarFunctionQuery<返回值类型>("方法名称",new FunctionModel{},"指定结果集列名");
```

**注意：这种方式只能单纯的输出函数的返回值，并不能联合 `sql` 进行查询。**

### 数据库内置函数使用

EF Core 为我们提供了很多常用的内置函数，可以在 Lambda 条件中使用，主要是通过  `EF.Functions` 调用，如：

```csharp
_testRepository.Entity
    .Where(u => EF.Functions.DateDiffHour(u.CreatedDt, DateTime.Now) > 8)
    .FirstOrDefault();
```

这个语句使用了 `EF.Functions.DateDiffHour` 最终生成的 `Sql` 如下：

```sql
SELECT TOP(1) [a].*
FROM [dbo].[TEST] AS [a]
WHERE DATEDIFF(HOUR, [a].[CREATED_DT], GETDATE()) > 8
```

也就是生成了对应 `DATEDIFF` 函数。

更多内置的EF函数[可查看官方文档](https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbfunctions?view=efcore-3.1)。

### 自定义标量函数

这个有区别与上面的 **函数调用，**&#x4E0A;面的 **函数调用** 只能用于单纯的调用函数，并不能在 Lambda 语句中使用。

而通过自定义标量函数的方式，可以实现在 `Lambda` 和 `Linq` 中调用。

假设我们数据库有一个自定义标量函数：

```sql
CREATE FUNCTION FN_GET_YEAR
(
	@DATE DATETIME
)
RETURNS VARCHAR(100)
AS
BEGIN
	RETURN YEAR(@DATE)
END
```

接下来

**第一步**：我们需要在 `Hoa.Core.DbScalarFunctions.cs` 中申明：

```csharp
using Microsoft.EntityFrameworkCore;
using System;

namespace Hoa.Core
{
    public static class DbScalarFunctions
    {
        // Name 为 数据库标量函数中真实的名称（必填）
        // Schema 为 数据库标量函数所在的 Schema（必填）
        [DbFunction(Name = "FN_GET_YEAR", Schema = "dbo")]
        public static string GetYear(DateTime date) => throw new NotSupportedException();
    }
}

```

第二步：需要在 `Hoa.EntityFramework.Core.ManualHoaDbContext.cs` 中配置中初始化：

```csharp
using Microsoft.EntityFrameworkCore;
using System;
using Hoa.Core;

namespace Hoa.EntityFrameworkCore
{
    public partial class HoaDbContext
    {
        partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
        {
            // 注意：如果你的函数有多少个参数，就填写多少个 default 
            modelBuilder.HasDbFunction(() => DbScalarFunctions.GetYear(default));
        }
    }
}

```

配置好上面两步后，就可以使用该函数了：

```csharp
_testRepository.Entity
    .Where(u => DbScalarFunctions.GetYear(u.CreatedDt) == "2020")
    .FirstOrDefault();
```

最终生成的 `Sql` 如下：

```sql
SELECT TOP(1) [a].*
FROM [dbo].[TEST] AS [a]
WHERE [dbo].[FN_GET_YEAR]([a].[CREATED_DT]) = N'2020'
```

这种自定义标量函数除了可以在 `Where` 条件中调用，还能在 `Select` 结果集中使用。如：

```csharp
_testRepository.Entity
    .Where(u => DbScalarFunctions.GetYear(u.CreatedDt) == "2020")
    .Select(u => new { Year = DbScalarFunctions.GetYear(u.CreatedDt) })
    .FirstOrDefault();
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://monksoul.gitbook.io/hoa/shujukucaozuoshinan/cunchuguochengshituhanshucaozuo.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
