Entity Framework Filter Index - entity-framework

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.

Related

EF Core Add function returns negative id

I noticed a weird thing today when I tried to save an entity and return its id in EF Core
Before:
After:
I was thinking about if it was before calling saveChanges() but it works with another entity with a similar setup.
ps: I use unit of work to save all changes at the end.
What was the reason?
It will be negative until you save your changes. Just call Save on the context.
_dbContext.Locations.Add(location);
_dbContext.Save();
After the save, you will have the ID which is in the database. You can use transactions, which you can roll back in case there's a problem after you get the ID.
The other way would be not to use the database's built-in IDENTITY fields, but rather implement them yourself. This can be very useful when you have a lot of bulk insert operations, but it comes with a price — it's not easy to implement.
Apparently, this isn't a bug it's a feature: https://github.com/aspnet/EntityFrameworkCore/issues/6147
It's not too arduous to override this behavior like so:
public class IntValueGenerator : TemporaryNumberValueGenerator<int>
{
private int _current = 0;
public override int Next(EntityEntry entry)
{
return Interlocked.Increment(ref _current);
}
}
Then reference the custom value generator here:
public class CustomContext: DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var type in modelBuilder.Model.GetEntityTypes().Select(c => c.ClrType))
{
modelBuilder.Entity(type, b =>
{
b.Property("Id").HasValueGenerator<IntValueGenerator>();
});
}
base.OnModelCreating(modelBuilder);
}
}
This will result in temporary Id's that increment from "1", however, more effort is required to prevent key conflicts when attempting to save.

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!

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.

Entity Framework 4 , Custom Properties . Add some traitement

I have a class Address Generated by entity Framework.
I Have an propertie AddressID in this class.
I Would like to be able to add some treatement for this prop in the set process.
EX :
public partial class Address
{
public bool _AddressID;
public bool AddressID{get return AddressID;}
set{
if(value == -1) _AddressID = null;
}
}
Thanks
Of course you can't redefine your AddressID in order to put your custom logic in its setter, as you'll get compiler error:
The type Address already contains a definition for 'AddressID'
But no worries, if you take a look at the EF generated code for your EntityObject (let's assume its name is Address) you'll see that every scalar property of generated Address class has its own version of OnPropertyChanging and OnPropertyChanged method. For example, OnAddressIDChanging and OnAddressIDChanged in this case.
As you can see below, there is no default implementation for these two methods, only a declaration. This perfectly provides you the opportunity to execute custom logic
as the property is about to change (PropertyChanging) as well as just after the property
value has changed (PropertyChanged).
// From the designer code for Address class:
partial void OnAddressIDChanging(global::System.Int32 value);
partial void OnAddressIDChanged();
This is how your Entity Model designer code already is look like (hypotetically):
public global::System.Int32 AddressID {
get {
return _AddressID;
}
set {
if (_AddressID != value) {
// OnPropertyChanging method get called here:
OnAddressIDChanging(value);
ReportPropertyChanging("AddressID");
_AddressID = StructuralObject.SetValidValue(value);
ReportPropertyChanged("AddressID");
// OnPropertyChanged get called here:
OnAddressIDChanged();
}
}
}
So all you need to do in order to hook up your custom code is:
public partial class Address {
partial void OnAddressIDChanged() {
if(AddressID == -1) {
AddressID = 0;
}
}
}
By the way, about other posted answers - with all due respect to them - if you want this solution for a production application then you cannot use "Code First" since it merely is a CTP as for now and will be part of the next release for EF, so it cannot be an option.
About customizing default code generation, while this is indeed possible since in VS 2010, Entity Framework itself also uses T4 for designer code generation and we can take advantage of it by changing the T4, But it is an option only if you want to fundamentally change how the entity classes are generated in general and you cannot use it for customizing a setter logic for a specific entity.
Code First in EF4 is an option - it allows you to fully control all of the code. However, another option is to customize the EF4 T4 templates that ship with EF4. If you have certain patterns in your code that you consistently use, this would be a good approach. You can read more about how to customize the templates here: Customizing Entity Classes in VS2010