# [112]天方科技 ASP.net core 教育訓練 1120427
### 建立up_setCmparea,新增修改資料
`Set NoCount On;`如果有設定,成功時回傳`-1`,沒有設定的情況下,回傳影響的**資料筆數**
```sql=
Create or ALTER PROCEDURE [dbo].[up_setCmparea] @cmp_area int, @cmp_name_abr nvarchar(24), @cmp_name_full nvarchar(45), @upd_name nvarchar(18)
AS
--set nocount on; --回傳Insert update delete 影響筆數
Declare @upd_dt nvarchar(13)
Declare @now datetime = getdate()
set @upd_dt = Format(year(@now) - 1911, '000' ) + Format( @now,'MMddHHmmss', 'en-US' ) --產生目前時間
UPDATE s90_cmparea set cmp_area = @cmp_area, cmp_name_abr = @cmp_name_abr,
cmp_name_full = @cmp_name_full, upd_name = @upd_name, upd_dt = @upd_dt
WHERE cmp_area = @cmp_area
--Insert to row if the Update statement faild
IF ( @@ROWCOUNT = 0 )
BEGIN
INSERT INTO s90_cmparea (cmp_area,cmp_name_abr,cmp_name_full, upd_name, upd_dt )
VALUES ( @cmp_area, @cmp_name_abr, @cmp_name_full, @upd_name, @upd_dt )
--select * from s90_cmparea
Return 100 --100代表是insert
END
Return 200 --200代表是update
```
### 建立up_deleteCmparea,刪除資料
成功會回傳`0`
```sql=
Create or ALTER procedure [dbo].[up_deleteCmparea] @cmp_area int
as
--set nocount on; --傳回insert, update, delete所影響的筆數
delete s90_cmparea
where cmp_area = @cmp_area
```
### 在Demo專案執行反向工程
### `IeduContextProcedures.cs`裡會出現目前建立的Stored Procedure
`returnValue`:在Stored Procedure內設定的回傳值(例如狀態碼)
`cancellationToken`:選擇是否取消非同步機制
```csharp=
public partial interface IeduContextProcedures
{
Task<int> up_deleteCmpareaAsync(int? cmp_area, OutputParameter<int> returnValue = null, CancellationToken cancellationToken = default);
Task<List<up_getCmpareaResult>> up_getCmpareaAsync(OutputParameter<int> returnValue = null, CancellationToken cancellationToken = default);
Task<List<up_getStudentResult>> up_getStudentAsync(decimal? std_key, OutputParameter<int> returnValue = null, CancellationToken cancellationToken = default);
Task<int> up_setCmpareaAsync(int? cmp_area, string cmp_name_abr, string cmp_name_full, string upd_name, OutputParameter<int> returnValue = null, CancellationToken cancellationToken = default);
}
```
### `eduContextProcedures.cs`由程式產生Stored Procedure相關方法
使用`SqlParameter`建立各項參數,最後`_context.Database`由DB物件進行資料處裡
```csharp=
public virtual async Task<int> up_deleteCmpareaAsync(int? cmp_area, OutputParameter<int> returnValue = null, CancellationToken cancellationToken = default)
{
var parameterreturnValue = new SqlParameter
{
ParameterName = "returnValue",
Direction = System.Data.ParameterDirection.Output,
SqlDbType = System.Data.SqlDbType.Int,
};
var sqlParameters = new []
{
new SqlParameter
{
ParameterName = "cmp_area",
Value = cmp_area ?? Convert.DBNull,
SqlDbType = System.Data.SqlDbType.Int,
},
parameterreturnValue,
};
//執行原始sql指令,程式產生時將寫入的字串參數化
var _ = await _context.Database.ExecuteSqlRawAsync("EXEC @returnValue = [dbo].[up_deleteCmparea] @cmp_area", sqlParameters, cancellationToken);
returnValue?.SetValue(parameterreturnValue.Value);
return _;
}
```
### 執行SQL指令的方法
`DbSet.FromSql()`:在 EF Core 7.0 中引入的,以 `FormattableString`作為唯一參數,可防止`SQL injection`,因為所有參數數據都包裝在`DbParameter`中
`DbSet.FromSqlRaw()`:不能防止`SQL injection`, 因此在定義SQL命令時需要格外注意
`Database.ExecuteSql()`:如同`FromSql`,可防止`SQL injection`,透過`DbContext.Database`物件使用
`Database.ExecuteSqlRaw`():如同`FromSqlRaw`,不能防止`SQL injection`,透過`DbContext.Database`物件使用
參考:
[**How to Execute Stored Procedures With EF Core 7**](https://code-maze.com/efcore-execute-stored-procedures/)
### 使用Controller,測試Stored Procedure
```csharp=
[HttpPost("sp/set")]
public async Task<ActionResult<int>> up_setCmparea(s90_cmparea s90_cmparea)
{
var result = await _context.Procedures.up_setCmpareaAsync(s90_cmparea.cmp_area, s90_cmparea.cmp_name_abr, s90_cmparea.cmp_name_full, s90_cmparea.upd_name);
var data = await _context.s90_cmparea.FindAsync(s90_cmparea.cmp_area);
return CreatedAtAction("Gets90_cmparea", new { id = s90_cmparea.cmp_area }, data); //並不會再次執行Action,需要最新資料,要透過手動再次查詢
}
```
### up_deleteCmparea 回傳0代表成功
```csharp=
[HttpDelete("sp/del/{id}")]
public async Task<ActionResult<int>> up_deleteCmparea(int id)
{
var result = await _context.Procedures.up_deleteCmpareaAsync(id);
return result == 0 ? NotFound() : NoContent();
}
```
## 在Edu專案不經過EF Tool使用Stored Procedure
### 如果直接將Demo專案的`up_deleteCmparea()`移植到Edu專案,會發現有Error
```csharp=
[HttpDelete("sp/del/{id}")]
public async Task<ActionResult<int>> up_deleteCmparea(int id)
{
//error
var result = await _context.Procedures.up_deleteCmpareaAsync(id);
return result == 0 ? NotFound() : NoContent();
}
```
### 回到Demo專案的`eduContextProcedures.cs`,將`up_deleteCmpareaAsync()`內容移植到Edu專案的`up_deleteCmparea()`並修改
`ParameterName`和`SqlDbType`可以直接放入`SqlParameter`當參數
使用`Database.ExecuteSqlRaw()`執行SQL指令,`cancellationToken`沒有用到可以去掉
`returnValue`在當初設計Stored Procedure時,有定return value(例如狀態碼)的話可以使用到,沒有的話可以將`parameterreturnValue`相關程式碼刪除,不去抓取
```csharp=
[HttpDelete("sp/del/{id}")]
public async Task<ActionResult<int>> up_deleteCmparea(int id)
{
var parameterreturnValue = new SqlParameter("returnValue", SqlDbType.Int)
{
Direction = ParameterDirection.Output,
};
var sqlParameters = new[]
{
new SqlParameter("cmp_area", SqlDbType.Int)
{
Value = id
},
parameterreturnValue,
};
var result = await _context.Database.ExecuteSqlRawAsync("EXEC @returnValue = [dbo].[up_deleteCmparea] @cmp_area", sqlParameters);
//int returnValue = (int)parameterreturnValue.Value;
return result == 0 ? NotFound() : NoContent();
}
```
### 建立第二支`up_deleteCmparea()`
由於沒有`returnValue`,可以將程式碼簡化,將`id`參數內插至SQL語法,並使用`Database.ExecuteSql()`執行SQL指令,可防止`SQL Injection`
```csharp=
[HttpDelete("sp/del2/{id}")]
public async Task<ActionResult<int>> up_deleteCmparea2(int id)
{
//沒有使用ExecuteSqlRawAsync()可以直接使用內插語法,不會有sql injection風險
var result = await _context.Database.ExecuteSqlAsync($"EXEC [dbo].[up_deleteCmparea] @cmp_area={id}");
return result == 0 ? NotFound() : NoContent();
}
```
### 以相同方式移植`up_setCmparea()`
將參數放入`SqlParameter`只留`Value`
`??`運算子是用來檢查是否為`null`,否的話傳回`??`左邊的值,是的話傳回右邊的值
```csharp=
[HttpPost("sp/set")]
public async Task<ActionResult<int>> up_setCmparea(s90_cmparea s90_cmparea)
{
var parameterreturnValue = new SqlParameter("returnValue", SqlDbType.Int)
{
Direction = ParameterDirection.Output,
};
var sqlParameters = new[]
{
new SqlParameter("cmp_area", SqlDbType.Int)
{
Value = s90_cmparea.cmp_area
},
new SqlParameter("cmp_name_abr", SqlDbType.NVarChar, 48)
{
Value = s90_cmparea.cmp_name_abr ?? Convert.DBNull
},
new SqlParameter("cmp_name_full", SqlDbType.NVarChar, 90)
{
Value = s90_cmparea.cmp_name_full ?? Convert.DBNull
},
new SqlParameter("upd_name", SqlDbType.NVarChar, 36)
{
Value = s90_cmparea.upd_name ?? Convert.DBNull
},
parameterreturnValue,
};
var _ = await _context.Database.ExecuteSqlRawAsync("EXEC @returnValue = [dbo].[up_setCmparea] @cmp_area, @cmp_name_abr, @cmp_name_full, @upd_name", sqlParameters);
int returnValue = (int)parameterreturnValue.Value;
var data = await _context.s90_cmparea.FindAsync(s90_cmparea.cmp_area);
return CreatedAtAction("Gets90_cmparea", new { id = s90_cmparea.cmp_area }, data);
}
```
參考:
[**?? 和??= 運算子 - null 聯合運算子**](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/operators/null-coalescing-operator)
### 建立第二支`up_setCmparea()`
不需要輸出參數的話,可以使用`Database.ExecuteSql()`內插語法
```csharp=
[HttpPost("sp/set2")]
public async Task<ActionResult<int>> up_setCmparea2(s90_cmparea s90_cmparea)
{
var _ = await _context.Database
.ExecuteSqlAsync($"EXEC [dbo].[up_setCmparea]
{s90_cmparea.cmp_area},
{s90_cmparea.cmp_name_abr},
{s90_cmparea.cmp_name_full},
{s90_cmparea.upd_name}");
var data = await _context.s90_cmparea.FindAsync(s90_cmparea.cmp_area);
return CreatedAtAction("Gets90_cmparea", new { id = s90_cmparea.cmp_area }, data);
}
```