【问题标题】:How can I call a SQL Stored Procedure using EntityFramework 7 and Asp.Net 5如何使用 EntityFramework 7 和 Asp.Net 5 调用 SQL 存储过程
【发布时间】:2016-01-12 14:05:11
【问题描述】:

在过去的几天里,我正在寻找一些关于如何使用 EntityFramework 7Web 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 即将推出。

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

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

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

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

【问题讨论】:

  • 您目前如何使用实体框架?你能包括你目前如何使用它的小例子吗?我个人也使用 STORED PROCEDURES,我主要在 Controller Action 中调用它并从 Web Api 重新运行结果。它允许我根本不为表定义任何类(实体)。我的 JavaScript 代码取决于从 STORED PROCEDURES 返回的列的名称,而不是我的 C# 代码。见the answer。因此有许多实现选项。如果您要发布当前代码的示例,那么最好展示如何为您提供帮助。
  • 您可能在 NuGet 中看不到实体框架的第 7 版,因为您选中了“仅稳定”复选框。这不包括预发布版本,“EF7 rc1”中的“rc”代表“候选发布”(即预发布)。
  • @Oleg:我以前从未将存储过程与 EntityFramework 一起使用。但是这里的数据库已经创建好了,所以我必须使用它。我的职责是为正在进行的移动版本开发开发 Web API。如果您能给我一个示例代码,那将是一个很大的帮助。
  • @Gary - 我尝试选中“包括预发布”复选框,但仍然没有获得 EntityFramework 的“rc”版本。

标签: c# sql-server asp.net-core-mvc entity-framework-core ef-database-first


【解决方案1】:

希望我能正确理解您的问题。您在数据库中有现有的存储过程,例如dbo.spGetSomeData,它返回一些带有一些字段的项目列表,您需要从 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# 代码没有从存储过程返回的字段名称。因此,如果您要扩展/更改 dbo.spGetSomeData 的代码(重命名一些字段,添加新字段),您只需要调整 JavaScript 代码,而不需要调整 C# 代码。

【讨论】:

  • 您好,Oleg,非常感谢您快速详细的回复。现在我有一些光了!我会试试这个,然后用我的经验回复你。
  • @NiladriSarkar:不客气!您可以看到 Get 操作的代码非常常见,只需使用不同的存储过程名称 (dbo.spGetSomeData) 和参数列表即可共享代码。您可以使用cmd.Parameters.AddRange 并提供为特定存储过程填充的SqlParameter 数组。我想你明白我的意思。我只是想让 Get 动作的代码大多小而简单,以展示编写 C# 代码的主要思想,它调用存储过程并透明地返回可以在 JavaScript 代码中使用的结果。
  • 太棒了!你让我很开心:) 我现在可以调用一个非常简单的存储过程,它使用Inner Join 列出一组记录。我现在应该能够继续处理更复杂的部分了。如果有任何疑问,你猜怎么着..我会给你更多的麻烦。
  • 问题:如何直接调用View?或者我应该回到过去,即创建一个Stored Procedure,在其中调用view,最后从我的控制器中使用这个SP?
  • @NiladriSarkar:可以在cmd.CommandText 中使用的SELECT 语句绝对常见。您可以使用cmd.CommandText = "SELECT * FROM vwMyViewFromDatabase"。您只需删除cmd.CommandType = CommandType.StoredProcedure;。您可以将SELECT 与参数一起使用,只需像使用存储过程的情况一样设置参数的值。
【解决方案2】:

DbContext 有一个Database 属性,它保存了与数据库的连接,您可以对它进行任何操作:

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

但是,我建议不要在您的 Web Api 操作中执行此操作,而是向您的上下文或与您的上下文交互的任何服务/存储库添加一个方法。然后只需在您的操作中调用此方法。理想情况下,您希望将所有 SQL 内容保存在一个地方。

【讨论】:

  • 我想以更 EF 的方式来做,通过在 EDM 设计器工具上放置一个 SP,标记它的类型(标量、复杂等),最后像对象一样调用它实体框架。如果我可以使用ASP.NETEntityFramework 7 完成所有这些工作,那就太好了。如果 7 不是解决方案,我可以回到以前的版本,例如 6.1.3,但这给了我一个错误,我不知道如何解决。此外,我在 VS 2015 社区版中看不到任何可添加到我的项目的 Entity Data Model (EDM) 模板!
  • 不是“EF-ish”方式。 EF 在 EF7 中放弃了对 EDMX 的支持。 EDM 设计器已被弃用,很好摆脱。
  • 是的,EF7 已放弃 EDM。您知道这是永久删除还是临时捕获?
【解决方案3】:

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

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

【讨论】:

  • 我刚收到The type arguments for DbContext.Set&lt;TEntity&gt;() cannot be inferred from the usage
  • 能否请您提供您使用的代码,以及您使用的是什么 .NET 框架?
  • .net 核心 1.1.0。没有context.Set().FromSql()方法
【解决方案4】:

使用 MySQL 连接器和 Entity Framework core 2.0

我的问题是我遇到了像 fx. Ex.Message =“‘FromSql’操作的结果中不存在所需的列‘body’。”。 因此,为了以这种方式通过存储过程获取行,您必须返回与 DBSet 关联的实体类型的所有列,即使您不需要此特定调用的所有数据。

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

或带参数

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

【讨论】:

    【解决方案5】:

    对于数据库优先方法,您必须使用 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
    

    但这不会得到你的存储过程。还在制作中,跟踪issue#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);
    

    【讨论】:

      猜你喜欢
      • 2012-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-11
      • 2014-06-22
      • 1970-01-01
      相关资源
      最近更新 更多