web-dev-qa-db-ja.com

コードファーストの移行とストアドプロシージャ

データベースを作成し、最初の移行を実行しました(単純なテーブルの追加)。ここで、SQLを記述してManagement Studioで実行することで追加したストアドプロシージャを追加します。しかし、可能であれば、これらのストアドプロシージャを移行に含めて保存し、それらに対してUpまたはDownメソッドを実行できるようにしたいと考えています。これは可能ですか?可能であれば、どの構文を使用する必要がありますか?または、Management Studioを使用してそれらを追加、編集、削除する必要がありますか?

29
user517406

私はこうやってこうしました...

現在のマイグレーションクラス-

public partial class MyMigration : DbMigration
{
    public override void Up()
    {
        ... other table creation logic

        // This command executes the SQL you have written
        // to create the stored procedures
        Sql(InstallScript);

        // or, to alter stored procedures
        Sql(AlterScript);
    }

    public override void Down()
    {
        ... other table removal logic

        // This command executes the SQL you have written
        // to drop the stored procedures
        Sql(UninstallScript);

        // or, to rollback stored procedures
        Sql(RollbackScript);
    }

    private const string InstallScript = @"
        CREATE PROCEDURE [dbo].[MyProcedure]
        ... SP logic here ...
    ";

    private const string UninstallScript = @"
        DROP PROCEDURE [dbo].[MyProcedure];
    ";

    // or for alters
    private const string AlterScript = @"
        ALTER PROCEDURE [dbo].[AnotherProcedure]
        ... Newer SP logic here ...
    ";

    private const string RollbackScript = @"
        ALTER PROCEDURE [dbo].[AnotherProcedure]
        ... Previous / Old SP logic here ...
    ";
}
26
NKeddie

私はEF6を使用しており、DbMigrationクラスはストアドプロシージャを作成/変更/削除するメソッドを提供します

  • 新しいストアドプロシージャを作成する

    public partial class MyFirstMigration : DbMigration
    {
        public override void Up()
        {
            // Create a new store procedure
            CreateStoredProcedure("dbo.DequeueMessages"
            // These are stored procedure parameters
            , c => new{                
                MessageCount = c.Int()
            },
            // Here is the stored procedure body
            @"
            SET NOCOUNT ON;
            SELECT TOP (@MessageCount)
                *
            FROM
                dbo.MyTable;
            ");
        }
    
        public override void Down()
        {
            // Delete the stored procedure
            DropStoredProcedure("dbo.DequeueMessages");                
        }
    }
    
  • ストアドプロシージャを変更する

    public partial class MySecondMigration : DbMigration
    {
        public override void Up()
        {
            // Modify an existing stored procedure
            AlterStoredProcedure("dbo.DequeueMessages"
            // These are new stored procedure parameters
            , c => new{                
                MessageCount = c.Int(),
                StatusId = c.Int()
            },
            // Here is the new stored procedure body
            @"
            SET NOCOUNT ON;
            SELECT TOP (@MessageCount)
                *
            FROM
                dbo.MyTable
            WHERE
                StatusId = @StatusId;
            ");
        }
    
        public override void Down()
        {
            // Rollback to the previous stored procedure
            // Modify an existing stored procedure
            AlterStoredProcedure("dbo.DequeueMessages"
            // These are old stored procedure parameters
            , c => new{                
                MessageCount = c.Int()
            },
            // Here is the old stored procedure body
            @"
            SET NOCOUNT ON;
            SELECT TOP (@MessageCount)
                *
            FROM
                dbo.MyTable;
            ");              
        }
    }
    
11
Thomas
namespace QuickProject.Migrations
{
    using System;
    using System.Data.Entity.Migrations;


public partial class CreateStoredProcedure_GellAllAgents : DbMigration
{
    public override void Up()
    {
        CreateStoredProcedure("dbo.GellAllAgents", c => new
        {
            DisplayLength = c.Int(10),
            DisplayStart = c.Int(0),
            UserName = c.String(maxLength: 255, defaultValueSql: "NULL"),
            FullName = c.String(maxLength: 255, defaultValueSql: "NULL"),
            PhoneNumber = c.String(maxLength: 255, defaultValueSql: "NULL"),
            LocationDescription = c.String(maxLength: 255, defaultValueSql: "NULL"),
            AgentStatusId = c.Int(defaultValueSql: "NULL"),
            AgentTypeId = c.Int(defaultValueSql: "NULL")
        }, StoredProcedureBody);
    }

    public override void Down()
    {
        DropStoredProcedure("dbo.GellAllAgents");
    }


    private const string StoredProcedureBody = @"
Declare @FirstRec int, @LastRec int
Set @FirstRec = @DisplayStart;
Set @LastRec = @DisplayStart + @DisplayLength;

With CTE_AspNetUsers as
(
     Select ROW_NUMBER() over (order by AspNetUsers.Id) as RowNum,
         COUNT(*) over() as TotalCount, AspNetUsers.Id, AspNetUsers.FullName, AspNetUsers.UserName, AspNetUsers.PhoneNumber, Locations.Desciption as LocationDescription, Cities.Name as LocationCity, AgentStatus.Name as AgentStatusName, AgentTypes.Name as AgentTypeName
         from AspNetUsers
     join Locations on AspNetUsers.LocationId = Locations.id
     join Cities on Locations.CityId = Cities.Id
     join AgentStatus on AspNetUsers.AgentStatusId = AgentStatus.Id
     join AgentTypes on AspNetUsers.AgentTypeId = AgentTypes.Id
     where (Discriminator = 'Agent' 
         and (@UserName is null or UserName like '%' + @UserName + '%')
         and (@FullName is null or FullName like '%' + @FullName + '%')
         and (@PhoneNumber is null or PhoneNumber like '%' + @PhoneNumber + '%')
         and (@LocationDescription is null or  @LocationDescription like '%' + (select Cities.Name from Cities where Locations.CityId = Cities.Id) + '%' or  @LocationDescription like '%' + Desciption + '%')
         and (@AgentStatusId is null or AgentStatusId = @AgentStatusId)
         and (@AgentTypeId is null or AgentTypeId = @AgentTypeId)
     )
     group by AspNetUsers.Id, AspNetUsers.FullName,AspNetUsers.UserName, AspNetUsers.PhoneNumber, Locations.Desciption, Cities.Name, AgentStatus.Name, AgentTypes.Name
)
Select *
from CTE_AspNetUsers
where RowNum > @FirstRec and RowNum <= @LastRec
";
}
}

結果、SQLサーバーでSPを表示/変更すると、 "ALTER PROCEDURE"と表示されるのはそのためです

enter image description here

1
Adel Mourad

C#文字列内にSQLコードを含めることはあまり魅力的ではなく、インテリセンスを提供するツール(SSMSなど)内でそのようなスクリプトを変更することを期待する必要があるため、別の視点を提供しようとします。

次のソリューションは、ASP.NET Core 2.0 Web APIプロジェクト内に実装されています。

  1. 便利なツールを使用して、開発データベースの手順を維持する

  2. 手順スクリプトを生成します。

    public class ProcedureItemMetadata
    {
        /// <summary>
        /// SQL server side object identifier
        /// </summary>
        [Key]
        public int ObjectId { get; set; }
    
        /// <summary>
        /// schema name
        /// </summary>
        public string SchemaName { get; set; }
    
        /// <summary>
        /// procedure name
        /// </summary>
        public string Name { get; set; }
    
        /// <summary>
        /// procedure body
        /// </summary>
        public string Definition { get; set; }
    }
    
    
    public string GetProceduresScript()
    {
       var query = Context.ProcedureItemMetadata.AsNoTracking().FromSql(@"
          SELECT ao.object_id as ObjectId, SCHEMA_NAME(ao.schema_id) as SchemaName, ao.name, sm.definition
          FROM sys.all_objects ao 
          JOIN sys.sql_modules sm ON sm.object_id = ao.object_id
          WHERE ao.type = 'P'
             and execute_as_principal_id IS NULL
          order by 1;");
    
          var list = query.ToList();
          string text = string.Join($" {Base.Constants.General.ScriptGeneratorSeparator}\n", list.Select(p => p.Definition));
    
          // replace create with create or alter
          string replaced = Regex.Replace(text,
             @"(?<create>CREATE\s+PROCEDURE\s+)",
             "CREATE OR ALTER PROCEDURE ", 
             RegexOptions.IgnoreCase);
    
          return replaced;
    }
    

これは手動のプロセスですが、開発の準備ができているときはいつでも手順を取得できます。また、他のタイプのオブジェクト(ビューなど)に簡単に拡張できます。

  1. ソリューション内にフォルダーを作成し、アプリケーションの起動時に実行するスクリプトを保持します(例:_SQL)

  2. 生成されたスクリプトをフォルダー内にコピー(例:all_procedures.sql)

このようにスクリプトを保存する利点の1つは、IDEが構文を自動的に検証し、内容を強調表示するなどです。

  1. アプリケーションの起動時に自動的に実行される「シード」コードを作成する

    private static void EnsureSqlObjects(CustomContext context)
    {
        string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "_Sql");
        foreach (var file in Directory.GetFiles(path, "*.sql"))
        {
            string fileText = File.ReadAllText(file);
            // escaping { } for those rare cases when sql code contains {..}
            // as ExecuteSqlCommand tries to replace them with params values
            fileText = fileText.Replace("{", "{{");
            fileText = fileText.Replace("}", "}}");
    
            // splitting objects (cannot run more than one DDL in a command)
            string[] ddlParts = fileText.Split(Base.Constants.General.ScriptGeneratorSeparator, StringSplitOptions.RemoveEmptyEntries);
            foreach (string ddl in ddlParts)
            {
                context.Database.ExecuteSqlCommand(ddl);
            }
        }
    }
    

このアプローチでは、移行によって容易に維持できない べき等 スクリプトを管理することができます。

0
Alexei