Entity Framework 4.1 Code First 非常适合创建表和关系。是否可以使用代码优先方法创建 sql 视图或存储过程?任何有关此的指针将不胜感激。非常感谢!
7 回答
我们在实体框架代码优先迁移中支持存储过程。我们的方法是创建一些文件夹来保存 .sql 文件(例如 ~/Sql/)。在文件夹中创建用于创建和删除存储过程的 .sql 文件。例如Create_sp_DoSomething.sql
和Drop_sp_DoSomething
。因为 SQL 是批处理运行的,并且CREATE PROCEDURE..
必须是批处理中CREATE PROCEDURE...
的第一条语句,所以将文件中的第一条语句设为第一条语句。另外,不要GO
放在DROP...
. 如果您还没有资源文件,请向您的项目添加资源文件。将 .sql 文件从解决方案资源管理器拖到资源设计器的文件视图中。现在创建一个空迁移 ( Add-Migration SomethingMeaningful_sp_DoSomething
) 并使用:
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]
乍一看,我真的很喜欢 Carl G 的方法,但它涉及大量手动交互。在我的场景中,我总是删除所有存储过程、视图……并在数据库发生更改时重新创建它们。通过这种方式,我们可以确保所有内容都与最新版本保持同步。
通过设置以下初始化程序来进行娱乐:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
然后,只要有迁移准备好,就会调用我们的种子方法
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 语句存储在 *.sql 文件中以便于编辑。确保您的文件将“构建操作”设置为“内容”,并将“复制到输出目录”设置为“始终复制”。我们查找文件夹并执行其中的所有脚本。不要忘记在 SQL 中排除“GO”语句,因为它们不能用 ExecuteSqlCommand() 执行。
我当前的目录布局如下:
Project.DAL
+ 迁移
+ Sql
++ 种子
+++ dbo.cleanDb.sql
++ StoredProcs
+++ dbo.sp_GetSomething.sql
现在您只需要在文件夹中删除额外的存储过程,所有内容都会得到适当的更新。
为了扩展bbodenmiller 的回答,在实体框架 6 中,DbMigration 类具有诸如 AlterStoredProcedure 之类的方法,这些方法允许修改存储过程,而不必一直下降到原始 SQL。
下面是一个Up()
迁移方法的示例,它改变了一个名为 EditItem 的现有 SQL Server 存储过程,该过程分别采用三个类型的参数int
,分别是nvarchar(50)
、 和smallmoney
:
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) "
}
//...
}
在我的机器上,此迁移脚本生成以下 SQL:
ALTER PROCEDURE [dbo].[EditItem]
@ItemID [int],
@ItemName [nvarchar](50),
@ItemCost [smallmoney]
AS
BEGIN
(Stored procedure body SQL goes here)
END
EF 代码优先方法期望数据库中没有逻辑。这意味着没有存储过程和数据库视图。由于这种代码优先方法不提供任何机制来自动为您生成此类构造。如果它意味着生成逻辑,它怎么能做到这一点?
您必须通过手动执行创建脚本在自定义数据库初始化程序中自己创建它们。我不认为 SQL 迁移可以处理这种自定义 SQL 构造。
它似乎没有很好的文档记录,但是您现在可以在 Entity Framework 6 中使用AlterStoredProcedure、CreateStoredProcedure、DropStoredProcedure、MoveStoredProcedure、RenameStoredProcedure进行一些存储过程操作。我还没有尝试过它们,所以还不能举例说明如何使用它们。
emp的设计就像一个冠军!我正在使用他的模式,但我也在我的 DbContext 类中映射存储过程,它允许简单地调用这些上下文方法而不是使用 SqlQuery() 并直接从我的存储库调用过程。当应用程序增长时,事情会变得有点麻烦,我在我的 Seed 方法中创建了一个检查,以确保实际的存储过程参数计数与映射方法上的参数计数相匹配。我还更新了提到的 DROP 循环 emp。我不必为 drop 语句维护一个单独的文件夹/文件,我只需读取每个 sql 文件的第一行并替换CREATE
为DROP
(只要确保第一行总是CREATE PROCEDURE ProcName
)。这样,每次运行 Update-Database 时,我的 StoredProcs 文件夹中的所有过程都会被删除并重新创建。如果这个过程是新的,drop 也被包裹在一个 try-catch 块中。为了使过程参数计数起作用,您需要确保BEGIN/END
在 tsql 周围包裹一个块,因为文件的每一行都被读取到 BEGIN。还要确保每个 sp 参数都在新行上。
// 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]);
}
}
享受!
正如 Ladislav 指出的那样,DbContext
通常确实倾向于最小化数据库中的逻辑,但可以使用context.Database.ExecuteSqlCommand()
or来执行自定义 SQL context.Database.SqlQuery()
。