如何使用 EntityFramework 7 和 Asp.Net 5 调用 SQL 存储过程

How can I call a SQL Stored Procedure using EntityFramework 7 and Asp.Net 5

最近几天,我正在搜索一些关于如何使用 EntityFramework 7.

Web API 控制器方法内部调用 Stored Procedure 的教程

我学习的所有教程都以相反的方式展示它,即 Code First 方法。但是我已经有了一个数据库,我需要用它来构建一个 Web API。各种业务逻辑已经编写为存储过程和视图,我必须从我的 Web API.

中使用它们

问题 1:这完全有可能继续使用 Database First 方法和 EF7 并像上面那样使用数据库对象吗?

我通过以下 NuGet 命令将 EntityFramework 6.1.3 安装到我的包中:

install-package EntityFramework 它将 6.1.3 版本添加到我的项目中,但立即开始向我显示错误消息(请参见下面的屏幕截图)。我不知道如何解决这个问题。

我有另一个测试项目,在 project.json 中我可以看到如下两个条目:

"EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final", "EntityFramework.MicrosoftSqlServer.Design": "7.0.0-rc1-final",

但是,当我在 Nu-Get 包管理器中搜索时,我没有看到这个版本!只有 6.1.3 即将推出。

我的主要 objective 是使用现有数据库中已编写的存储过程和视图。

1) 我不想使用 ADO.Net,而是想使用 ORM 使用 EntityFramework

2) 如果 EntityFramework 6.1.3 能够从现有数据库调用 Stored ProcsViews,我该如何解决错误(屏幕截图)?

实现此目标的最佳做法是什么?

DbContext 有一个 Database 属性,它保持到数据库的连接,你可以用它做任何你想做的事:

context.Database.SqlQuery<Foo>("exec [dbo].[GetFoo] @Bar = {0}", bar);

但是,与其在您的 Web Api 操作中执行此操作,我建议将方法添加到您的上下文或任何与您的上下文交互的 service/repository。然后只需在您的操作中调用此方法即可。理想情况下,您希望将所有 SQL-stuff 放在一个地方。

希望我正确理解了你的问题。您在数据库中有现有的存储过程,例如 dbo.spGetSomeData,其中 returns 具有某些字段的某些项目的列表,您需要从 Web API 方法提供数据。

实施可能与以下内容有关。您可以定义一个 empty DbContext 如:

public class MyDbContext : DbContext
{
}

并用数据库的连接字符串定义appsettings.json

{
  "Data": {
    "DefaultConnection": {
      "ConnectionString": "Server=(localdb)\mssqllocaldb;Database=MyDb;Trusted_Connection=True;MultipleActiveResultSets=true"
    }
  }
}

您应该使用 Microsoft.Extensions.DependencyInjectionMyDbContext 添加到

public class Startup
{
    // property for holding configuration
    public IConfigurationRoot Configuration { get; set; }

    public Startup(IHostingEnvironment env)
    {
        // Set up configuration sources.
        var builder = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
            .AddEnvironmentVariables();
        // save the configuration in Configuration property
        Configuration = builder.Build();
    }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddMvc()
            .AddJsonOptions(options => {
                options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            });

        services.AddEntityFramework()
            .AddSqlServer()
            .AddDbContext<MyDbContext>(options => {
                options.UseSqlServer(Configuration["ConnectionString"]);
            });
    }
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        ...
    }
}

现在您可以按如下方式实现您的 WebApi 操作:

[Route("api/[controller]")]
public class MyController : Controller
{
    public MyDbContext _context { get; set; }

    public MyController([FromServices] MyDbContext context)
    {
        _context = context;
    }

    [HttpGet]
    public async IEnumerable<object> Get()
    {
        var returnObject = new List<dynamic>();

        using (var cmd = _context.Database.GetDbConnection().CreateCommand()) {
            cmd.CommandText = "exec dbo.spGetSomeData";
            cmd.CommandType = CommandType.StoredProcedure;
            // set some parameters of the stored procedure
            cmd.Parameters.Add(new SqlParameter("@someParam",
                SqlDbType.TinyInt) { Value = 1 });

            if (cmd.Connection.State != ConnectionState.Open)
                cmd.Connection.Open();

            var retObject = new List<dynamic>();
            using (var dataReader = await cmd.ExecuteReaderAsync())
            {
                while (await dataReader.ReadAsync())
                {
                    var dataRow = new ExpandoObject() as IDictionary<string, object>;
                    for (var iFiled = 0; iFiled < dataReader.FieldCount; iFiled++) {
                        // one can modify the next line to
                        //   if (dataReader.IsDBNull(iFiled))
                        //       dataRow.Add(dataReader.GetName(iFiled), dataReader[iFiled]);
                        // if one want don't fill the property for NULL
                        // returned from the database
                        dataRow.Add(
                            dataReader.GetName(iFiled),
                            dataReader.IsDBNull(iFiled) ? null : dataReader[iFiled] // use null instead of {}
                        );
                    }

                    retObject.Add((ExpandoObject)dataRow);
                }
            }
            return retObject;
        }
    }
}

以上代码仅使用 exec dbo.spGetSomeData 执行并使用 dataRader 读取所有结果并将其保存在 dynamic 对象中。如果您从 api/My 进行 $.ajax 调用,您将获得从 dbo.spGetSomeData 返回的数据,您可以直接在 JavaScript 代码中使用这些数据。上面的代码非常透明。 dbo.spGetSomeData 返回的数据集中的字段名称将是 JavaScript 代码中的属性名称。 您不需要以任何方式在 C# 代码中管理任何实体 类。您的 C# 代码没有从存储过程返回的字段名称。因此,如果您要 extend/change dbo.spGetSomeData 的代码(重命名一些字段,添加新字段),您将只需要调整 JavaScript 代码,而不需要调整 C# 代码。

正如上面的答案,您可以简单地使用 FromSQL() 而不是 SqlQuery<>()。

context.Set().FromSql("[dbo].[GetFoo] @Bar = {0}", 45);

对于数据库优先方法,您必须使用 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 查询或存储的 procedures.You 不能用于 INSERT/UPDATE/DELETE 有一定的限制。如果要执行 INSERT、UPDATE、DELETE 查询,请使用 ExecuteSqlCommand

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

使用 MySQL 连接器和 Entity Framework 核心 2.0

我的问题是我遇到了类似 fx 的异常。 Ex.Message = "The required column 'body' was not present in the results of a 'FromSql' operation."。 因此,为了以这种方式通过存储过程获取行,您必须 return 与 DBSet 关联的实体类型的所有列,即使您不需要此特定调用的所有数据。

var result = _context.DBSetName.FromSql($"call storedProcedureName()").ToList(); 

或带参数

var result = _context.DBSetName.FromSql($"call storedProcedureName({optionalParam1})").ToList();