使用实体框架核心生成和访问存储过程

Posted

技术标签:

【中文标题】使用实体框架核心生成和访问存储过程【英文标题】:Generating and accessing stored procedures using Entity framework core 【发布时间】:2017-10-29 01:26:06 【问题描述】:

我正在使用 Visual Studio 2017 实现 Asp.Net 核心 Web API、实体框架核心、数据库优先方法。我已经设法基于现有数据库生成上下文和类文件。我需要使用我的上下文访问存储过程。在早期版本的实体框架中,通过在向导中选择存储过程对象并生成包含这些对象的 edmx 很简单。然后我可以通过实体框架公开的复杂类型对象访问存储过程。我如何在实体框架核心中做类似的事情。举个例子会有帮助吗?

【问题讨论】:

您可以将 ADO.NET 用于您的 DbContext 一个例子将不胜感激 How to run stored procedures in Entity Framework Core?的可能重复 第 227 行 github.com/hherzl/CatFactory.SqlServer/blob/master/src/… 这有帮助吗? 【参考方案1】:

在带有 edmx 文件的 EF Core 中不存在数据库优先方法。相反,您必须使用 Scaffold-DbContext

安装 Nuget 包 Microsoft.EntityFrameworkCore.Tools 和 Microsoft.EntityFrameworkCore.SqlServer.Design

Scaffold-DbContext "Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models

但这不会得到你的存储过程。还在制作中,跟踪问题#245

但是,要执行存储过程,请使用执行 RAW SQL 查询的FromSql 方法

例如

var products= context.Products
    .FromSql("EXECUTE dbo.GetProducts")
    .ToList();

与参数一起使用

var productCategory= "Electronics";

var product = context.Products
    .FromSql("EXECUTE dbo.GetProductByCategory 0", productCategory)
    .ToList();

var productCategory= new SqlParameter("productCategory", "Electronics");

var product = context.Product
    .FromSql("EXECUTE dbo.GetProductByName  @productCategory", productCategory)
    .ToList();

执行 RAW SQL 查询或存储过程有一定的限制。您不能将其用于 INSERT/UPDATE/DELETE。如果要执行 INSERT、UPDATE、DELETE 查询,请使用 ExecuteSqlCommand

var categoryName = "Electronics";
dataContext.Database
           .ExecuteSqlCommand("dbo.InsertCategory @p0", categoryName);

【讨论】:

运行 Scaffold-DbContext System.ArgumentNullException 后出现以下错误:值不能为空。参数名称:contentRootPath【参考方案2】:

如果您希望结果集与已定义的任何对象相同,则上述示例在执行存储过程时可以正常工作。但是,如果您想要一个不受支持的结果集怎么办?根据 EF Core 2 的开发人员的说法,这是一个即将推出的功能,但今天已经有了一个简单的解决方案。

创建要用于输出的模型。此模型将表示输出,而不是数据库中的表。

namespace Example.EF.Model

    public class Sample
    
        public int SampleID  get; set; 
        public string SampleName  get; set; 
    

然后在您的上下文中添加一个带有您的模型的新 DBSet:

public virtual DbSet<Sample> Sample  get; set; 

然后按照上面的操作,使用你的模型作为输出:

var products = _samplecontext.Sample
      .FromSql($"EXEC ReturnAllSamples id, startdate, enddate").ToList();

我希望这可以帮助任何人。

【讨论】:

这样一来,每当我们使用EF生成模型时,每次都需要手动添加dbset? 您可以将它们放在具有部分类的不同文件中,以使其更容易。 嘿@Sami,你能提供部分类的例子吗? EXEC ReturnAllSamples id, startdate, enddate会生成不带参数的sql,可能会被SQL注入。当您尝试以这种方式执行您的过程时,.NET Core 会给出一个非常彻底的警告消息。最好使用接受的答案是.FromSql("EXECUTE dbo.GetProductByCategory 0", productCategory) @SantiagoTrejo,我认为他的意思是您可以创建您的 DbContext 的一部分(它们作为部分生成)并在那里添加您的 DbSet。这样,如果您重新生成上下文,就不会丢失部分内容。【参考方案3】:

我们在 EF Core 中用于执行存储过程以获取数据的解决方法是使用 FromSql 方法,您可以通过这种方式执行存储过程:

List<Employee> employees = dbcontext.Employee
                    .FromSql("GetAllEmployees").ToList();

但是对于创建、更新和删除,我们使用如下所示的 ExecuteSqlCommand:

var employee = "Harold Javier";
dbcontext.Employee
           .ExecuteSqlCommand("InsertEmployee @emp", employee);

【讨论】:

如果 SP 使用 join 从多个表返回值,那么这将失败,我们应该使用什么方法【参考方案4】:

我的原帖 - https://***.com/a/57224037/1979465

要调用存储过程并将结果放入 EF Core 中的模型列表中,我们必须遵循 3 个步骤。

第 1 步。 您需要添加一个新类,就像您的实体类一样。哪个应该具有您的 SP 中所有列的属性。例如,如果您的 SP 返回两个名为 IdName 的列,那么您的新类应该类似于

public class MySPModel

    public int Id get; set;
    public string Name get; set;

第 2 步。

然后你必须在你的 SP 的 DBContext 类中添加一个 DbQuery 属性。

public partial class Sonar_Health_AppointmentsContext : DbContext

        public virtual DbSet<Booking> Booking  get; set;  // your existing DbSets
        ...

        public virtual DbQuery<MySPModel> MySP  get; set;  // your new DbQuery
        ...

第 3 步。

现在您将能够从您的 DBContext 调用您的 SP 并获取结果。

var result = await _context.Query<MySPModel>().AsNoTracking().FromSql(string.Format("EXEC 0 1", functionName, parameter)).ToListAsync();

我使用的是通用 UnitOfWork & Repository。所以我执行 SP 的功能是

/// <summary>
/// Execute function. Be extra care when using this function as there is a risk for SQL injection
/// </summary>
public async Task<IEnumerable<T>> ExecuteFuntion<T>(string functionName, string parameter) where T : class

    return await _context.Query<T>().AsNoTracking().FromSql(string.Format("EXEC 0 1", functionName, parameter)).ToListAsync();

希望对某人有所帮助!!!

【讨论】:

【参考方案5】:

Rohith / Harold Javier / Sami 提供的解决方案有效。我想补充一点,您可以创建一个单独的 EF6 项目来为结果集生成 C# 类,然后将文件复制到您的 EFCore 项目中。如果您更改存储过程,您可以使用此处讨论的方法更新结果文件:Stored Procedures and updating EDMX

如果需要对应的typescript接口,可以安装这个VS2017扩展typescript定义生成器:https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TypeScriptDefinitionGenerator

还有一些复制,但比手动创建类要少。

编辑:有一个 VS2017 扩展用于生成 dbconext https://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools。它不做存储过程,但它提供了来自 VS 项目的右键菜单项,而不是命令行 Scaffold-DbContext。

【讨论】:

EF6 将是那时要走的路。直到 ef core 才有存储过程支持。【参考方案6】:

如果您需要从 EntityFramework Core 执行 mysql 数据库中的存储过程,下面的代码应该可以工作。

var blogTagId = 1;
var tags = await _dbContext.BlogTags.FromSqlRaw("CALL SP_GetBlogTags(0)", blogTagId).ToListAsync(); 

【讨论】:

以上是关于使用实体框架核心生成和访问存储过程的主要内容,如果未能解决你的问题,请参考以下文章

存储过程参数名称和实体框架

存储过程中返回的地理列未显示在实体框架自动生成的复杂类型中

存储过程和实体框架的性能

VS 2010 - 带有 MySql 存储过程的实体框架似乎不起作用

如何在实体框架中定义存储过程(代码优先)?

使用实体框架和存储过程进行并发检查