Custom logic in code-first EF6 SqlServerMigrationSqlGenerator not working - entity-framework

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.

Related

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 Filter Index

I use EF 6.1.x Code First.
I have read that an Index with Filter Expression is not supported by EF latest.
There is also no solution on SO:
EF 6.1 Unique Nullable Index
One year later, what is the working way to make a Filter Index work with Code First and DbMigrations?
CREATE UNIQUE NONCLUSTERED INDEX [IX_DefaultLanguageApplicationId] ON [dbo].[Languages]
(
[IsDefaultLanguage] ASC,
[ApplicationId] ASC,
)
WHERE ([IsDefaultLanguage]=(1))
In EF 6.1, the working way to make the this work with Code First and DbMigrations is to use the Sql method in the DbMigration class:
public partial class AddIndexes : DbMigration
{
public override void Up()
{
Sql(#"CREATE UNIQUE NONCLUSTERED INDEX
[IX_DefaultLanguageApplicationId] ON [dbo].[Languages]
(
[IsDefaultLanguage] ASC,
[ApplicationId] ASC
)
WHERE ([IsDefaultLanguage]=(1))");
}
public override void Down()
{
DropIndex("dbo.Languages", "IX_DefaultLanguageApplicationId");
}
}
But I realise that you are probably asking if you can create an index using the IndexAttribute introduced in 6.1, but with an Filter - the answer to that is "No"
Almost a duplicate of: Entity Framework 6.1 - Create index with INCLUDE statement
Please note that right now EF core 2.1.X added built in support for filtered indexes via the HasFilter extension on the IndexBuilder, so a custom implementation is not required anymore.
See this for more details
I know that the original post referred to the 6.1 version of EF, but after some research I have found a way to add an extension method for filtered indexes to the fluent api of EF Core (1.1 version). Maybe someone will find this useful (and maybe there is a way to implement this also in older versions).
I have to warn you though. As this solution uses classes from within Microsoft.EntityFrameworkCore.Migrations.Internal and Microsoft.EntityFrameworkCore.Infrastructure namespaces, it’s no guaranteed that this code will work after EF gets updated. There is a massage included in a summary of each class within these namespaces saying that
This API may change or be removed in future releases
, so you have been warned.
But to the point.
First you have to create a standard extension method for the IndexBuilder. Its main responsibility is going to be adding a new annotation with the condition to the constructed index. One will use this method afterwards with the fluent api. Lest call our annotation SqlServer:FilteredIndex.
static class FilteredIndexExtension
{
public static IndexBuilder Filtered(this IndexBuilder indexBuilder, string condition)
{
indexBuilder.HasAnnotation("SqlServer:FilteredIndex", condition);
return indexBuilder;
}
}
Next you have to allow this annotation to be actually included inside migrations. You have to override the default behavior of SqlServerMigrationsAnnotationProvider for index builders.
class ExtendedSqlServerMigrationsAnnotationProvider : SqlServerMigrationsAnnotationProvider
{
public override IEnumerable<IAnnotation> For(IIndex index)
{
var baseAnnotations = base.For(index);
var customAnnotatinos = index.GetAnnotations().Where(a => a.Name == "SqlServer:FilteredIndex");
return baseAnnotations.Concat(customAnnotatinos);
}
}
Now comes the most difficult part. We have to override the default behavior of SqlServerMigrationsSqlGenerator regarding indexes.
class ExtendedSqlServerMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator
{
public ExtendedSqlServerMigrationsSqlGenerator(IRelationalCommandBuilderFactory commandBuilderFactory, ISqlGenerationHelper sqlGenerationHelper, IRelationalTypeMapper typeMapper, IRelationalAnnotationProvider annotations, IMigrationsAnnotationProvider migrationsAnnotations) : base(commandBuilderFactory, sqlGenerationHelper, typeMapper, annotations, migrationsAnnotations)
{
}
protected override void Generate(CreateIndexOperation operation, IModel model, MigrationCommandListBuilder builder, bool terminate)
{
base.Generate(operation, model, builder, false);
var filteredIndexCondition = operation.FindAnnotation("SqlServer:FilteredIndex");
if (filteredIndexCondition != null)
builder.Append($" WHERE {filteredIndexCondition.Value}");
if (terminate)
{
builder.AppendLine(SqlGenerationHelper.StatementTerminator);
EndStatement(builder);
}
}
}
As you can see, we are calling the base generator here, so our condition will be added at the end of it without altering it. We have to remember not to terminate the base SQL statement here (last argument passed to the base.Generate method is false). If our annotation is set we can append its value after the WHERE clause at the end of the SQL statement. After that, depending on the argument passed to this method, we can finally terminate the statement or leave it as it is.
For all those parts to work we have to replace old services with their new versions by overriding the OnConfiguring method of our DbContext.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.ReplaceService<SqlServerMigrationsAnnotationProvider, ExtendedSqlServerMigrationsAnnotationProvider>();
optionsBuilder.ReplaceService<SqlServerMigrationsSqlGenerator, ExtendedSqlServerMigrationsSqlGenerator>();
}
Now we can use our extension method like this:
builder.HasIndex(a => a.Identity).IsUnique().Filtered("[End] IS NULL");
It will generate migration like this:
migrationBuilder.CreateIndex(
name: "IX_Activities_Identity",
table: "Activities",
column: "Identity",
unique: true)
.Annotation("SqlServer:FilteredIndex", "[End] IS NULL");
And after calling Script-Migration commad in Package Manager Console we will see a resulting SQL as this:
CREATE UNIQUE INDEX [IX_Activities_Identity] ON [Activities] ([Identity]) WHERE [End] IS NULL;
This method can actually be used to include any custom SQL generator into ef core fluent api. At least as long as the EF API remains the same.

Create database with data

I'm trying to create my database (code first) and I want to add some data in it when it's created.
public class InitializerWithData : CreateDatabaseIfNotExists<DatabaseContext>
{
protected override void Seed(DatabaseContext ctx)
{
GroupType gt = new GroupType() { Name = "RNC" };
//save
ctx.GroupType.Add(gt);
ctx.SaveChanges();
}
}
public DatabaseContext()
{
Database.SetInitializer<DatabaseContext>(new InitializerWithData());
Database.CreateIfNotExists();
}
As you can see I wrote my custom initializer but the code inside it is never fired though the database does get created.
So how do I solve this?
When you call Database.CreateIfNotExists(), it doesn't trigger the InitializeDatabase of the initializer. Basically it has separated implementation than the initializer.
If you want the Seed method to be fired. You need to execute a code that causes EF to send a query to the database.
First remove this line.
Database.CreateIfNotExists();
Then just execute a query, the least you could have is something like.
using(var db = new DatabaseContext())
{
db.Set<GroupType>().Any();
}
This code will create the database if it doesn't exist and execute the Seed method.

EF 4.1 - How to add a default on insertion for datetime column

Using EF 4.1 how could I add a default value to the underlying table? In this particular case how could I set a datetime column to the equivalent of getdate every time I insert a new record to the database, without having to set it in code.
Thanks in advance
The solution proposed by #elkdanger is way to go but just if you use code-first approach you don't have to create partial class - you can place initialization directly to your entity.
Don't use database approach! It will not work because it would demand marking property as database generated (to be correctly repopulated after insert). Once you mark property database generated you can never change its value in the application.
The last option is overriding SaveChanges in your derived DbContext and setting the property manually. Something like:
public override int SaveChanges()
{
var entities = ChangeTracker.Entries<YourEntityType>()
.Where(e => e.State == EntityState.Added)
.Select(e => e.Entity);
var currentDate = DateTime.Now;
foreach(var entity in entities)
{
entity.Date = currentDate;
}
return base.SaveChanges();
}
This approach can be better if there can be significant difference between creating an instance of the entity and saving the instanance.
You could create a partial class for your entity, and inside the constructor set the date column to DateTime.Now. This way, every time you create an instance of your class, that field will be set to the current date "automatically".
You could (and perhaps should) do it in the table itself using a trigger or a default value.
Entity Framework itself has not a mechanism for it. You have to do it manually in the db or the code.
You can also modify your T4 template (.tt file) to add a partial method that you call from within the generated constructor. Then, you can create your own partial class and implement the partial method and set your default value.
A snippet from the T4 template where the constructor is created, followed by the partial method. Note the last three lines:
public <#=code.Escape(entity)#>()
{
<#
foreach (var edmProperty in propertiesWithDefaultValues)
{
#>
this.<#=code.Escape(edmProperty)#> = =code.CreateLiteral(edmProperty.DefaultValue)#>;
<#
}
foreach (var navigationProperty in collectionNavigationProperties)
{
#>
this.<#=code.Escape(navigationProperty)#> = new HashSet<<#=code.Escape(navigationProperty.ToEndMember.GetEntityType())#>>();
<#
}
foreach (var complexProperty in complexProperties)
{
#>
this.<#=code.Escape(complexProperty)#> = new <#=code.Escape(complexProperty.TypeUsage)#>();
<#
}
#>
SetDefaultValues();
}
partial void SetDefaultValues();
That will result in a generated entity having something like:
public Foo()
{
// Properties set based on defaults in edmx
SetDefaultValues();
}
partial void SetDefaultValues();
Then, in your partial class, you can simply add something like:
partial void SetDefaultValues()
{
this.SomeDate = DateTime.Today;
}
Use [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
from System.ComponentModel.DataAnnotations.Schema;
if you have the default values configured on the database.

Add index with entity framework code first (CTP5)

Is there a way to get EF CTP5 to create an index when it creates a schema?
Update: See here for how EF 6.1 handles this (as pointed out by juFo below).
You can take advantage of the new CTP5’s ExecuteSqlCommand method on Database class which allows raw SQL commands to be executed against the database.
The best place to invoke SqlCommand method for this purpose is inside a Seed method that has been overridden in a custom Initializer class. For example:
protected override void Seed(EntityMappingContext context)
{
context.Database.ExecuteSqlCommand("CREATE INDEX IX_NAME ON ...");
}
As some mentioned in the comments to Mortezas answer there is a CreateIndex/DropIndex method if you use migrations.
But if you are in "debug"/development mode and is changing the schema all the time and are recreating the database every time you can use the example mentioned in Morteza answer.
To make it a little easier, I have written a very simple extension method to make it strongly typed, as inspiration that I want to share with anyone who reads this question and maybe would like this approach aswell. Just change it to fit your needs and way of naming indexes.
You use it like this: context.Database.CreateUniqueIndex<User>(x => x.Name);
.
public static void CreateUniqueIndex<TModel>(this Database database, Expression<Func<TModel, object>> expression)
{
if (database == null)
throw new ArgumentNullException("database");
// Assumes singular table name matching the name of the Model type
var tableName = typeof(TModel).Name;
var columnName = GetLambdaExpressionName(expression.Body);
var indexName = string.Format("IX_{0}_{1}", tableName, columnName);
var createIndexSql = string.Format("CREATE UNIQUE INDEX {0} ON {1} ({2})", indexName, tableName, columnName);
database.ExecuteSqlCommand(createIndexSql);
}
public static string GetLambdaExpressionName(Expression expression)
{
MemberExpression memberExp = expression as MemberExpression;
if (memberExp == null)
{
// Check if it is an UnaryExpression and unwrap it
var unaryExp = expression as UnaryExpression;
if (unaryExp != null)
memberExp = unaryExp.Operand as MemberExpression;
}
if (memberExp == null)
throw new ArgumentException("Cannot get name from expression", "expression");
return memberExp.Member.Name;
}
Update: From version 6.1 and onwards there is an [Index] attribute available.
For more info, see http://msdn.microsoft.com/en-US/data/jj591583#Index
This feature should be available in the near-future via data annotations and the Fluent API. Microsoft have added it into their public backlog:
http://entityframework.codeplex.com/workitem/list/basic?keywords=DevDiv [Id=87553]
Until then, you'll need to use a seed method on a custom Initializer class to execute the SQL to create the unique index, and if you're using code-first migrations, create a new migration for adding the unique index, and use the CreateIndex and DropIndex methods in your Up and Down methods for the migration to create and drop the index.
Check my answer here Entity Framework Code First Fluent Api: Adding Indexes to columns this allows you to define multi column indexes by using attributes on properties.