EF6:创建存储过程。使用 Fluent API 或 DBMigrations?
Posted
技术标签:
【中文标题】EF6:创建存储过程。使用 Fluent API 或 DBMigrations?【英文标题】:EF6: Create stored procedure. Use Fluent API or DBMigrations? 【发布时间】:2016-05-19 15:36:53 【问题描述】:我首先使用 EF6 代码来创建我的数据库。我了解语法、DbContext 和模型构建器。我使用 LINQ 进行了几个详尽的查询,一切正常。
但是现在我需要做一些使用 linq 无法在一个查询中完成的事情。我需要使用存储过程执行 Merge 语句。
我见过几个关于如何创建存储过程的问题,例如: Create Stored Procedures using Entity Framework Code First?
大多数答案都是关于为 DbMigrations 创建派生类并覆盖 Up() 函数。我明白我应该在 Up 函数中写什么以确保创建存储过程。
但是我应该怎么做才能在创建数据库期间调用这个 Up 函数呢?
我应该在 DbContext.OnModelCreating 中做些什么吗?
我认为我不应该实例化 DbMigrations 的子类并调用 Up()。
上面提到的链接是关于“打开包管理器控件”的。那是什么?或者你真的在从旧版本迁移到新版本时使用这种方法吗?
【问题讨论】:
【参考方案1】:经过一番调查,我发现了如何确保在创建数据库时创建存储过程。我发现了两种方法,每种方法各有优缺点。因此,我对它们都进行了描述。对不起,如果这使答案相当长。
这里描述的两种方法是:
创建一个DataBase Initializer,一个实现IDataBaseInitializer的类。这可能是派生自 DropCreateDatabaseIfModelChanges 或类似的类。覆盖 Seed 函数并在此函数中使用 context.Database.ExecuteSqlCommand(...) 创建存储过程。 使用实体框架迁移来创建存储过程。第一种方法更简单。每当创建数据库时,就会调用 Seed 并创建存储过程。但是这种方法的缺点是,每当存储过程的参数名称或类型发生变化时,直到运行时才被检测到。
DbMigration 方法使用 lambda 表达式匹配存储过程的参数,因此每当参数的类型或名称发生变化时,编译器都会检测远程过程的定义是否与参数匹配。
我将描述这两种方法。这两个示例都有相同的简单 Hello World!过程和一个带有很多参数的大型 Merge 过程。
merge 语句的定义并不重要。什么 做的是它检查是否已经有一条记录匹配几个 属性,如果是这样,它会增加现有成本的成本。如果不是它 创建记录并使用成本初始化成本。这是一个 使用 linq 语句和 IQueryable 不够的典型示例。 使用 linq,必须检索记录、更新它并调用 SaveChanges,存在问题 (1) 与此同时,其他人可能已经增加了一个价值,并且 (2) 它需要 至少两次往返。因此需要一个存储过程。
方法 IDatabaseInitializer
在您的项目中,您为要访问的数据库表创建实体类和具有 DbSet 属性的类派生形式 DbContext。
例如:
public class UsageCosts
public int Id get; set;
public DateTime InvoicePeriod get; set;
public long CustomerContractId get; set;
public string TypeA get; set;
public string TypeB get; set;
public decimal VatValue get; set;
// the value to invoice
public decimal PurchaseCosts get; set;
public decimal RetailCosts get; set;
public class DemoContext : DbContext
public DemoContext(string nameOrConnectionString) : base(nameOrConnectionString)
public DbSet<UsageCosts> UsageCosts get; set;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
base.OnModelCreating(modelBuilder);
// add entity framework fluent api statements here
除了你的数据库类,创建一个数据库初始化器,它有一个函数 Seed 将在创建数据库时调用。
internal class DataBaseInitializer : DropCreateDatabaseIfModelChanges<DemoContext>
protected override void Seed(DemoContext context)
base.Seed(context);
// create stored procedures here
this.CreateStoredProcedureHelloWorld(context)
this.CreateStoredProcedureUpdateUsageCosts(context)
展示如何创建存储过程的简单示例(Hello World!)
private void CreateStoredProcedureHelloWorld(DemoContext context)
context.Database.ExecuteSqlCommand("create procedure HelloWorld as begin Select 'Hello World' end;");
使用输入参数创建存储过程:
private void CreateStoredProcedureUpdateUsageCosts(DemoContext context)
var x = new StringBuilder();
x.AppendLine(@"create procedure updateusagecosts");
x.AppendLine(@"@InvoicePeriod datetime,");
x.AppendLine(@"@CustomerContractId bigint,");
x.AppendLine(@"@TypeA nvarChar(80),");
x.AppendLine(@"@TypeB nvarChar(80),");
x.AppendLine(@"@VatValue decimal(18, 2),");
x.AppendLine(@"@PurchaseCosts decimal(18, 2),");
x.AppendLine(@"@RetailCosts decimal(18, 2)");
x.AppendLine(@"as");
x.AppendLine(@"begin");
x.AppendLine(@"Merge [usagecosts]");
x.AppendLine(@"Using (Select @InvoicePeriod as invoicePeriod,");
x.AppendLine(@" @CustomerContractId as customercontractId,");
x.AppendLine(@" @TypeA as typeA,");
x.AppendLine(@" @TypeB as typeB,");
x.AppendLine(@" @VatValue as vatvalue)");
x.AppendLine(@" As tmp ");
x.AppendLine(@"On ([usagecosts].[invoiceperiod] = tmp.invoiceperiod");
x.AppendLine(@"AND [usagecosts].[customercontractId] = tmp.customercontractid");
x.AppendLine(@"AND [usagecosts].[typeA] = tmp.typeA");
x.AppendLine(@"AND [usagecosts].[typeB] = tmp.typeB");
x.AppendLine(@"AND [usagecosts].[vatvalue] = tmp.Vatvalue)");
x.AppendLine(@"When Matched Then ");
x.AppendLine(@" Update Set [usagecosts].[purchasecosts] = [usagecosts].[purchasecosts] + @purchasecosts,");
x.AppendLine(@" [usagecosts].[retailcosts] = [usagecosts].[retailcosts] + @retailcosts");
x.AppendLine(@"When Not Matched Then");
x.AppendLine(@" Insert (InvoicePeriod, CustomerContractId, typea, typeb, vatvalue, purchasecosts, retailcosts)");
x.AppendLine(@" Values (@invoiceperiod, @CustomerContractId, @TypeA, @TypeB, @VatValue, @PurchaseCosts, @RetailCosts);");
x.AppendLine(@"end");
context.Database.ExecuteSqlCommand(x.ToString());
The hello world example can be found here on ***
StringBuilder 的方法也可以在 *** 上的某个地方找到,可惜我找不到。
在创建数据库期间调用 DatabaseInitializer.Seed(...)。这里的上下文被命令执行一条 SQL 语句。该语句是一个字符串。 这就是为什么编译器不会注意到函数的名称或参数类型的变化。
DbMigration 方法
有关迁移,请参阅:
MSDN: Enabling Migrations Creating and Calling Stored Procedure from Entity Framework 6 Code First这个想法是让 Visual Studio 包管理器创建一个具有 Up() 函数的 DbManager 派生类。每当数据库向上迁移到派生类的版本时,都会调用此函数。
在 Up() 内部,您可以调用基类 DbMigration.CreateStoredProcedure。这种方法的好处是从实体类型到参数的转换是使用委托(使用 lambda 表达式)完成的,因此在编译时检查:属性是否仍然存在并且它们是否具有正确的类型?
唉,仅仅从 DbMigration 构造派生类并从 Seed() 函数中调用 Up() 函数是不够的。
要确保调用 Up() 函数,最简单的方法是让 Visual Studio 执行此操作。
创建您的项目 为实体框架添加 Nuget 包 使用实体类的 DbSet 属性创建实体类和 DbContext 在 Visual Studio 中,通过“工具”菜单启动 Nuget 包管理器控制台 使用 Nuget 包管理器控制台启用迁移,使用命令 Enable-Migrations 使用 Nuget 包管理器控制台添加一个迁移并提供一个名称,例如 InitialCreation 使用命令 add-Migration InitialCreation您会注意到在您的项目中添加了几个类。
配置派生自带有函数 Seed() 的 DbMigratinConfiguration InitialCreation 从 DbMigration 派生,带有函数 Up()(和函数 Down()。在这个 Up 中,您将看到一个或多个 CreateTable 函数如果您仍然有前面示例中描述的数据库播种器类,并且您使用 DataBase.SetInitializer 对其进行初始化,那么每当需要重新创建数据库时,就会调用各种 Up() 和 Seed() 函数按以下顺序:
配置的构造函数 InitialCreation.Up() DatabaseSeeder.Seed()由于某种原因,未调用 Configuration.Seed()。
这让我们有机会在 InitialCaeation.Up() 中创建存储过程
public override void Up()
CreateTable("dbo.UsageCosts",
c => new
Id = c.Int(nullable: false, identity: true),
InvoicePeriod = c.DateTime(nullable: false),
CustomerContractId = c.Long(nullable: false),
TypeA = c.String(),
TypeB = c.String(),
VatValue = c.Decimal(nullable: false, precision: 18, scale: 2),
PurchaseCosts = c.Decimal(nullable: false, precision: 18, scale: 2),
RetailCosts = c.Decimal(nullable: false, precision: 18, scale: 2),
)
.PrimaryKey(t => t.Id);
“Hello World”存储过程的创建如下:
base.CreateStoredProcedure("dbo.HelloWorld3", "begin Select 'Hello World' end;");
带输入参数的存储过程:
base.CreateStoredProcedure("dbo.update2", p => new
InvoicePeriod = p.DateTime(),
CustomerContractId = p.Long(),
TypeA = p.String(maxLength: 80),
TypeB = p.String(maxLength: 80),
VatValue = p.Decimal(10, 8),
PurchaseCosts = p.Decimal(10, 8),
RetailCosts = p.Decimal(10, 8),
,
@"begin
Merge [usagecosts]
Using (Select
@InvoicePeriod as invoicePeriod,
@CustomerContractId as customercontractId,
@TypeA as typeA,
@TypeB as typeB,
@VatValue as vatvalue)
As tmp
On ([usagecosts].[invoiceperiod] = tmp.invoiceperiod
AND [usagecosts].[customercontractId] = tmp.customercontractid
AND [usagecosts].[typeA] = tmp.typeA
AND [usagecosts].[typeB] = tmp.typeB
AND [usagecosts].[vatvalue] = tmp.Vatvalue)
When Matched Then
Update Set [usagecosts].[purchasecosts] = [usagecosts].[purchasecosts] + @purchasecosts, [usagecosts].[retailcosts] = [usagecosts].[retailcosts] + @retailcosts
When Not Matched Then
Insert (InvoicePeriod, CustomerContractId, typea, typeb, vatvalue, purchasecosts, retailcosts)
Values (@invoiceperiod, @CustomerContractId, @TypeA, @TypeB, @VatValue, @PurchaseCosts, @RetailCosts);
end;");
记住Down()
方法:
public override void Down()
this.DropStoredProcedure("dbo.update2");
为了完整性:远程过程调用
using (var dbContext = new DemoContext())
object[] functionParameters = new object[]
new SqlParameter(@"InvoicePeriod", usageCosts.InvoicePeriod),
new SqlParameter(@"CustomerContractId", usageCosts.CustomerContractId),
new SqlParameter(@"TypeA", usageCosts.TypeA),
new SqlParameter(@"TypeB", usageCosts.TypeB),
new SqlParameter(@"VatValue", usageCosts.VatValue),
new SqlParameter(@"PurchaseCosts", 20M),
new SqlParameter(@"RetailCosts", 30M),
;
string sqlCommand = String.Format(@"Exec 0 @InvoicePeriod, @CustomerContractId, @TypeA, @TypeB, @VatValue, @PurchaseCosts, @RetailCosts", functionName);
dbContext.Database.ExecuteSqlCommand(sqlCommand, functionParameters);
dbContext.SaveChanges();
在我看来,最好将它放在 DbSet 的扩展方法中。每当 UsageCosts 发生变化时,编译器就可以检查名称和属性类型。
【讨论】:
以上是关于EF6:创建存储过程。使用 Fluent API 或 DBMigrations?的主要内容,如果未能解决你的问题,请参考以下文章
使用 Entity Framework 6.1 fluent API 创建唯一索引
EF 6.X 中的实体框架代码优先 Fluent API 默认值