我首先使用EF6代码来创建我的数据库。我理解语法,DbContext和模型构建器。我使用LINQ进行了几次详尽的查询,一切正常。
但是现在我需要做一些使用linq在一个查询中无法完成的事情。我需要使用存储过程执行Merge语句。
我已经看到了几个关于如何创建存储过程的问题,例如: Create Stored Procedures using Entity Framework Code First?
大多数答案都在谈论为DbMigrations创建派生类并重写Up()函数。我理解我应该在Up函数中编写什么来确保创建存储过程。
但是我应该怎么做才能在数据库创建过程中调用Up函数?
我应该在DbContext.OnModelCreating中做些什么吗?
我认为我不应该实例化DbMigrations的子类并调用Up()。
上面提到的链接是关于“打开包管理器控件”。那是什么?或者,在从较旧版本迁移到较新版本时,您是否真的使用此方法?
答案 0 :(得分:9)
经过一番调查后,我发现如何确保在创建数据库时创建存储过程。我发现了两种方法,每种方法各有优缺点。因此我描述了它们。对不起,如果这个答案相当长。
这里描述的两种方法是:
第一种方法更简单。无论何时创建数据库,都会调用Seed并创建存储过程。但是,此方法的缺点是,只要存储过程的参数的名称或类型发生更改,就不会在运行时检测到这种情况。
DbMigration方法使用lambda表达式匹配存储过程的参数,因此只要参数的类型或名称发生更改,编译器就会检测远程过程的定义是否与参数匹配。
我将描述这两种方法。这两个例子都有相同的简单Hello World!过程和带有大量参数的大合并程序。
合并声明的定义并不重要。什么 确实是否已经有匹配的记录 属性,如果是这样,它会增加现有成本的成本。如果没有 创建记录并使用成本初始化成本。这是一个 使用linq语句和IQueryable的典型示例是不够的。 使用linq,必须检索记录,更新它并调用 SaveChanges,问题(1)在 与此同时,其他人可能已经添加了一个值,并且(2)它需要 至少两次往返。因此需要存储过程。
在您的项目中,您可以为要访问的数据库表创建实体类和一个派生自DbContext的类,其中包含DbSet属性。
例如:
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 StackOverflow
使用StringBuilder的方法也可以在StackOverflow的某个地方找到,但是我无法找到它。
在创建数据库期间,调用DatabaseInitializer.Seed(...)。这里命令上下文执行SQL语句。该语句是一个字符串。 这就是为什么编译器不会注意到函数参数名称或类型的变化。
对于迁移,请参阅:
我们的想法是让visual studio包管理器创建一个具有Up()函数的DbManager派生类。只要将数据库向上迁移到派生类的版本,就会调用此函数。
在Up()中,您可以调用基类DbMigration.CreateStoredProcedure。这个方法的好处是从实体类型到参数的转换是使用委托(使用lambda表达式)完成的,因此在编译时检查:属性是否仍然存在并且它们是否具有正确的类型?
唉,从DbMigration构造派生类是不够的,并从Seed()函数中调用Up()函数。
为了确保调用Up()函数,最简单的方法是让visual studio执行此操作。
您会注意到您的项目中添加了几个类。
如果您仍然拥有上一个示例中所述的数据库播种器类,并且您使用DataBase.SetInitializer对其进行初始化,那么每当需要重新创建数据库时,都会调用各种Up()和Seed()函数按以下顺序:
由于某种原因,未调用Configuration.Seed()。
这使我们有机会在InitialCraeation.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);
&#34; Hello World&#34;存储过程创建如下:
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;");
}
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更改时,编译器都可以检查名称和属性类型。