70

テーブル値パラメーターを受け入れるストアドプロシージャを呼び出そうとしています。これはEntityFrameworkで直接サポートされていないことはわかっていますが、私が理解していることから、のExecuteStoreQueryコマンドを使用してサポートできますObjectContext。私は次のExecuteStoredProcedure方法を持っている汎用エンティティフレームワークリポジトリを持っています:

public IEnumerable<T> ExecuteStoredProcedure<T>(string procedureName, params object[] parameters)
{
    StringBuilder command = new StringBuilder();
    command.Append("EXEC ");
    command.Append(procedureName);
    command.Append(" ");

    // Add a placeholder for each parameter passed in
    for (int i = 0; i < parameters.Length; i++)
    {
        if (i > 0)
            command.Append(",");

        command.Append("{" + i + "}");
    }

    return this.context.ExecuteStoreQuery<T>(command.ToString(), parameters);
}

コマンド文字列は次のようになります。

EXEC someStoredProcedureName {0},{1},{2},{3},{4},{5},{6},{7}

テーブル値パラメーターを受け入れるストアドプロシージャでこのメソッドを実行しようとしましたが、失敗しました。ここで、パラメーターは型である必要がSqlParameterあり、テーブル値パラメーターはにSqlDbType設定されている必要があることを読みましたStructured。だから私はこれをしました、そして私は次のようなエラーを受け取ります:

The table type parameter p6 must have a valid type name

そのため、SqlParameter.TypeNameをデータベースで作成したユーザー定義型の名前に設定し、クエリを実行すると、次の非常に役立つエラーが発生します。

Incorrect syntax near '0'.

ADO.NETに戻ってデータリーダーを実行すると、クエリを実行できますが、データコンテキストを使用してクエリを機能させることを望んでいました。

を使用してテーブル値パラメータを渡す方法はありますExecuteStoreQueryか?また、私は実際にEntity Framework Code Firstを使用しており、メソッドを使用可能にするためににキャストしDbContextています。これは必要ですか、それとも私もこれを行うことができますか?ObjectContextExecuteStoreQueryDbContext

4

6 に答える 6

99

アップデート

Nugetパッケージでこれのサポートを追加しました-https ://github.com/Fodsuk/EntityFrameworkExtras#nuget (EF4、EF5、EF6)

コード例については、 GitHubリポジトリを確認してください。


少し質問はありませんが、ユーザー定義テーブルをストアドプロシージャに渡そうとする人にとってはそれほど便利ではありません。Nickの例や他のStackoverflowの投稿をいじってみた後、私はこれを思いついた:

class Program
{
    static void Main(string[] args)
    {
        var entities = new NewBusinessEntities();

        var dt = new DataTable();
        dt.Columns.Add("WarningCode");
        dt.Columns.Add("StatusID");
        dt.Columns.Add("DecisionID");
        dt.Columns.Add("Criticality");

        dt.Rows.Add("EO01", 9, 4, 0);
        dt.Rows.Add("EO00", 9, 4, 0);
        dt.Rows.Add("EO02", 9, 4, 0);

        var caseId = new SqlParameter("caseid", SqlDbType.Int);
        caseId.Value = 1;

        var userId = new SqlParameter("userid", SqlDbType.UniqueIdentifier);
        userId.Value = Guid.Parse("846454D9-DE72-4EF4-ABE2-16EC3710EA0F");

        var warnings = new SqlParameter("warnings", SqlDbType.Structured);
        warnings.Value= dt;
        warnings.TypeName = "dbo.udt_Warnings";

        entities.ExecuteStoredProcedure("usp_RaiseWarnings_rs", userId, warnings, caseId);
    }
}

public static class ObjectContextExt
{
    public static void ExecuteStoredProcedure(this ObjectContext context, string storedProcName, params object[] parameters)
    {
        string command = "EXEC " + storedProcName + " @caseid, @userid, @warnings";

        context.ExecuteStoreCommand(command, parameters);
    }
}

ストアドプロシージャは次のようになります。

ALTER PROCEDURE [dbo].[usp_RaiseWarnings_rs]
    (@CaseID int, 
     @UserID uniqueidentifier = '846454D9-DE72-4EF4-ABE2-16EC3710EA0F', --Admin
     @Warnings dbo.udt_Warnings READONLY
)
AS

ユーザー定義のテーブルは次のようになります。

CREATE TYPE [dbo].[udt_Warnings] AS TABLE(
    [WarningCode] [nvarchar](5) NULL,
    [StatusID] [int] NULL,
    [DecisionID] [int] NULL,
    [Criticality] [int] NULL DEFAULT ((0))
)

私が見つけた制約は次のとおりです。

  1. 渡すExecuteStoreCommandパラメータは、ストアドプロシージャのパラメータと一致している必要があります
  2. デフォルトがある場合でも、すべての列をユーザー定義テーブルに渡す必要があります。したがって、UDTにIDENTITY(1,1)NOTNULL列を設定できなかったようです。
于 2012-03-23T10:50:05.113 に答える
21

さて、これが2018年の更新です: nugetパッケージなしでEntityFrameworkからテーブルパラメーターを使用してストアドプロシージャを呼び出す方法を説明するエンドツーエンドのソリューション

EF 6.xx、SQL Server 2012、VS2017を使用しています

1.テーブルバリュープラメーター

このように定義された単純なテーブルタイプがあるとします(1列のみ)

go
create type GuidList as table (Id uniqueidentifier)

2.ストアドプロシージャ

次のようないくつかのパラメータを持つストアドプロシージャ。

go
create procedure GenerateInvoice
    @listIds GuidList readonly,
    @createdBy uniqueidentifier,
    @success int out,
    @errorMessage nvarchar(max) out
as
begin
    set nocount on;

    begin try
    begin tran;  

    -- 
    -- Your logic goes here, let's say a cursor or something:
    -- 
    -- declare gInvoiceCursor cursor forward_only read_only for
    -- 
    -- bla bla bla
    --
    --  if (@brokenRecords > 0)
    --  begin
    --      RAISERROR(@message,16,1);
    --  end
    -- 


    -- All good!
    -- Bonne chance mon ami!

    select @success = 1
    select @errorMessage = ''

    end try
    begin catch  
        --if something happens let's be notified
        if @@trancount > 0 
        begin
            rollback tran;  
        end

        declare @errmsg nvarchar(max)
        set @errmsg =       
            (select 'ErrorNumber: ' + cast(error_number() as nvarchar(50))+
            'ErrorSeverity: ' + cast(error_severity() as nvarchar(50))+
            'ErrorState: ' + cast(error_state() as nvarchar(50))+
            'ErrorProcedure: ' + cast(error_procedure() as nvarchar(50))+
            'ErrorLine: ' + cast(error_number() as nvarchar(50))+
            'error_message: ' + cast(error_message() as nvarchar(4000))
            )
        --save it if needed

        print @errmsg

        select @success = 0
        select @errorMessage = @message

        return;
    end catch;

    --at this point we can commit everything
    if @@trancount > 0 
    begin
        commit tran;  
    end

end
go

3.このストアドプロシージャを使用するSQLコード

SQLでは、次のようなものを使用します。

declare @p3 dbo.GuidList
insert into @p3 values('f811b88a-bfad-49d9-b9b9-6a1d1a01c1e5')
exec sp_executesql N'exec GenerateInvoice @listIds, @CreatedBy, @success',N'@listIds [dbo].[GuidList] READONLY,@CreatedBy uniqueidentifier',@listIds=@p3,@CreatedBy='FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF'

4.このストアドプロシージャを使用するためのC#コード

そして、これがEntity Framework(WebAPI内)からそのストアドプロシージャを呼び出す方法です。

    [HttpPost]
    [AuthorizeExtended(Roles = "User, Admin")]
    [Route("api/BillingToDo/GenerateInvoices")]
    public async Task<IHttpActionResult> GenerateInvoices(BillingToDoGenerateInvoice model)
    {
        try
        {
            using (var db = new YOUREntities())
            {
                //Build your record
                var tableSchema = new List<SqlMetaData>(1)
                {
                    new SqlMetaData("Id", SqlDbType.UniqueIdentifier)
                }.ToArray();

                //And a table as a list of those records
                var table = new List<SqlDataRecord>();

                for (int i = 0; i < model.elements.Count; i++)
                {
                    var tableRow = new SqlDataRecord(tableSchema);
                    tableRow.SetGuid(0, model.elements[i]);
                    table.Add(tableRow);
                }

                //Parameters for your query
                SqlParameter[] parameters =
                {
                    new SqlParameter
                    {
                        SqlDbType = SqlDbType.Structured,
                        Direction = ParameterDirection.Input,
                        ParameterName = "listIds",
                        TypeName = "[dbo].[GuidList]", //Don't forget this one!
                        Value = table
                    },
                    new SqlParameter
                    {
                        SqlDbType = SqlDbType.UniqueIdentifier,
                        Direction = ParameterDirection.Input,
                        ParameterName = "createdBy",
                        Value = CurrentUser.Id
                    },
                    new SqlParameter
                    {
                        SqlDbType = SqlDbType.Int,
                        Direction = ParameterDirection.Output, // output!
                        ParameterName = "success"
                    },
                    new SqlParameter
                    {
                        SqlDbType = SqlDbType.NVarChar,
                        Size = -1,                             // "-1" equals "max"
                        Direction = ParameterDirection.Output, // output too!
                        ParameterName = "errorMessage"
                    }
                };

                //Do not forget to use "DoNotEnsureTransaction" because if you don't EF will start it's own transaction for your SP.
                //In that case you don't need internal transaction in DB or you must detect it with @@trancount and/or XACT_STATE() and change your logic
                await db.Database.ExecuteSqlCommandAsync(TransactionalBehavior.DoNotEnsureTransaction,
                    "exec GenerateInvoice @listIds, @createdBy, @success out, @errorMessage out", parameters);

                //reading output values:
                int retValue;
                if (parameters[2].Value != null && Int32.TryParse(parameters[2].Value.ToString(), out retValue))
                {
                    if (retValue == 1)
                    {
                        return Ok("Invoice generated successfully");
                    }
                }

                string retErrorMessage = parameters[3].Value?.ToString();

                return BadRequest(String.IsNullOrEmpty(retErrorMessage) ? "Invoice was not generated" : retErrorMessage);
            }
        }
        catch (Exception e)
        {
            return BadRequest(e.Message);
        }
    }
}

お役に立てば幸いです。

于 2018-05-14T21:39:55.967 に答える
9

この問題に関する私の解決策を共有したいと思います:

いくつかのテーブル値パラメーターを使用してプロシージャーを保管しましたが、このように呼び出すと、次のことがわかりました。

var query = dbContext.ExecuteStoreQuery<T>(@"
EXECUTE [dbo].[StoredProcedure] @SomeParameter, @TableValueParameter1, @TableValueParameter2", spParameters[0], spParameters[1], spParameters[2]);
var list = query.ToList();

レコードのないリストを取得します。

しかし、私はそれをもっと遊んで、この行は私にアイデアを与えました:

var query = dbContext.ExecuteStoreQuery<T>(@"
EXECUTE [dbo].[StoredProcedure] 'SomeParameterValue', @TableValueParameter1, @TableValueParameter2",  spParameters[1], spParameters[2]);
var list = query.ToList();

コマンドテキストのパラメータ@SomeParameterを実際の値'SomeParameterValue'に変更しました。そしてそれはうまくいきました:)これは、パラメータにSqlDbType.Structured以外のものがある場合、それらすべてを正しく渡さず、何も得られないことを意味します。実際のパラメータをそれらの値に置き換える必要があります。

したがって、私のソリューションは次のようになります。

public static List<T> ExecuteStoredProcedure<T>(this ObjectContext dbContext, string storedProcedureName, params SqlParameter[] parameters)
{
    var spSignature = new StringBuilder();
    object[] spParameters;
    bool hasTableVariables = parameters.Any(p => p.SqlDbType == SqlDbType.Structured);

    spSignature.AppendFormat("EXECUTE {0}", storedProcedureName);
    var length = parameters.Count() - 1;

    if (hasTableVariables)
    {
        var tableValueParameters = new List<SqlParameter>();

        for (int i = 0; i < parameters.Count(); i++)
        {
            switch (parameters[i].SqlDbType)
            {
                case SqlDbType.Structured:
                    spSignature.AppendFormat(" @{0}", parameters[i].ParameterName);
                    tableValueParameters.Add(parameters[i]);
                    break;
                case SqlDbType.VarChar:
                case SqlDbType.Char:
                case SqlDbType.Text:
                case SqlDbType.NVarChar:
                case SqlDbType.NChar:
                case SqlDbType.NText:
                case SqlDbType.Xml:
                case SqlDbType.UniqueIdentifier:
                case SqlDbType.Time:
                case SqlDbType.Date:
                case SqlDbType.DateTime:
                case SqlDbType.DateTime2:
                case SqlDbType.DateTimeOffset:
                case SqlDbType.SmallDateTime:
                    // TODO: some magic here to avoid SQL injections
                    spSignature.AppendFormat(" '{0}'", parameters[i].Value.ToString());
                    break;
                default:
                    spSignature.AppendFormat(" {0}", parameters[i].Value.ToString());
                    break;
            }

            if (i != length) spSignature.Append(",");
        }
        spParameters = tableValueParameters.Cast<object>().ToArray();
    }
    else
    {
        for (int i = 0; i < parameters.Count(); i++)
        {
            spSignature.AppendFormat(" @{0}", parameters[i].ParameterName);
            if (i != length) spSignature.Append(",");
        }
        spParameters = parameters.Cast<object>().ToArray();
    }

    var query = dbContext.ExecuteStoreQuery<T>(spSignature.ToString(), spParameters);


    var list = query.ToList();
    return list;
}

コードは確かにもっと最適化される可能性がありますが、これが役立つことを願っています。

于 2012-09-28T23:13:15.907 に答える
3
var sqlp = new SqlParameter("@param3", my function to get datatable);
sqlp.SqlDbType = System.Data.SqlDbType.Structured;
sqlp.TypeName = "dbo.mytypename";

  var v = entitycontext.Database.SqlQuery<bool?>("exec [MyStorProc] @param1,@param2,@param3,@param4", new SqlParameter[]
                    {
                        new SqlParameter("@param1",value here),
                        new SqlParameter("@param2",value here),

                        sqlp,
                        new SqlParameter("@param4",value here)

                    }).FirstOrDefault();
于 2018-02-03T06:46:51.453 に答える
2

DataTableアプローチが唯一の方法ですが、DataTableを作成して手動で入力するのは簡単ではありません。EFの流暢なモデルビルダーに似たスタイルで、IEnumerableから直接DataTableを定義したかったのです。それで:

var whatever = new[]
            {
                new
                {
                    Id = 1,
                    Name = "Bacon",
                    Foo = false
                },
                new
                {
                    Id = 2,
                    Name = "Sausage",
                    Foo = false
                },
                new
                {
                    Id = 3,
                    Name = "Egg",
                    Foo = false
                },
            };

            //use the ToDataTable extension method to populate an ado.net DataTable
            //from your IEnumerable<T> using the property definitions.
            //Note that if you want to pass the datatable to a Table-Valued-Parameter,
            //The order of the column definitions is significant.
            var dataTable = whatever.ToDataTable(
                whatever.Property(r=>r.Id).AsPrimaryKey().Named("item_id"),
                whatever.Property(r=>r.Name).AsOptional().Named("item_name"),
                whatever.Property(r=>r.Foo).Ignore()
                );

dontnetfiddleに投稿しました:https ://dotnetfiddle.net/ZdpYM3 (すべてのアセンブリがフィドルにロードされているわけではないため、そこで実行できないことに注意してください)

于 2014-06-13T09:08:52.217 に答える
-2

文字列連結コードを変更して、次のようなものを生成します。

EXEC someStoredProcedureName @p0,@p1,@p2,@p3,@p4,@p5,@p6,@p7
于 2011-11-24T17:43:17.720 に答える