Introduction

In enterprise applications, especially in ERP / CRM, all business logic is often demanded to Stored Procedures.
Stored procedures are a powerful tool for developers whose know T-SQL to elaborate complex query or to implement core business logic with an eye on database performance.
Unluckily, in the new .NET Core 3, stored procedure support is not complete.
  1. You have to use FromSqlRaw ( or Query ) and compose your own SQL Command
  2. The resulting structure of the Stored Procedure must have an Entity Structure
  3. There is not a parameter mapping so each time you modify a stored procedure by adding a new parameter you have to manually fix the SQL Command
  4. There is not a scaffolding function that imports all the stored procedure
(Please refer to this article if you want to go deep on how to call stored procedure)
In today's article, I will show you how SPToCore utility can help you to manage a lot of stored procedures.

What is SPToCore ???

SPToCore is a utility that scaffolds all database stored procedures and creates a new dbContext that implements stored procedure methods, results, and parameter mappings.
You can find it on GitHub.
Step 1
First, create your existing SQL server database named "DB_WITH_SP" by executing the following SQL script:
  1. USE [DB_WITH_SP]
  2. GO
  3. /****** Object: Table [dbo].[anMaterial] Script Date: 12/05/2020 16:57:53 ******/
  4. SET ANSI_NULLS ON
  5. GO
  6. SET QUOTED_IDENTIFIER ON
  7. GO
  8. CREATE TABLE [dbo].[anMaterial](
  9. [idMaterial] [int] IDENTITY(1,1) NOT NULL,
  10. [Code] [varchar](50) NULL,
  11. [Material] [varchar](255) NULL,
  12. [Cost] [decimal](12, 2) NULL,
  13. [idMaterialType] [int] NULL,
  14. CONSTRAINT [PK_anMaterial] PRIMARY KEY CLUSTERED
  15. (
  16. [idMaterial] ASC
  17. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
  18. ) ON [PRIMARY]
  19. GO
  20. /****** Object: Table [dbo].[mdMaterialType] Script Date: 12/05/2020 16:57:53 ******/
  21. SET ANSI_NULLS ON
  22. GO
  23. SET QUOTED_IDENTIFIER ON
  24. GO
  25. CREATE TABLE [dbo].[mdMaterialType](
  26. [idMaterialType] [int] NOT NULL,
  27. [Code] [varchar](50) NULL,
  28. [MaterialType] [varchar](255) NULL,
  29. CONSTRAINT [PK_mdMaterialType] PRIMARY KEY CLUSTERED
  30. (
  31. [idMaterialType] ASC
  32. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
  33. ) ON [PRIMARY]
  34. GO
  35. /****** Object: View [dbo].[vw_anMaterial] Script Date: 12/05/2020 16:57:53 ******/
  36. SET ANSI_NULLS ON
  37. GO
  38. SET QUOTED_IDENTIFIER ON
  39. GO
  40. CREATE VIEW [dbo].[vw_anMaterial]
  41. AS
  42. SELECT m.idMaterial,
  43. m.Code,
  44. m.Material,
  45. m.Cost,
  46. m.idMaterialType,
  47. MaterialTypeCode = t.Code,
  48. t.MaterialType
  49. FROM anMaterial m LEFT JOIN mdMaterialType t ON t.idMaterialType = m.idMaterialType
  50. GO
  51. SET IDENTITY_INSERT [dbo].[anMaterial] ON
  52. GO
  53. INSERT [dbo].[anMaterial] ([idMaterial], [Code], [Material], [Cost], [idMaterialType]) VALUES (1, N'MAT001', N'Cement', CAST(1000.00 AS Decimal(12, 2)), 1)
  54. GO
  55. INSERT [dbo].[anMaterial] ([idMaterial], [Code], [Material], [Cost], [idMaterialType]) VALUES (2, N'MAT002', N'Raw Meal', CAST(500.00 AS Decimal(12, 2)), 3)
  56. GO
  57. INSERT [dbo].[anMaterial] ([idMaterial], [Code], [Material], [Cost], [idMaterialType]) VALUES (3, N'MAT003', N'Clinker', CAST(750.00 AS Decimal(12, 2)), 2)
  58. GO
  59. INSERT [dbo].[anMaterial] ([idMaterial], [Code], [Material], [Cost], [idMaterialType]) VALUES (4, N'MAT004', N'Liquid Additive', CAST(50.00 AS Decimal(12, 2)), 3)
  60. GO
  61. INSERT [dbo].[anMaterial] ([idMaterial], [Code], [Material], [Cost], [idMaterialType]) VALUES (5, N'MAT005', N'Intermediate Binder', CAST(350.00 AS Decimal(12, 2)), 2)
  62. GO
  63. INSERT [dbo].[anMaterial] ([idMaterial], [Code], [Material], [Cost], [idMaterialType]) VALUES (6, N'MAT005', N'Clay', CAST(200.00 AS Decimal(12, 2)), 3)
  64. GO
  65. INSERT [dbo].[anMaterial] ([idMaterial], [Code], [Material], [Cost], [idMaterialType]) VALUES (7, N'MAT007', N'C4', CAST(100.00 AS Decimal(12, 2)), 3)
  66. GO
  67. SET IDENTITY_INSERT [dbo].[anMaterial] OFF
  68. GO
  69. INSERT [dbo].[mdMaterialType] ([idMaterialType], [Code], [MaterialType]) VALUES (1, N'FIN_PRO', N'Finish Product')
  70. GO
  71. INSERT [dbo].[mdMaterialType] ([idMaterialType], [Code], [MaterialType]) VALUES (2, N'INT_PRO', N'Intermediate Product')
  72. GO
  73. INSERT [dbo].[mdMaterialType] ([idMaterialType], [Code], [MaterialType]) VALUES (3, N'RAW_MAT', N'Raw Material')
  74. GO
  75. /****** Object: StoredProcedure [dbo].[Material_GetByCode] Script Date: 12/05/2020 16:57:53 ******/
  76. SET ANSI_NULLS ON
  77. GO
  78. SET QUOTED_IDENTIFIER ON
  79. GO
  80. CREATE PROC [dbo].[Material_GetByCode](
  81. @Code VARCHAR(50)
  82. )
  83. AS
  84. SELECT m.idMaterial,
  85. m.Code,
  86. m.Material,
  87. m.Cost,
  88. m.idMaterialType,
  89. m.MaterialTypeCode,
  90. m.MaterialType
  91. FROM vw_anMaterial m WHERE m.Code = @Code
  92. GO
  93. /****** Object: StoredProcedure [dbo].[Material_GetById] Script Date: 12/05/2020 16:57:53 ******/
  94. SET ANSI_NULLS ON
  95. GO
  96. SET QUOTED_IDENTIFIER ON
  97. GO
  98. CREATE PROC [dbo].[Material_GetById](
  99. @idMaterial INT
  100. )
  101. AS
  102. SELECT m.idMaterial,
  103. m.Code,
  104. m.Material,
  105. m.Cost,
  106. m.idMaterialType,
  107. m.MaterialTypeCode,
  108. m.MaterialType
  109. FROM vw_anMaterial m WHERE m.idMaterial = @idMaterial
  110. GO
  111. /****** Object: StoredProcedure [dbo].[Material_NEW] Script Date: 12/05/2020 16:57:53 ******/
  112. SET ANSI_NULLS ON
  113. GO
  114. SET QUOTED_IDENTIFIER ON
  115. GO
  116. CREATE PROC [dbo].[Material_NEW]
  117. (
  118. @Code VARCHAR(50),
  119. @Material VARCHAR(255),
  120. @Cost DECIMAL(12,2) NULL,
  121. @idMaterialType INT,
  122. @idMaterial INT OUT
  123. )
  124. AS
  125. INSERT INTO anMaterial
  126. (
  127. Code,
  128. Material,
  129. Cost,
  130. idMaterialType
  131. )
  132. VALUES ( @Code, @Material, @Cost, @idMaterialType )
  133. SET @idMaterial = @@IDENTITY
  134. GO
  135. /****** Object: StoredProcedure [dbo].[Material_SEARCH] Script Date: 12/05/2020 16:57:53 ******/
  136. SET ANSI_NULLS ON
  137. GO
  138. SET QUOTED_IDENTIFIER ON
  139. GO
  140. CREATE PROC [dbo].[Material_SEARCH]
  141. (
  142. @Code VARCHAR(50) NULL,
  143. @Material VARCHAR(255) NULL,
  144. @idMaterialType INT NULL
  145. )
  146. AS
  147. SELECT m.idMaterial,
  148. m.Code,
  149. m.Material,
  150. m.Cost,
  151. m.idMaterialType,
  152. m.MaterialTypeCode,
  153. m.MaterialType
  154. FROM vw_anMaterial m
  155. WHERE m.Code = ISNULL(@Code,m.Code)
  156. AND m.Material LIKE '%' + ISNULL(@Material,m.Material) + '%'
  157. AND m.idMaterialType = ISNULL(@idMaterialType, m.idMaterialType)
  158. GO
  159. /****** Object: StoredProcedure [dbo].[Material_UPD] Script Date: 12/05/2020 16:57:53 ******/
  160. SET ANSI_NULLS ON
  161. GO
  162. SET QUOTED_IDENTIFIER ON
  163. GO
  164. CREATE PROC [dbo].[Material_UPD]
  165. (
  166. @idMaterial INT,
  167. @Material VARCHAR(255),
  168. @Cost DECIMAL(12,2) NULL,
  169. @idMaterialType INT NULL
  170. )
  171. AS
  172. UPDATE anMaterial
  173. SET Material = @Material,
  174. Cost = @Cost,
  175. idMaterialType = @idMaterialType
  176. WHERE idMaterial = @idMaterial
  177. GO
In the above script, I have created two simple tables ( anMaterial, mdMaterialType ), one view ( vw_anMaterial ) with five stored procedures:
  1. Material_NEW: create a new material
  2. Material_UPD: update an existing material
  3. Material_GetById: get the material by his id
  4. Material_GetByCode: get the material by his code
  5. Material_SEARCH: search for material by his description ( Material column )
Step 2
Now, create a new .NET Core console application project and name it "myStoredProcedureCalls" as shown below:
Learn About Stored Procedure Scaffolding Utillity For .NET Core 3
Learn About Stored Procedure Scaffolding Utillity For .NET Core 3
Note
I will put the console application in "C:\Temp" folder, just to simplify the example.
Step 3
Build the solution and ensure that the build is successful.
Step 4
Add a new folder to the solution called "Model" and add all the libraries necessaries to scaffold the database:
  1. Right click on Solution --> Add new Folder ( call it "Model" )
  2. Open Package Manager console and run,

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


  3. Scaffold the DB_WITH_SP database by using the following command (review connectionstring!!!),

    Scaffold-DbContext "Data Source=DN1;Initial Catalog=DB_WITH_SP;Persist Security Info=True;User ID=sa;Password=secret" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Model
If everything is ok, your solution will look like this:
Learn About Stored Procedure Scaffolding Utillity For .NET Core 3
Step 4
Now it's time to implement the stored procedure methods by using the SPToCore utility!
First of all download SPToCore.exe from the link below (sorry for the file size, it's a single file compiled .NET Core application )
https://github.com/DarioN1/SPToCore/tree/master/SPToCore/dist
Then open a command prompt and run the following command from the SPToCore.exe folder.
SPToCore.exe scan -cnn "Data Source=DN1;Initial Catalog=DB_WITH_SP;Persist Security Info=True;User ID=sa;Password=password;" -sch * -nsp myStoredProcedureCalls -ctx DB_WITH_SPContext -sf Model -pf C:\Temp\myStoredProcedureCalls\myStoredProcedureCalls\Model\ -f SPToCoreContext.cs
If everything is OK, you will see these console logs:
Learn About Stored Procedure Scaffolding Utillity For .NET Core 3
You will find a new file in the solution:
Learn About Stored Procedure Scaffolding Utillity For .NET Core 3
This SPToCoreContext.cs is a new dbContext that implements all the stored procedures!
Step 4
Its time to test the stored procedures.
Now open your Program.cs and add the following code and run the program.
  1. static void Main(string[] args)
  2. {
  3. Console.WriteLine("Hello, I'm SPToCore!");
  4. using (SPToCoreContext db = new SPToCoreContext()) {
  5. var mat = db.Material_GetByIdAsync(1).Result.FirstOrDefault();
  6. Console.WriteLine($"TEST1 : The material with id 1 is: {mat.Material}");
  7. var mat2 = db.Material_GetByCodeAsync("MAT002").Result.FirstOrDefault();
  8. Console.WriteLine($"TEST2 : The material with code MAT001 is: {mat2.Material}");
  9. var allMaterials = db.Material_SEARCHAsync(null, null, null);
  10. foreach (var m in allMaterials.Result) {
  11. Console.WriteLine($"TEST3 : {m.Code} {m.Material}");
  12. }
  13. //now lets create new material
  14. int? idMaterialNew = null;
  15. Random random = new Random();
  16. int randomNumber = random.Next(0, 1000);
  17. db.Material_NEW($"TESTNEW{randomNumber}", $"Material Test {randomNumber}", 0, 1, ref idMaterialNew);
  18. var mat4 = db.Material_GetByIdAsync(idMaterialNew).Result.FirstOrDefault();
  19. Console.WriteLine($"TEST4 : The new material is: {mat4.Code} {mat4.Material}");
  20. //now update the material by setting a new cost
  21. db.Material_UPD(idMaterialNew, mat4.Material, 100, mat4.idMaterialType);
  22. mat4 = db.Material_GetByIdAsync(idMaterialNew).Result.FirstOrDefault();
  23. Console.WriteLine($"TEST5 : The new material cost for {mat4.Code} {mat4.Material} is {mat4.Cost}");
  24. Console.WriteLine();
  25. Console.ReadLine();
  26. }
  27. }
As you can see, the program calls the five stored procedures that were just imported...
If everything is OK, you should see these logs in your console:
Learn About Stored Procedure Scaffolding Utillity For .NET Core 3

Conclusion

In this article, I showed you a useful utility to extend your dbContext by adding a coder-friendly stored procedure support for efcore.
The utility must be strongly tested but its a good starting point to bring a little sp support in efcore environment.
I'm personally using this utility to migrate a .Net 4.5 web API project who implements more than 300 stored procedures to a new .NET Core 3.1 project, and I can say that it works well.