Skip to content

Commit

Permalink
Fix for 4050. IndexAttribute. (#21012)
Browse files Browse the repository at this point in the history
Fix for 4050. Add a new IndexAttribute.
  • Loading branch information
lajones committed May 28, 2020
1 parent d1c39bc commit 0162568
Show file tree
Hide file tree
Showing 60 changed files with 2,392 additions and 72 deletions.
58 changes: 58 additions & 0 deletions src/EFCore.Abstractions/IndexAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// Specifies an index to be generated in the database.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class IndexAttribute : Attribute
{
private static readonly bool DefaultIsUnique = false;
private bool? _isUnique;

/// <summary>
/// Initializes a new instance of the <see cref="IndexAttribute" /> class.
/// </summary>
/// <param name="propertyNames"> The properties which constitute the index, in order (there must be at least one). </param>
public IndexAttribute(params string[] propertyNames)
{
Check.NotEmpty(propertyNames, nameof(propertyNames));
Check.HasNoEmptyElements(propertyNames, nameof(propertyNames));
PropertyNames = propertyNames.ToList();
}

/// <summary>
/// The properties which constitute the index, in order.
/// </summary>
public List<string> PropertyNames { get; }

/// <summary>
/// The name of the index.
/// </summary>
public string Name { get; [param: NotNull] set; }


/// <summary>
/// Whether the index is unique.
/// </summary>
public bool IsUnique
{
get => _isUnique ?? DefaultIsUnique;
set => _isUnique = value;
}

/// <summary>
/// Use this method if you want to know the uniqueness of
/// the index or <see langword="null"/> if it was not specified.
/// </summary>
public bool? GetIsUnique() => _isUnique;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Abstractions/Properties/AbstractionsStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,7 @@
<data name="CollectionArgumentIsEmpty" xml:space="preserve">
<value>The collection argument '{argumentName}' must contain at least one element.</value>
</data>
<data name="CollectionArgumentHasEmptyElements" xml:space="preserve">
<value>The collection argument '{argumentName}' must not contain any empty elements.</value>
</data>
</root>
37 changes: 34 additions & 3 deletions src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Scaffolding.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.Utilities;
Expand Down Expand Up @@ -743,6 +744,8 @@ protected virtual void GenerateIndex(
Check.NotNull(index, nameof(index));
Check.NotNull(stringBuilder, nameof(stringBuilder));

// Note - method names below are meant to be hard-coded
// because old snapshot files will fail if they are changed
stringBuilder
.AppendLine()
.Append(builderName)
Expand All @@ -752,6 +755,15 @@ protected virtual void GenerateIndex(

using (stringBuilder.Indent())
{
if (index.Name != null)
{
stringBuilder
.AppendLine()
.Append(".HasName(")
.Append(Code.Literal(index.Name))
.Append(")");
}

if (index.IsUnique)
{
stringBuilder
Expand All @@ -777,10 +789,8 @@ protected virtual void GenerateIndexAnnotations(

IgnoreAnnotations(
annotations,
RelationalAnnotationNames.TableIndexMappings);
CSharpModelGenerator.IgnoredIndexAnnotations);

GenerateFluentApiForAnnotation(
ref annotations, RelationalAnnotationNames.Name, nameof(RelationalIndexBuilderExtensions.HasName), stringBuilder);
GenerateFluentApiForAnnotation(
ref annotations, RelationalAnnotationNames.Filter, nameof(RelationalIndexBuilderExtensions.HasFilter), stringBuilder);

Expand Down Expand Up @@ -1213,6 +1223,27 @@ protected virtual void IgnoreAnnotations(
}
}

/// <summary>
/// Removes ignored annotations.
/// </summary>
/// <param name="annotations"> The annotations to remove from. </param>
/// <param name="annotationNames"> The ignored annotation names. </param>
protected virtual void IgnoreAnnotations(
[NotNull] IList<IAnnotation> annotations, [NotNull] IReadOnlyList<string> annotationNames)
{
Check.NotNull(annotations, nameof(annotations));
Check.NotNull(annotationNames, nameof(annotationNames));

foreach (var annotationName in annotationNames)
{
var annotation = annotations.FirstOrDefault(a => a.Name == annotationName);
if (annotation != null)
{
annotations.Remove(annotation);
}
}
}

/// <summary>
/// Removes ignored annotations.
/// </summary>
Expand Down
22 changes: 16 additions & 6 deletions src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,15 @@ private void GenerateEntityType(IEntityType entityType, bool useDataAnnotations)

foreach (var index in entityType.GetIndexes())
{
GenerateIndex(index);
// If there are annotations that cannot be represented
// using an IndexAttribute then use fluent API even
// if useDataAnnotations is true.
if (!useDataAnnotations
|| index.GetAnnotations().Any(
a => !CSharpModelGenerator.IgnoredIndexAnnotations.Contains(a.Name)))
{
GenerateIndex(index);
}
}

foreach (var property in entityType.GetProperties())
Expand Down Expand Up @@ -582,14 +590,16 @@ private void GenerateIndex(IIndex index)

var annotations = index.GetAnnotations().ToList();

RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableIndexMappings);
foreach (var annotation in CSharpModelGenerator.IgnoredIndexAnnotations)
{
RemoveAnnotation(ref annotations, annotation);
}

if (!string.IsNullOrEmpty((string)index[RelationalAnnotationNames.Name]))
if (index.Name != null)
{
lines.Add(
$".{nameof(RelationalIndexBuilderExtensions.HasName)}" +
$"({_code.Literal(index.GetName())})");
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Name);
$".{nameof(IndexBuilder.HasName)}" +
$"({_code.Literal(index.GetDatabaseName())})");
}

if (index.IsUnique)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ protected virtual void GenerateEntityTypeDataAnnotations(

GenerateKeylessAttribute(entityType);
GenerateTableAttribute(entityType);
GenerateIndexAttributes(entityType);
}

private void GenerateKeylessAttribute(IEntityType entityType)
Expand Down Expand Up @@ -170,6 +171,39 @@ private void GenerateTableAttribute(IEntityType entityType)
}
}

private void GenerateIndexAttributes(IEntityType entityType)
{
// Do not generate IndexAttributes for indexes which
// would be generated anyway by convention.
foreach (var index in entityType.GetIndexes().Where(i =>
ConfigurationSource.Convention != ((IConventionIndex)i).GetConfigurationSource()))
{
// If there are annotations that cannot be represented
// using an IndexAttribute then use fluent API instead.
if (!index.GetAnnotations().Any(
a => !CSharpModelGenerator.IgnoredIndexAnnotations.Contains(a.Name)))
{
var indexAttribute = new AttributeWriter(nameof(IndexAttribute));
foreach (var property in index.Properties)
{
indexAttribute.AddParameter($"nameof({property.Name})");
}

if (index.Name != null)
{
indexAttribute.AddParameter($"{nameof(IndexAttribute.Name)} = {_code.Literal(index.Name)}");
}

if (index.IsUnique)
{
indexAttribute.AddParameter($"{nameof(IndexAttribute.IsUnique)} = {_code.Literal(index.IsUnique)}");
}

_sb.AppendLine(indexAttribute.ToString());
}
}
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.IO;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
Expand Down Expand Up @@ -128,5 +129,11 @@ public override ScaffoldedModel GenerateModel(

return resultingFiles;
}

/// <summary>
/// The set of annotations ignored for the purposes of code generation for indexes.
/// </summary>
public static IReadOnlyList<string> IgnoredIndexAnnotations
=> new List<string> { RelationalAnnotationNames.TableIndexMappings };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ protected virtual IMutableForeignKey VisitForeignKey([NotNull] ModelBuilder mode
_reporter.WriteWarning(
DesignStrings.ForeignKeyPrincipalEndContainsNullableColumns(
foreignKey.DisplayName(),
index.GetName(),
index.GetDatabaseName(),
nullablePrincipalProperties.Select(tuple => tuple.column.DisplayName()).ToList()
.Aggregate((a, b) => a + "," + b)));

Expand Down
54 changes: 54 additions & 0 deletions src/EFCore.Relational/Diagnostics/IndexEventData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Microsoft.EntityFrameworkCore.Diagnostics
{
/// <summary>
/// A <see cref="DiagnosticSource" /> event payload class for
/// the events involving an invalid index.
/// </summary>
public class IndexEventData : EventData
{
/// <summary>
/// Constructs the event payload for events involving an invalid index.
/// </summary>
/// <param name="eventDefinition"> The event definition. </param>
/// <param name="messageGenerator"> A delegate that generates a log message for this event. </param>
/// <param name="entityType"> The entity type on which the index is defined. </param>
/// <param name="indexName"> The name of the index. </param>
/// <param name="indexPropertyNames"> The names of the properties which define the index. </param>
public IndexEventData(
[NotNull] EventDefinitionBase eventDefinition,
[NotNull] Func<EventDefinitionBase, EventData, string> messageGenerator,
[NotNull] IEntityType entityType,
[CanBeNull] string indexName,
[NotNull] List<string> indexPropertyNames)
: base(eventDefinition, messageGenerator)
{
EntityType = entityType;
Name = indexName;
PropertyNames = indexPropertyNames;
}

/// <summary>
/// The entity type on which the index is defined.
/// </summary>
public virtual IEntityType EntityType { get; }

/// <summary>
/// The name of the index.
/// </summary>
public virtual string Name { get; }

/// <summary>
/// The list of properties which define the index.
/// </summary>
public virtual List<string> PropertyNames { get; }
}
}
Loading

0 comments on commit 0162568

Please sign in to comment.