Can you create sql views / stored procedure using

2019-01-07 06:43发布

问题:

Entity Framework 4.1 Code First works great creating tables and relationships. Is it possible to create sql views or stored procedure using Code first approach? Any pointers regarding this will be highly appreciated. Thanks a lot!

回答1:

EF code-first approach expects that there is no logic in the database. That means no stored procedures and no database views. Because of that code-first approach doesn't provide any mechanism to generate such constructs automatically for you. How could it do that if it means generating logic?

You must create them yourselves in custom database initializer by manual executing creation scripts. I don't think that this custom SQL constructs can be handled by SQL migrations.



回答2:

We support stored procedures in our Entity Framework Code First Migrations. Our approach is to create some folder to hold the .sql files (~/Sql/ for example). Create .sql files in the folder for both creating and dropping the stored procedure. E.g. Create_sp_DoSomething.sql and Drop_sp_DoSomething. Because the SQL runs in a batch and CREATE PROCEDURE.. must be the first statement in a batch, make the CREATE PROCEDURE... the first statement in the file. Also, don't put GO after the DROP.... Add a resources file to your project, if you don't have one already. Drag the .sql files from solution explorer into the Files view of the Resources designer. Now create an empty migration (Add-Migration SomethingMeaningful_sp_DoSomething) and use:

namespace MyApplication.Migrations
{
    using System;
    using System.Data.Entity.Migrations;

    public partial class SomethingMeaningful_sp_DoSomething : DbMigration
    {
        public override void Up()
        {
            this.Sql(Properties.Resources.Create_sp_DoSomething);
        }

        public override void Down()
        {
            this.Sql(Properties.Resources.Drop_sp_DoSomething);
        }
    }
}

~/Sql/Create_sp_DoSomething.sql

CREATE PROCEDURE [dbo].[sp_DoSomething] AS
BEGIN TRANSACTION
-- Your stored procedure here
COMMIT TRANSACTION
GO

~/Sql/Drop_sp_DoSomething.sql

DROP PROCEDURE [dbo].[sp_DoSomething]


回答3:

At first sight I really like the approach of Carl G but it involves a lot of manual interaction. In my scenario, I always drop all stored procedures, views... and recreate them whenever there is a change in the database. This way we are sure everything is up-to-date with the latest version.

Recreation happens by setting the following Initializer:

Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());

Then our seed method will get called whenever there is a migration ready

protected override void Seed(DeploymentLoggingContext context)
    {
        // Delete all stored procs, views
        foreach (var file in Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sql\\Seed"), "*.sql"))
        {
            context.Database.ExecuteSqlCommand(File.ReadAllText(file), new object[0]);
        }

        // Add Stored Procedures
        foreach (var file in Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sql\\StoredProcs"), "*.sql"))
        {
            context.Database.ExecuteSqlCommand(File.ReadAllText(file), new object[0]);
        }
    }

SQL Statements are stored in *.sql files for easy editing. Make sure your files have "Build Action" set to "Content" and "Copy to Output Directory" set to "Copy Always". We lookup the folders and execute all scripts inside. Don't forget to exclude "GO" statements in your SQL because they cannot be executed with ExecuteSqlCommand().

My current directory layout is as follows:

Project.DAL
+ Migrations
+ Sql
++ Seed
+++ dbo.cleanDb.sql
++ StoredProcs
+++ dbo.sp_GetSomething.sql

Now you just need to drop extra stored procedures in the folder and everything will get updated appropriately.



回答4:

It appears to be poorly documented however it appears you can now do some Stored Procedure manipulation using AlterStoredProcedure, CreateStoredProcedure, DropStoredProcedure, MoveStoredProcedure, RenameStoredProcedure in Entity Framework 6. I haven't tried them yet so can't yet give an example of how to use them.



回答5:

To expand on bbodenmiller's answer, In Entity Framework 6, the DbMigration class has methods such as AlterStoredProcedure which allow for modification of stored procedures without having to drop all the way down to raw SQL.

Here's an example of an Up() migration method which alters an existing SQL Server stored procedure named EditItem which takes three parameters of type int, nvarchar(50), and smallmoney, respectively:

public partial class MyCustomMigration : DbMigration
{
    public override void Up()
    {
        this.AlterStoredProcedure("dbo.EditItem", c => new
        {
            ItemID = c.Int(),
            ItemName = c.String(maxLength:50),
            ItemCost = c.Decimal(precision: 10, scale: 4, storeType: "smallmoney")
        }, @" (Stored procedure body SQL goes here) "   
    }

    //...
}

On my machine, this migration script produces the following SQL:

ALTER PROCEDURE [dbo].[EditItem]
    @ItemID [int],
    @ItemName [nvarchar](50),
    @ItemCost [smallmoney]
AS
BEGIN
    (Stored procedure body SQL goes here)
END


回答6:

emp's design works like a champion! I'm using his pattern but I also map stored procedures inside of my DbContext class which allows simply calling those context methods instead of using SqlQuery() and calling the procedures directly from my repository. As things can get a bit hairy when the application grows, I've created a check within my Seed method that makes sure the actual stored procedure parameter count match up to the parameter count on the mapping method. I've also updated the DROP loop emp mentioned. Instead of having to maintain a separate folder/file for the drop statements, I simply read the first line of each sql file and replace CREATE with DROP (just make sure the first line is always just CREATE PROCEDURE ProcName). This way all procedures in my StoredProcs folder get dropped and recreated each time Update-Database is ran. The drop is also wrapped in a try-catch block in case the procedure is new. For the procedure parameter count to work, you'll need to make sure you wrap a BEGIN/END block around your tsql since each line of the file is read up to BEGIN. Also make sure each sp parameter is on new line.

        // Drop Stored Procs
        foreach (var file in Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..\\DataContext\\SiteMigrations\\StoredProcs"), "*.sql"))
        {
            // Try to drop proc if its already created
            // Without this, for new procs, seed method fail on trying to delete
            try
            {
                StreamReader reader = new StreamReader(file);
                // Read first line of file to create drop command (turning CREATE [dbo].[TheProc] into DROP [dbo].[TheProc])
                string dropCommand = reader.ReadLine().Replace("CREATE", "DROP");

                context.Database.ExecuteSqlCommand(dropCommand, new object[0]);
            }
            catch { }

        }

        // Add Stored Procs
        foreach (var file in Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..\\DataContext\\SiteMigrations\\StoredProcs"), "*.sql"))
        {
            // File/Proc names must match method mapping names in DbContext
            int lastSlash = file.LastIndexOf('\\');
            string fileName = file.Substring(lastSlash + 1);
            string procName = fileName.Substring(0, fileName.LastIndexOf('.'));

            // First make sure proc mapping in DbContext contain matching parameters.  If not throw exception.
            // Get parameters for matching mapping
            MethodInfo mi = typeof(SiteContext).GetMethod(procName);

            if (mi == null)
            {
                throw new Exception(String.Format("Stored proc mapping for {0} missing in DBContext", procName));
            }

            ParameterInfo[] methodParams = mi.GetParameters();
            // Finished getting parameters

            // Get parameters from stored proc
            int spParamCount = 0;
            using (StreamReader reader = new StreamReader(file))
            {
                string line;                    
                while ((line = reader.ReadLine()) != null) 
                {
                    // If end of parameter section, break out
                    if (line.ToUpper() == "BEGIN")
                    {
                        break;
                    }
                    else
                    {
                        if (line.Contains("@"))
                        {
                            spParamCount++;
                        }
                    }                        
                }
            }
            // Finished get parameters from stored proc

            if (methodParams.Count() != spParamCount)
            {
                string err = String.Format("Stored proc mapping for {0} in DBContext exists but has {1} parameter(s)" +
                    " The stored procedure {0} has {2} parameter(s)", procName, methodParams.Count().ToString(), spParamCount.ToString());
                throw new Exception(err);
            }
            else
            {
                context.Database.ExecuteSqlCommand(File.ReadAllText(file), new object[0]);
            }
        }

Enjoy!



回答7:

As Ladislav pointed out, DbContext in general does tend to minimize the logic in the database, but it is possible to execute custom SQL by using context.Database.ExecuteSqlCommand() or context.Database.SqlQuery().