web-dev-qa-db-fra.com

Code First Migrations et procédures stockées

Je viens de créer une base de données et d’effectuer ma première migration (un simple ajout de tableau). Maintenant, je veux ajouter des procédures stockées que je viens d'ajouter en écrivant le SQL et en l'exécutant dans Management Studio. Mais j'aimerais si possible inclure ces procédures stockées dans une migration afin qu'elles soient sauvegardées et que je puisse exécuter une méthode Up ou Down sur elles. Est-ce possible et si oui quelle syntaxe faut-il utiliser? Ou devrais-je simplement les ajouter/modifier/supprimer à l'aide de Management Studio?

25
user517406

J'ai fait ça comme si ...

Dans la classe de migration actuelle -

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 ...
    ";
}
25
NKeddie

J'utilise EF6 et la classe DbMigration fournit des méthodes pour créer/modifier/supprimer des procédures stockées.

  • Créer une nouvelle procédure stockée

    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");                
        }
    }
    
  • Modifier une procédure stockée

    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
";
}
}

Résultat: lorsque vous affichez/modifiez le SP dans SQL Server, c'est pourquoi il affiche "ALTER PROCEDURE".

 enter image description here

0
Adel Mourad

Je vais essayer de donner un point de vue différent, car avoir du code SQL dans les chaînes C # n’est pas très attrayant et qu’il faut s’attendre à changer de tels scripts dans un outil qui fournit intellisense (par exemple, SSMS). 

La solution suivante est implémentée dans un projet API Web ASP.NET Core 2.0.

  1. Maintenir les procédures dans la base de développement en utilisant n'importe quel outil pratique

  2. Générer des scripts de procédures:

    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;
    }
    

Ceci est un processus manuel, mais permet d'obtenir des procédures lorsque leur développement est prêt. En outre, il peut facilement être étendu à d’autres types d’objets (vues, par exemple).

  1. Créez un dossier dans la solution pour contenir les scripts à exécuter au démarrage de l'application (par exemple, _SQL).

  2. Copier le script généré dans le dossier (par exemple, all_procedures.sql)

Un des avantages du stockage de scripts comme celui-ci est que le IDE peut automatiquement valider la syntaxe + le surlignage, etc.

  1. Créer un code "de démarrage" à exécuter automatiquement au démarrage de l'application

    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);
            }
        }
    }
    

Cette approche permet de gérer tous les scripts idempotent qui ne sont pas facilement gérés lors des migrations.

0
Alexei