Prevent LinqPad from querying __MigrationHistory - entity-framework

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();
}
}

Related

EF Migration script to exit migration on a condition

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.

Entity Framework Intercept Generate Migration Script

I use Entity Framework 6.2 Code First (.net framework 4.6.1) and I map few entities to view via Table Attribute. It works for select operations and I handle Insert/Update/Delete with writing trigger to view at sql server side. It works as expected, however when I add a new migration, Entity Framework generate RenameTable scripts for used Table Attribute (actuallyis expected behavior for EF). But I want to intercept migration generation and change these entities tableName to original name.
my code like;
[MapToView("Users","UsersView")]
public class User
{
...
}
I wrote MapToView Attribute, this attribute inherited by TableAttribute and pass to second parameter to TableAttribute. I create this Attribute because if I intercept migration generation, return original table name with this attribute parameters.
In this case when I run "add-migration migrationName" it creates migration scripts like this;
RenameTable(name: "dbo.Users", newName: "UsersView");
but i want to create empty migration when I run "add-migration migrationName" script.
anyone can help me?
I solve the problem.
First: Problem is; When I Map Entity to View EF Code-first generate migration with ViewName. This is problem because I want to use View Instead of Table. So I solve problem with this instructions;
1- I Create BaseEntityConfiguration that Inherited from EntityTypeConfiguration and all entity configuration classes are inherited by.
for example:
public class UserConfig: BaseEntityConfiguration<User> //Generic Type is Entity
{
public UserConfig()
{
}
}
2- I Create MapToViewAttribute that inherited by TableAttribute
public class MapToViewAttribute : TableAttribute
{
public string TableName { get; }
public string ViewName { get; }
public MapToViewAttribute(string tableName, string viewName) : base(viewName)
{
TableName = tableName;
ViewName = viewName;
}
}
3- I Use MapToViewAttribute for example User Entity to use View.
[MapToView("User","UserView")]
public class User
{
...
}
And in BaseEntityConfiguration's Constructor I Get Generic Type and custom attributes. If any entity has MapToView Attribute, I pass to TableName parameter to ToTable Method. So at runtime EF uses View for these entities but doesn't create migration with RenameTable for these entities.
protected BaseEntityConfiguration()
{
var baseType = typeof(TEntityType);
var attributes = baseType.GetCustomAttributes(true);
foreach (var attr in attributes)
{
if (attr.GetType() == typeof(MapToViewAttribute))
{
var tableName = ((MapToViewAttribute)attr).TableName;
ToTable(tableName);
}
}
}
Last EF don't use your configuration files, so you must tell the EF to use this in DbContext class's InternalModelCreate method.
My implementation like this;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var typesToRegister = Assembly.GetExecutingAssembly()
.GetTypes().Where(IsConfigurationType);
foreach (var type in typesToRegister)
{
dynamic configurationInstance = type.BaseType != null
&& type.BaseType.IsGenericType
&& type.BaseType.GetGenericTypeDefinition() == typeof(BaseEntityConfiguration<>)
? Activator.CreateInstance(type, culture)
: Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
modelBuilder.Types().Configure(t => t.ToTable(t.ClrType.Name));
BaseDbContext.InternalModelCreate(modelBuilder);
}
But if you use this approach you must create Insert, Update and Delete Triggers/Rule (if you use SQLServer trigger is an option but if you use postgresql rule is better option) because EF uses this view for insert, update and delete operations.

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!!

Extra Column in Many to Many Relationship in Entity Framework 5.0 reviewed

I'm using the newest Entity Framework and ran into a problem with Many To Many Relationship when I want to create an extra column.
The issue is the same raised in this older post:
EF Code First Additional column in join table for ordering purposes
Is it still the problem today that one can not add an extra column without loosing the many to many relation ship (link from object A to B as A.B because the mapping becomes and entity it self) ?
What are the work a rounds ?
Look up the a of class A I need and then query for mapping table where(e=>e.A == a) to get my Bs? And when I need the extra colums i would do MappingTable.find(a,b) ?
Are there other modeling options, linq to sql that would make it easier ?
As far as I know things haven't changed with EF 5. You would need to do it as the link says to. I like to stick with EF as its easy to use, but that's just my opinion...
I had the same problem. What I did to work-around it was create another derivative DbContext specifically to handle joins. I.E.:
public class JoinContext : DbContext
{
internal JoinContext() : base("name=SampleConnectionString")
{
PreventErrorIfDatabaseSchemaChanges();
// Get the ObjectContext related to this DbContext
var objectContext = (this as IObjectContextAdapter).ObjectContext;
}
public DbSet<StudentImage> StudentImages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<StudentImage>()
.ToTable("StudentImages");
.HasKey(joinTable => new { joinTable.StudentId, joinTable.ImageId });
base.OnModelCreating(modelBuilder);
}
private static void PreventErrorIfDatabaseSchemaChanges()
{
Database.SetInitializer<JoinContext>(null);
}
}
I left the other application context with the Student/Image many-to-many join mapping as-is. Don't forget to specify a compounded key for the join table (refer to HasKey method above), else EF bombs on databse initialization.
After you have your special join context, use a repository to access this context and get or set the desired fields from mapped join table:
public class StudentRepository
{
public int GetImageSortOrder(int studentId, int imageId)
{
var joinContext = new JoinContext();
var joinTuple = joinContext.StudentImages.Find(studentId, imageId);
return joinTuple.SortOrder;
}
}
Hope this helps!

Custom Initialization Strategy for EF Code First that doesn't drop tables to add a column

The latest EF Code First NuGet package comes with a custom implementation of IDatabaseInitializer called DontDropDbJustCreateTablesIfModelChanged. As the name implies, when a model change is detected, it will not drop and recreate the whole database, just drop and recreate the tables.
Say I have this model class:
public class User
{
public string Username { get; set; }
// This property is new; the model has changed!
public string OpenID { get; set; }
}
How would one go about implementing an IDatabaseInitializer that doesn't drop any tables either. In this case, it would just add an OpenID column to the User table?
I think it is a matter of SQL. So for SQL Server you can write something like:
public class MyInitializer : IDatabaseInitializer<MyContext>
{
public void InitializeDatabase(MyContext context)
{
context.Database.SqlCommand(
#"
IF NOT EXISTS (SELECT 1 FROM sys.columns AS col
INNER JOIN sys.tables AS tab ON tab.object_Id = col.object_Id
WHERE tab.Name = 'User' AND col.Name = 'OpenId')
BEGIN
ALTER TABLE dbo.User ADD OpenId INT;
END");
}
}
But in the same way you can execute such script without adding it to your application which I think is much better approach.
With the current version of Code First, you cannot simply amend your schema and preserve any data that you might have in your tables. If maintaining data, such as reference data / lookup tables is important with this release you can create your own Initializer and override the Seed method to populate your tables
public class MyDbInitializer : DropCreateDatabaseIfModelChanges<MyContext>
{
protected override void Seed(MyContext context)
{
var countries = new List<Country>
{
new Country {Id=1, Name="United Kingdom"},
new Country{Id=2, Name="Ireland"}
};
countries.ForEach(c => context.Countries.Add(c));
}
}
And then use this in your Application_Start:
Database.SetInitializer<MyContext>(new MyDbInitializer());
I believe that this is being addressed currently by the EF Team, but wasn't ready for release at the time the Code First drop came out. You can see a preview here: Code First Migrations