web-dev-qa-db-ja.com

Entity Framework Coreでストアドプロシージャを実行するにはどうすればよいですか?

Asp.netコアアプリでEF7(エンティティフレームワークコア)を使用しています。ストアドプロシージャを実行する適切な方法を教えていただけますか? ObjectParametersおよび((IObjectContextAdapter)this).ObjectContext.ExecuteFunctionを使用した古いメソッドは機能していません。

46
eadam

EF7のストアドプロシージャのサポートが解決されました。これにより、複数の結果セットのマッピングもサポートされます。

修正の詳細についてはこちらをご覧ください

そして、あなたはこのように呼び出すことができます-var userType = dbContext.Set().FromSql("dbo.SomeSproc @Id = {0}, @Name = {1}", 45, "Ada");

60
Arvin

EF7では、ストアドプロシージャのサポートはまだ実装されていません(7.0.0-beta3時点)。 issue #245 を使用して、この機能の進行状況を追跡できます。

今のところ、ADO.NETを使用して昔ながらの方法でそれを行うことができます。

var connection = (SqlConnection)context.Database.AsSqlServer().Connection.DbConnection;

var command = connection.CreateCommand();
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "MySproc";
command.Parameters.AddWithValue("@MyParameter", 42);

command.ExecuteNonQuery();
19
bricelam

ストアドプロシージャを実行するには、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);
15
Rohith

"(SqlConnection)context"-この型キャストは機能しなくなりました。できること:"SqlConnection context;

".AsSqlServer()"-存在しません。

"command.ExecuteNonQuery();"-結果を返しません。 reader=command.ExecuteReader()は機能します。

Dt.load(reader)...を使用すると、5.0はデータテーブル/データセットをまだサポートしていないため、フレームワークを5.0から4.51に切り替える必要があります。注:これはVS2015 RCです。

4
Rich

EF Coreのストアドプロシージャのサポートは、EF Code Firstの以前のバージョンと同様です。

EFからDbContextクラスを継承して、DbContextクラスを作成する必要があります。ストアドプロシージャは、DbContextを使用して実行されています。

最初のステップは、DbContextからDbCommandを作成するメソッドを作成することです。

public static DbCommand LoadStoredProc(
  this DbContext context, string storedProcName)
{
  var cmd = context.Database.GetDbConnection().CreateCommand();
  cmd.CommandText = storedProcName;
  cmd.CommandType = System.Data.CommandType.StoredProcedure;
  return cmd;
}

ストアドプロシージャにパラメーターを渡すには、次のメソッドを使用します。

public static DbCommand WithSqlParam(
  this DbCommand cmd, string paramName, object paramValue)
{
  if (string.IsNullOrEmpty(cmd.CommandText))
    throw new InvalidOperationException(
      "Call LoadStoredProc before using this method");
  var param = cmd.CreateParameter();
  param.ParameterName = paramName;
  param.Value = paramValue;
  cmd.Parameters.Add(param);
  return cmd;
}

最後に、結果をカスタムオブジェクトのリストにマッピングするには、MapToListメソッドを使用します。

private static List<T> MapToList<T>(this DbDataReader dr)
{
  var objList = new List<T>();
  var props = typeof(T).GetRuntimeProperties();

  var colMapping = dr.GetColumnSchema()
    .Where(x => props.Any(y => y.Name.ToLower() == x.ColumnName.ToLower()))
    .ToDictionary(key => key.ColumnName.ToLower());

  if (dr.HasRows)
  {
    while (dr.Read())
    {
      T obj = Activator.CreateInstance<T>();
      foreach (var prop in props)
      {
        var val = 
          dr.GetValue(colMapping[prop.Name.ToLower()].ColumnOrdinal.Value);
          prop.SetValue(obj, val == DBNull.Value ? null : val);
      }
      objList.Add(obj);
    }
  }
  return objList;
}

これで、ExecuteStoredProcメソッドを使用してストアドプロシージャを実行し、Tとして渡される型を持つリストにマップする準備ができました。

public static async Task<List<T>> ExecuteStoredProc<T>(this DbCommand command)
{
  using (command)
  {
    if (command.Connection.State == System.Data.ConnectionState.Closed)
    command.Connection.Open();
    try
    {
      using (var reader = await command.ExecuteReaderAsync())
      {
        return reader.MapToList<T>();
      }
    }
    catch(Exception e)
    {
      throw (e);
    }
    finally
    {
      command.Connection.Close();
    }
  }
}

たとえば、「StoredProcedureName」というストアドプロシージャを「firstparamname」と「secondparamname」という2つのパラメータで実行するには、これが実装です。

List<MyType> myTypeList = new List<MyType>();
using(var context = new MyDbContext())
{
  myTypeList = context.LoadStoredProc("StoredProcedureName")
  .WithSqlParam("firstparamname", firstParamValue)
  .WithSqlParam("secondparamname", secondParamValue).
  .ExecureStoredProc<MyType>();
}
3
Marco Barbero

現在、EF 7またはEF Coreは、デザイナーにストアドプロシージャをインポートして直接呼び出す古い方法をサポートしていません。ロードマップを見て、今後サポートされるものを確認できます: EF core roadmap

したがって、現時点ではSqlConnectionを使用してストアドプロシージャまたは生のクエリを呼び出すことをお勧めします。このジョブにはEF全体が必要ないためです。以下に2つの例を示します。

単一の値を返すストアドプロシージャを呼び出します。この場合の文字列。

CREATE PROCEDURE [dbo].[Test]
    @UserName nvarchar(50)
AS
BEGIN
    SELECT 'Name is: '+@UserName;
END

リストを返すストアドプロシージャを呼び出します。

CREATE PROCEDURE [dbo].[TestList]
AS
BEGIN
    SELECT [UserName], [Id] FROM [dbo].[AspNetUsers]
END

これらのストアドプロシージャを呼び出すには、これらの関数をすべて保持する静的クラスを作成することをお勧めします。たとえば、次のようにDataAccessクラスと呼びます。

public static class DataAccess

    {
        private static string connectionString = ""; //Your connection string
        public static string Test(String userName)
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();

                // 1.  create a command object identifying the stored procedure
                SqlCommand cmd = new SqlCommand("dbo.Test", conn);

                // 2. set the command object so it knows to execute a stored procedure
                cmd.CommandType = CommandType.StoredProcedure;

                // 3. add parameter to command, which will be passed to the stored procedure
                cmd.Parameters.Add(new SqlParameter("@UserName", userName));

                // execute the command
                using (var rdr = cmd.ExecuteReader())
                {
                    if (rdr.Read())
                    {
                        return rdr[0].ToString();
                    }
                    else
                    {
                        return null;
                    }
                }
            }
        }

        public static IList<Users> TestList()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();

                // 1.  create a command object identifying the stored procedure
                SqlCommand cmd = new SqlCommand("dbo.TestList", conn);

                // 2. set the command object so it knows to execute a stored procedure
                cmd.CommandType = CommandType.StoredProcedure;

                // execute the command
                using (var rdr = cmd.ExecuteReader())
                {
                    IList<Users> result = new List<Users>();
                    //3. Loop through rows
                    while (rdr.Read())
                    {
                        //Get each column
                        result.Add(new Users() { UserName = (string)rdr.GetString(0), Id = rdr.GetString(1) });
                    }
                    return result;
                }
            }

        }
    }

また、Usersクラスは次のようになります。

public class Users
{
     public string UserName { set; get; }
     public string Id { set; get; }
}

ちなみに、asp.netが接続を管理しているので、sqlへのすべての要求に対して接続を開いたり閉じたりするパフォーマンスを心配する必要はありません。そして、これがお役に立てば幸いです。

3
Said Al Souti

他のすべてのソリューションを試しましたが、うまくいきませんでした。しかし、私は適切な解決策にたどり着き、ここで誰かに役立つかもしれません。

ストアドプロシージャを呼び出して、結果をEF Coreのモデルのリストに取得するには、3つの手順に従う必要があります。

手順1。エンティティクラスと同じように新しいクラスを追加する必要があります。 SPのすべての列にプロパティが必要です。たとえば、SPがIdおよびNameという2つの列を返す場合、新しいクラスは次のようになります。

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

ステップ2。

次に、1つのDbQueryプロパティをSPのDBContextクラスに追加する必要があります。

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とリポジトリを使用しています。したがって、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();
}

それが誰かに役立つことを願っています!!!

1

ExecuteSqlCommandExecuteSqlCommandAsyncには多くの問題がありました。INパラメーターは簡単でしたが、OUTパラメーターは非常に困難でした。

DbCommandを使用するように戻さなければなりませんでした-

DbCommand cmd = _context.Database.GetDbConnection().CreateCommand();

cmd.CommandText = "dbo.sp_DoSomething";
cmd.CommandType = CommandType.StoredProcedure;

cmd.Parameters.Add(new SqlParameter("@firstName", SqlDbType.VarChar) { Value = "Steve" });
cmd.Parameters.Add(new SqlParameter("@lastName", SqlDbType.VarChar) { Value = "Smith" });

cmd.Parameters.Add(new SqlParameter("@id", SqlDbType.BigInt) { Direction = ParameterDirection.Output });

詳細については この投稿 で書きました。

1
Bryan

この拡張は非常に便利であることがわかりました: StoredProcedureEFCore

次に、使用方法は次のようになります

List<Model> rows = null;

ctx.LoadStoredProc("dbo.ListAll")
   .AddParam("limit", 300L)
   .AddParam("limitOut", out IOutParam<long> limitOut)
   .Exec(r => rows = r.ToList<Model>());

long limitOutValue = limitOut.Value;

ctx.LoadStoredProc("dbo.ReturnBoolean")
   .AddParam("boolean_to_return", true)
   .ReturnValue(out IOutParam<bool> retParam)
   .ExecNonQuery();

bool b = retParam.Value;

ctx.LoadStoredProc("dbo.ListAll")
   .AddParam("limit", 1L)
   .ExecScalar(out long l);
0
Manfred Wippel

何もする必要はありません...コードファーストアプローチのdbcontextを作成するとき、流なAPIエリアの下のネームスペースを初期化し、spのリストを作成し、それを別の場所で使用します。

public partial class JobScheduleSmsEntities : DbContext
{
    public JobScheduleSmsEntities()
        : base("name=JobScheduleSmsEntities")
    {
        Database.SetInitializer<JobScheduleSmsEntities>(new CreateDatabaseIfNotExists<JobScheduleSmsEntities>());
    }

    public virtual DbSet<Customer> Customers { get; set; }
    public virtual DbSet<ReachargeDetail> ReachargeDetails { get; set; }
    public virtual DbSet<RoleMaster> RoleMasters { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //modelBuilder.Types().Configure(t => t.MapToStoredProcedures());

        //modelBuilder.Entity<RoleMaster>()
        //     .HasMany(e => e.Customers)
        //     .WithRequired(e => e.RoleMaster)
        //     .HasForeignKey(e => e.RoleID)
        //     .WillCascadeOnDelete(false);
    }
    public virtual List<Sp_CustomerDetails02> Sp_CustomerDetails()
    {
        //return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<Sp_CustomerDetails02>("Sp_CustomerDetails");
        //  this.Database.SqlQuery<Sp_CustomerDetails02>("Sp_CustomerDetails");
        using (JobScheduleSmsEntities db = new JobScheduleSmsEntities())
        {
           return db.Database.SqlQuery<Sp_CustomerDetails02>("Sp_CustomerDetails").ToList();

        }

    }

}

}

public partial class Sp_CustomerDetails02
{
    public long? ID { get; set; }
    public string Name { get; set; }
    public string CustomerID { get; set; }
    public long? CustID { get; set; }
    public long? Customer_ID { get; set; }
    public decimal? Amount { get; set; }
    public DateTime? StartDate { get; set; }
    public DateTime? EndDate { get; set; }
    public int? CountDay { get; set; }
    public int? EndDateCountDay { get; set; }
    public DateTime? RenewDate { get; set; }
    public bool? IsSMS { get; set; }
    public bool? IsActive { get; set; }
    public string Contact { get; set; }
}
0
SHUBHASIS

MySQLコネクタとEntity Framework Core 2.0の使用

私の問題は、fxのような例外が発生することでした。 Ex.Message =「必要な列「body」が「FromSql」操作の結果に存在しませんでした。」したがって、この方法でストアドプロシージャを介して行をフェッチするには、現在のリクエストですべてにアクセスする必要がない場合でも、DBSetが関連付けられているエンティティタイプのすべての列を返す必要があります。

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

パラメータ付きOR

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