使用Entity Framework Core生成和访问存储过程

29

我正在使用Visual Studio 2017 实现 Asp.Net Core Web API,采用基于数据库的方法(database first approach),使用Entity Framework Core生成上下文和类文件。我已经成功生成了基于现有数据库的上下文和类文件。我需要使用我的上下文访问存储过程。在以前的版本中,通过向导选择存储过程对象并生成包含这些对象的edmx即可实现。然后,我可以通过Entity Framework公开的复杂类型对象访问存储过程。如何在Entity Framework Core中做类似的事情?请举个例子。


您可以使用 ADO.NET 作为 DbContext。 - H. Herzl
一个例子会很有帮助。 - Tom
可能是如何在Entity Framework Core中运行存储过程?的重复问题。 - Ilya Chumakov
请问这个有帮助吗? https://github.com/hherzl/CatFactory.SqlServer/blob/master/src/CatFactory.SqlServer/SqlServerDatabaseFactory.cs 的第227行 - H. Herzl
6个回答

31

在 EF Core 中,使用 edmx 文件没有数据库优先的方法。相反,您必须使用 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

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

例如:

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();

执行原始SQL查询或存储过程存在某些限制。您不能将其用于插入/更新/删除。如果想要执行插入、更新、删除查询,请使用ExecuteSqlCommand。

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

在运行 Scaffold-DbContext 后,我收到以下错误 System.ArgumentNullException: 值不能为空。 参数名:contentRootPath - Tom
我已经安装了这些软件包,但是没有找到 FromSql 方法。有什么想法吗? - Jeroen van Langen

13

以上示例在执行存储过程时,如果您期望结果集与已定义的任何对象相同,则可以正常工作。但是,如果您想要不受支持的结果集怎么办?根据 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吗? - Kunal Kakkad
2
你可以使用部分类将它们放在不同的文件中,以便更容易地管理。 - Sami
嘿 @Sami ,你能提供一下部分类的例子吗? - Máster
2
EXEC ReturnAllSamples {id}, {startdate}, {enddate} will generate sql without parameters and can be subject to SQL injection. .NET Core gives a very thorough warning message when you try to execute your procedure in this way. It's best to use the way the accepted answer is .FromSql("EXECUTE dbo.GetProductByCategory {0}", productCategory) - dst3p
@SantiagoTrejo,我认为他的意思是你可以创建一个DbContext的部分类(它们生成为部分类),并在其中添加你的DbSet<>。这样,如果你重新生成你的上下文,你就不会失去这个部分类。 - dst3p

6

我的原帖:https://dev59.com/Fl4b5IYBdhLWcg3w3k-Y#57224037

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

第一步。您需要添加一个新类,就像您的实体类一样。该类应具有与 SP 中所有列对应的属性。例如,如果您的 SP 返回名为 IdName 的两个列,则您的新类应该是:

public class MySPModel
{
    public int Id {get; set;}
    public string Name {get; set;}
}

第二步。

接下来,您需要在您的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
        ...
}

第三步

现在,您将能够从您的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();
}

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


3

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

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

但是对于创建、更新和删除,我们使用像下面这样的ExecuteSqlCommand:
var employee = "Harold Javier";
dbcontext.Employee
           .ExecuteSqlCommand("InsertEmployee @emp", employee);

如果存储过程使用联接从多个表返回值,则可能会失败,我们应该采用什么方法? - Akhil RJ

1
Rohith/Harold Javier/Sami提供的解决方案可行。您可以创建一个单独的EF6项目来生成结果集的C#类,然后将文件复制到您的EFCore项目中。如果更改存储过程,可以使用此处讨论的方法更新结果文件:Stored Procedures and updating EDMX
如果需要相应的TypeScript接口,可以安装此VS2017扩展程序TypeScript Definition Generator:https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TypeScriptDefinitionGenerator 仍然需要进行一些复制操作,但比手动创建类要简单得多。
编辑:有一个用于生成dbconext的VS2017扩展程序https://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools。它不支持存储过程,但提供了一个从VS项目中右键单击菜单项,而不是命令行Scaffold-DbContext。

EF6是目前的最佳选择。在EF Core支持存储过程之前,EF6是首选。 - Manfred Wippel

0
如果您需要从EntityFramework Core执行MySQL数据库中的存储过程,则以下代码应该可以工作。
var blogTagId = 1;
var tags = await _dbContext.BlogTags.FromSqlRaw("CALL SP_GetBlogTags({0})", blogTagId).ToListAsync(); 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接