Skip to content

Commit

Permalink
feat(Migrations): SQL Server Filterable indexes.
Browse files Browse the repository at this point in the history
  • Loading branch information
laskoviymishka committed Oct 27, 2016
1 parent e0d25af commit 548c9a9
Show file tree
Hide file tree
Showing 23 changed files with 257 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,14 @@ protected virtual void Generate([NotNull] CreateIndexOperation operation, [NotNu
.Append("unique: true");
}

if (operation.Filter != null)
{
builder
.AppendLine(",")
.Append("filter: ")
.Append(operation.Filter);
}

builder.Append(")");

Annotations(operation.GetAnnotations(), builder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ protected virtual void GenerateIndex(

stringBuilder
.AppendLine()
.Append("b.HasIndex(")
.Append($"b.{nameof(EntityTypeBuilder.HasIndex)}(")
.Append(string.Join(", ", index.Properties.Select(p => _code.Literal(p.Name))))
.Append(")");

Expand All @@ -341,7 +341,14 @@ protected virtual void GenerateIndex(
{
stringBuilder
.AppendLine()
.Append(".IsUnique()");
.Append($".{nameof(IndexBuilder.IsUnique)}()");
}

if (index.Filter != null)
{
stringBuilder
.AppendLine()
.Append($".{nameof(IndexBuilder.HasFilter)}({index.Filter})");
}

var annotations = index.GetAnnotations().ToList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ public virtual ICollection<string> FluentApiLines
lines.Add("." + nameof(IndexBuilder.IsUnique) + "()");
}

if (Index.Filter != null)
{
lines.Add("." + nameof(IndexBuilder.HasFilter) + "(\"" + Index.Filter + "\")");
}

return lines;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ public class IndexModel : Annotatable
public virtual string Name { get; [param: NotNull] set; }
public virtual ICollection<IndexColumnModel> IndexColumns { get; [param: NotNull] set; } = new List<IndexColumnModel>();
public virtual bool IsUnique { get; [param: NotNull] set; }
public virtual string WhereClauses { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ protected virtual IndexBuilder VisitIndex([NotNull] EntityTypeBuilder builder, [
var primaryKeyColumns = index.Table.Columns
.Where(c => c.PrimaryKeyOrdinal.HasValue)
.OrderBy(c => c.PrimaryKeyOrdinal);
if (columnNames.SequenceEqual(primaryKeyColumns.Select(c => c.Name)))
if (columnNames.SequenceEqual(primaryKeyColumns.Select(c => c.Name)) && index.WhereClauses == null)
{
// index is supporting the primary key. So there is no need for
// an extra index in the model. But if the index name does not
Expand All @@ -445,6 +445,11 @@ protected virtual IndexBuilder VisitIndex([NotNull] EntityTypeBuilder builder, [
var indexBuilder = builder.HasIndex(propertyNames)
.IsUnique(index.IsUnique);

if (index.WhereClauses != null)
{
indexBuilder.HasFilter(index.WhereClauses);
}

if (!string.IsNullOrEmpty(index.Name))
{
indexBuilder.HasName(index.Name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -973,10 +973,12 @@ protected virtual IEnumerable<MigrationOperation> Diff(
Annotations.For(t).Name,
StringComparison.OrdinalIgnoreCase)
&& s.IsUnique == t.IsUnique
&& s.Filter == t.Filter
&& !HasDifferences(MigrationsAnnotations.For(s), MigrationsAnnotations.For(t))
&& s.Properties.Select(diffContext.FindTarget).SequenceEqual(t.Properties),
// ReSharper disable once ImplicitlyCapturedClosure
(s, t) => s.IsUnique == t.IsUnique
&& s.Filter == t.Filter
&& !HasDifferences(MigrationsAnnotations.For(s), MigrationsAnnotations.For(t))
&& s.Properties.Select(diffContext.FindTarget).SequenceEqual(t.Properties));

Expand Down Expand Up @@ -1022,7 +1024,8 @@ protected virtual IEnumerable<MigrationOperation> Add(
Schema = targetEntityTypeAnnotations.Schema,
Table = targetEntityTypeAnnotations.TableName,
Columns = GetColumns(target.Properties),
IsUnique = target.IsUnique
IsUnique = target.IsUnique,
Filter = target.Filter
};
operation.AddAnnotations(MigrationsAnnotations.For(target));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,20 +300,23 @@ public virtual OperationBuilder<CreateIndexOperation> CreateIndex(
[NotNull] string table,
[NotNull] string column,
[CanBeNull] string schema = null,
bool unique = false)
bool unique = false,
string filter = null)
=> CreateIndex(
name,
table,
new[] { column },
schema,
unique);
unique,
filter);

public virtual OperationBuilder<CreateIndexOperation> CreateIndex(
[NotNull] string name,
[NotNull] string table,
[NotNull] string[] columns,
[CanBeNull] string schema = null,
bool unique = false)
bool unique = false,
string filter = null)
{
Check.NotEmpty(name, nameof(name));
Check.NotEmpty(table, nameof(table));
Expand All @@ -325,7 +328,8 @@ public virtual OperationBuilder<CreateIndexOperation> CreateIndex(
Table = table,
Name = name,
Columns = columns,
IsUnique = unique
IsUnique = unique,
Filter = filter
};
Operations.Add(operation);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,13 @@ protected virtual void Generate(
.Append(ColumnList(operation.Columns))
.Append(")");

if (operation.Filter != null)
{
builder
.Append(" WHERE ")
.Append(operation.Filter);
}

if (terminate)
{
builder.AppendLine(SqlGenerationHelper.StatementTerminator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public class CreateIndexOperation : MigrationOperation
public virtual string Schema { get; [param: CanBeNull] set; }
public virtual string Table { get; [param: NotNull] set; }
public virtual string[] Columns { get; [param: NotNull] set; }
public virtual string Filter { get; [param: NotNull] set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,9 @@ private void GetIndexes()
i.is_unique,
c.name AS [column_name],
i.type_desc,
ic.key_ordinal
ic.key_ordinal,
i.has_filter,
i.filter_definition
FROM sys.indexes i
INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
INNER JOIN sys.columns c ON ic.object_id = c.object_id AND c.column_id = ic.column_id
Expand All @@ -471,6 +473,8 @@ AND object_name(i.object_id) <> '" + HistoryRepository.DefaultTableName + @"'" +
var typeDesc = reader.GetValueOrDefault<string>("type_desc");
var columnName = reader.GetValueOrDefault<string>("column_name");
var indexOrdinal = reader.GetValueOrDefault<byte>("key_ordinal");
var hasFilter = reader.GetValueOrDefault<bool>("has_filter");
var filterDefinition = reader.GetValueOrDefault<string>("filter_definition");

Logger.LogDebug(
RelationalDesignEventId.FoundIndexColumn,
Expand Down Expand Up @@ -513,7 +517,8 @@ AND object_name(i.object_id) <> '" + HistoryRepository.DefaultTableName + @"'" +
{
Table = table,
Name = indexName,
IsUnique = isUnique
IsUnique = isUnique,
WhereClauses = hasFilter ? filterDefinition : null
};
index.SqlServer().IsClustered = typeDesc == "CLUSTERED";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1035,7 +1035,8 @@ protected virtual void CreateIndexes(
Name = Annotations.For(index).Name,
Schema = Annotations.For(index.DeclaringEntityType).Schema,
Table = Annotations.For(index.DeclaringEntityType).TableName,
Columns = index.Properties.Select(p => Annotations.For(p).ColumnName).ToArray()
Columns = index.Properties.Select(p => Annotations.For(p).ColumnName).ToArray(),
Filter = index.Filter
};
operation.AddAnnotations(_migrationsAnnotations.For(index));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ public virtual IndexBuilder HasAnnotation([NotNull] string annotation, [NotNull]
return this;
}

/// <summary>
/// Determines whether [has where clauses] [the specified where clauses].
/// </summary>
/// <param name="filterExpression">The where clauses.</param>
/// <returns></returns>
public virtual IndexBuilder HasFilter([NotNull] string filterExpression)
{
Check.NotEmpty(filterExpression, nameof(filterExpression));

Metadata.Filter = filterExpression;

return this;
}

/// <summary>
/// Configures whether this index is unique (i.e. the value(s) for each instance must be unique).
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.EntityFrameworkCore/Metadata/IIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public interface IIndex : IAnnotatable
/// </summary>
bool IsUnique { get; }

/// <summary>
/// Gets a value indicating whre clauses to specific index.
/// </summary>
string Filter { get; }

/// <summary>
/// Gets the entity type the index is defined on. This may be different from the type that <see cref="Properties" />
/// are defined on when the index is defined a derived type in an inheritance hierarchy (since the properties
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.EntityFrameworkCore/Metadata/IMutableIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ namespace Microsoft.EntityFrameworkCore.Metadata
/// </summary>
public interface IMutableIndex : IIndex, IMutableAnnotatable
{
/// <summary>
/// Gets the properties that this index is defined on.
/// </summary>
new string Filter { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the values assigned to the indexed properties are unique.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.EntityFrameworkCore/Metadata/Internal/Index.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public Index(
/// </summary>
public virtual IReadOnlyList<Property> Properties { get; }

/// <summary>
/// Gets the properties that this index is defined on.
/// </summary>
public string Filter { get; set; }

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,15 @@ public override void CreateIndexOperation_nonunique()
Sql);
}

public override void CreateIndexOperation_with_whrere_clauses()
{
base.CreateIndexOperation_with_whrere_clauses();

Assert.Equal(
"CREATE INDEX \"IX_People_Name\" ON \"People\" (\"Name\") WHERE [Id] > 2;" + EOL,
Sql);
}

public override void CreateSequenceOperation_with_minValue_and_maxValue()
{
base.CreateSequenceOperation_with_minValue_and_maxValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,18 @@ public virtual void CreateIndexOperation_nonunique()
IsUnique = false
});

[Fact]
public virtual void CreateIndexOperation_with_whrere_clauses()
=> Generate(
new CreateIndexOperation
{
Name = "IX_People_Name",
Table = "People",
Columns = new[] { "Name" },
IsUnique = false,
Filter = "[Id] > 2"
});

[Fact]
public virtual void CreateSequenceOperation_with_minValue_and_maxValue()
=> Generate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@
<None Include="ReverseEngineering\Expected\Attributes\UnmappablePKColumn.expected">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="ReverseEngineering\Expected\FilteredIndexContext.expected">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="ReverseEngineering\Expected\FilteredIndex.expected">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="ReverseEngineering\Expected\PrimaryKeyWithSequence.expected">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;

namespace E2ETest.Namespace
{
public partial class FilteredIndex
{
public int Id { get; set; }
public int? Number { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace E2ETest.Namespace
{
public partial class FilteredIndexContext : DbContext
{
public virtual DbSet<FilteredIndex> FilteredIndex { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings.
optionsBuilder.UseSqlServer(@"{{connectionString}}");
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<FilteredIndex>(entity =>
{
entity.HasIndex(e => e.Number)
.HasName("Unicorn_Filtered_Index")
.HasWhereClauses("([Number]>(10))");

entity.Property(e => e.Id).ValueGeneratedNever();
});
}
}
}
Loading

0 comments on commit 548c9a9

Please sign in to comment.