EF Migration script to exit migration on a condition - entity-framework

I want to abort the migration in code first approach on a condition basis.
Suppose, if the condition is true, I want to exit the migration without making changes to the database.

This should be possible with MigrationBuilder.Sql(String, Boolean) method, so create an SQL script with what you want to do with IF...ELSE conditions. Then create an empty migration and write the script in the Sql() method as a verbatim string.
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(#"
BEGIN
DECLARE #sales INT;
SELECT
#sales = SUM(list_price * quantity)
FROM
sales.order_items i
INNER JOIN sales.orders o ON o.order_id = i.order_id
WHERE
YEAR(order_date) = 2018;
SELECT #sales;
IF #sales > 1000000
BEGIN
-- // What you want to do if sales > 1,000,000
END
END
");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// But you need to write another script to revert the changes done by Up method.
}
If you don't know how to create a script for your migration, use Script-Migration.
I don't think you can run queries or inject services to migrations, since the Entity-framework command-line tool analyzes your code but does not run the startup.cs class.

Related

Prevent LinqPad from querying __MigrationHistory

This is about a performance optimization:
I use LinqPad to access a database using an EF 6 DbContext. The database is created using code first.
When I run a query, I see the correct result, but I see in the "SQL"-tab that LinqPad (and/or EF) emmits SQL to check the migration history:
-- Region Parameters
-- p__linq__0: String [UserQuery]
-- EndRegion
SELECT
"GroupBy1".A1 AS C1
FROM ( SELECT Count(1) AS A1
FROM ARIANE_ADMIN."__MigrationHistory" "Extent1"
WHERE "Extent1"."ContextKey" = :p__linq__0
) "GroupBy1"
GO
-- Region Parameters
-- p__linq__0: String [UserQuery]
-- EndRegion
SELECT
"GroupBy1".A1 AS C1
FROM ( SELECT Count(1) AS A1
FROM "__MigrationHistory" "Extent1"
WHERE "Extent1"."ContextKey" = :p__linq__0
) "GroupBy1"
GO
SELECT
"GroupBy1".A1 AS C1
FROM ( SELECT Count(1) AS A1
FROM "__MigrationHistory" "Extent1"
) "GroupBy1"
GO
SELECT
"Extent1"."Id",
"Extent1"."ModelHash"
FROM "EdmMetadata" "Extent1"
ORDER BY "Extent1"."Id" DESC
FETCH FIRST 1 ROWS ONLY
GO
Only then there is the actual query.
Since I usually access the DB through multiple layers of VPN, the extra queries cost more than a second.
My questions are:
Can I avoid the query to '__MigrationHistory' alltogether?
If not: Is there a way to pass the correct parameter instead of '[UserQuery]', so that the first query returns the correct result?
I connect to an Oracle server using Devart dotconnect for Oracle.
As Steve Greene suggested, you have to set a null initializer. But not for your DbContext. It must be set for the UserQuery-Type that LinqPad dynamically creates.
It can be done by writing
Database.SetInitializer<UserQuery>(null);
at the beginning of every LinqPad query.
A more general approach is to set it in your dll using reflection.
Add this extension method
public static class DbContextExtensions
{
public static void RemoveLinqpadInitializer(this DbContext context)
{
var contextType = context.GetType();
if (contextType.Name == "UserQuery")
{
var setInitializer = typeof(Database).GetMethod(nameof(Database.SetInitializer))?.MakeGenericMethod(contextType);
setInitializer?.Invoke(null, new object[] {null});
}
}
}
and call it in your DbContext:
public class MyDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
Database.SetInitializer<MyDbContext>(null); // or any other initializer
this.RemoveLinqpadInitializer();
}
}

Entity Framework code-first & stored procedure

I am using a code-first approach in my application. I have generated the entities (tables) using the approach. Now I would like to create a stored procedure as well through code. Can someone guide me as I tried migration option and it's failing.
I am using Entity Framework Code First approach.Using this,I have created Customer and some other entities.
Now I want to create a stored procedure 'GetCustomers' using context class and pass parameters and get result set in a collection
it has to return 2 collections as below
create procedure getcustomer #name nvarchar(max),#zipcode int
as
select id,name,zipcode from Customer where name like (#name );
select id,name,zipcode from Customer where zipcode =#zipcode
I want to create a stored procedure 'GetCustomers' using context class and not manually execute in DB.I need to achieve below results:
1.Pass name parameter alone and return first collection
2.Pass zipcode parameter alone and return 2nd collection.
3.Combine result collection of 1 and 2 into a single collection using merge
You can create/generate stored Procedure using CreateStoredProcedure() method using Add-
Migration option in Entity Framework.
Step 1: Generate Migration script using add-migration SP_DO_NOT_DELETE in Package Manager Console. If no Model Changes is there, then the system will generate Empty migration script like below.
public partial class SP_DO_NOT_DELETE : DbMigration
{
public override void Up()
{
}
public override void Down()
{
}
}
Step 2: After generating the Script, Please add your stored procedure inside Up() and down() methods like below. Note: below example, "dbo.GetNextDisplayId" is the Stored Procedure Name which will be used to get the NextAvailableDisplayId using Stored procedure.
public partial class SP_DO_NOT_DELETE : DbMigration
{
public override void Up()
{
CreateStoredProcedure(
"dbo.GetNextDisplayId",
body:
#"DECLARE #requestid INT
SELECT #requestid = NextAvailableDisplayId
FROM [TrackingNumberHistories] WITH (TABLOCKX)
UPDATE [TrackingNumberHistories]
SET NextAvailableDisplayId = #requestid + 1
SELECT #requestid AS 'NextAvailableDisplayId'"
);
}
public override void Down()
{
DropStoredProcedure("dbo.GetNextDisplayId");
}
}
Note: CreateStoredProcedure() in Up() Method will create Stored procedure automatically whenever running migration script. DropStoredProcedure() in Down() will be used to drop stored procedure when ever we roll back/delete the stored procedure automatically in migration script.
Hope this might help you to move forward!!

Custom logic in code-first EF6 SqlServerMigrationSqlGenerator not working

I am trying to set the default value SQL for a computed column called 'Duration' in a table 'dbo.Table1', in code-first Entity Framework 6 migration through SqlServerMigrationSqlGenerator class.
I tried setting this in Generate methods for AddColumnOperation as well as for CreateTableOperation. While the code under Generate method for column never fires, but the code under Generate table fires and throws an error saying the following. (the column EndTime is a column in table dbo.Table1 and so is StartTime)
The name "EndTime" is not permitted in this context. Valid expressions
are constants, constant expressions, and (in some contexts) variables.
Column names are not permitted.
Question: How could I make this work in either of the Generate methods in code below?
internal class CustomImplForSqlServerMigration: SqlServerMigrationSqlGenerator {
protected override void Generate(AlterColumnOperation alterColumnOperation) {
base.Generate(alterColumnOperation);
}
protected override void Generate(AddColumnOperation addColumnOperation) {
if (addColumnOperation.Table == "dbo.Table1" && addColumnOperation.Column.Name == "Duration") {
addColumnOperation.Column.DefaultValueSql = "(CAST(CAST(EndTime AS DATETIME) - CAST(StartTime AS DATETIME) AS TIME))";
}
base.Generate(addColumnOperation);
}
protected override void Generate(CreateTableOperation createTableOperation) {
if (createTableOperation.Name == "dbo.Table1") {
foreach(ColumnModel cm in createTableOperation.Columns) {
if (cm.Name == "Duration") {
cm.DefaultValueSql = "(CAST(CAST(EndTime AS DATETIME) - CAST(StartTime AS DATETIME) AS TIME))";
}
}
}
base.Generate(createTableOperation);
}
}
UPDATE 1:
I used another simple approach to add my custom logic for modifying database objects using ExecuteSqlCommand. Just follow the steps below to use this in your situation.
come up with the custom script for modifying or creating a database object
execute a command in Seed method for every custom script
make sure that the ExecuteSqlCommand statement is placed at end of Seed method and also context.SaveChanges( ) method is called before the code for custom scripts in case there is a dependency on seed data
protected override void Seed(EfCodeFirst.ShiftsDb context)
{
//Write your seed data statements
//call SaveChanges in case your custom script depends
//on some seed data
context.SaveChanges();
//include your custom scripts like ALTER TABLE
//or CREATE PROCEDURE or anything else
//use a ExecuteSqlCommand for every custom script
context.Database.ExecuteSqlCommand(#"ALTER TABLE ShiftTypes DROP COLUMN Duration;
ALTER TABLE TABLE1 ADD Duration AS (CAST(CAST(EndTime AS DATETIME) -
CAST(StartTime AS DATETIME) AS TIME)); ");
}
This is not a limitation of EF but of the database itself - you cannot refer to other columns within Default value specification. What I would recommend you instead is to write stored procedure for inserting new Table1 entities and then map with fluent api according to article.

Unit Test with transaction: CREATE DATABASE statement not allowed within multi-statement transaction

I have base class for my unit tests:
public abstract class DatabaseTestsBase
{
protected OvuContext DbContext;
protected TransactionScope TransactionScope;
[SetUp]
public void TestSetup()
{
TransactionScope = new TransactionScope(TransactionScopeOption.RequiresNew);
DbContext = new OvuContext("IntegrationTestContext");
}
[TearDown]
public void TestCleanup()
{
TransactionScope.Dispose();
}
}
And the test itself:
[TestFixture]
public class MyEntityTests : DatabaseTestsBase
{
[Test]
public void MyEntity_Create_HasSucceeded()
{
var myEntity = new MyEntity(Guid.NewGuid());
myEntity.Description = "bla bla bla";
var id = myEntity.Id;
DbContext.MyEntities.Add(myEntity);
DbContext.SaveChanges();
var temp = DbContext.MyEntities.Single(x => x.Id == id);
ComparisonResult result = CompareLogic.Compare(myEntity, temp);
Assert.True(result.AreEqual);
}
}
I got the following error when the test runs:
CREATE DATABASE statement not allowed within multi-statement transaction
The 'strange' thing is that when I run the test a second time, it passes.
I believe you are taking the pattern of rolling back database side-effecting Integration / Unit Tests done under an uncommitted transaction (such as TransactionScope) a bit far :)
Code First EF is attempting to create the database under the context of the ambient TransactionScope, which isn't possible.
In Sql Server (and possibly other RDBMS's), it isn't possible to execute certain statements, such as CREATE DATABASE, command under a transaction. You'll get the same error if you execute the following directly in SSMS:
use master;
BEGIN TRAN
CREATE DATABASE FOO;
-- CREATE DATABASE statement not allowed within multi-statement transaction.
I would suggest that you change your strategy such that you pre-create a permanent, empty test database for testing. Other DDL statements executed by EF Code First, such as CREATE TABLE can be rolled back by the uncommitted TransactionScope.

Entity Framework 4 - Get generated SQL for Updates/Inserts

With EF4, is it possible to get the generated SQL for Updates/Inserts rather than executing it... just like you can view the query SQL before it runs.
The reason is, I have a set of helper functions that execute SQL commands. For instance...
Decrement<Category>("ProductCount", categoryID);
SetNull<Product>("CategoryID", productID);
Which generates...
UPDATE Categories
SET ProductCount = ProductCount - 1
WHERE CategoryID = #CategoryID;
UPDATE Products
SET CategoryID = NULL
WHERE CategoryID = #ProductID;
I usually run several commands per operation, so with each helper function call, the SQL is generated and stored. When I call SaveChanges(), all of the commands are run at ONE time.
The only problem is that EF runs its commands separately behind the scenes, then I run the others right afterward. It would be ideal to run everything as one single command.
You can get it at design time with Sql Profiler, but I think you're meaning that you want it at run-time. Here's an example I found on how to do that:
public static void WriteGeneratedSql(EntityCommand cmd)
{
cmd.Prepare();
IServiceProvider isp = (IServiceProvider)EntityProviderFactory.Instance;
DbProviderServices mps = (DbProviderServices)isp.GetService(typeof(DbProviderServices));
EntityCommandDefinition definition = (EntityCommandDefinition)mps.CreateCommandDefinition(cmd);
int commandId = 1;
foreach (string commandText in definition.MappedCommands)
{
Console.WriteLine("Generated Command {0}:", commandId);
commandId++;
Console.WriteLine(commandText);
}
}
Found here.