Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for 20014. KeyAttribute does not override Keyless. Log warning. #20050

Merged
merged 3 commits into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/EFCore/Diagnostics/CoreEventId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ private enum Id
ServiceProviderDebugInfo,
RedundantAddServicesCallWarning,

// Model events
// Model and ModelValidation events
ShadowPropertyCreated = CoreBaseId + 600,
RedundantIndexRemoved,
IncompatibleMatchingForeignKeyProperties,
Expand All @@ -102,6 +102,7 @@ private enum Id
RequiredAttributeInverted,
RequiredAttributeOnCollection,
CollectionWithoutComparer,
ConflictingKeylessAndKeyAttributesWarning,

// ChangeTracking events
DetectChangesStarting = CoreBaseId + 800,
Expand Down Expand Up @@ -615,6 +616,21 @@ public static readonly EventId PossibleUnintendedReferenceComparisonWarning
/// </summary>
public static readonly EventId RedundantForeignKeyWarning = MakeModelValidationId(Id.RedundantForeignKeyWarning);

/// <summary>
/// <para>
/// A <see cref="KeylessAttribute"/> attribute on the entity type is conflicting
/// with a <see cref="KeyAttribute"/> attribute on at least one of its properties.
/// </para>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Model" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="PropertyEventData" /> payload when used with a
/// <see cref="DiagnosticSource" />.
/// </para>
/// </summary>
public static readonly EventId ConflictingKeylessAndKeyAttributesWarning = MakeModelId(Id.ConflictingKeylessAndKeyAttributesWarning);

private static readonly string _changeTrackingPrefix = DbLoggerCategory.ChangeTracking.Name + ".";
private static EventId MakeChangeTrackingId(Id id) => new EventId((int)id, _changeTrackingPrefix + id);

Expand Down
36 changes: 36 additions & 0 deletions src/EFCore/Diagnostics/CoreLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2912,5 +2912,41 @@ private static string ContextDisposed(EventDefinitionBase definition, EventData
var p = (DbContextEventData)payload;
return d.GenerateMessage(p.Context.GetType().ShortDisplayName());
}

/// <summary>
/// Logs for the <see cref="CoreEventId.ConflictingKeylessAndKeyAttributesWarning" /> event.
/// </summary>
/// <param name="diagnostics"> The diagnostics logger to use. </param>
/// <param name="property"> The property which is being defined as part of a key. </param>
public static void ConflictingKeylessAndKeyAttributesWarning(
[NotNull] this IDiagnosticsLogger<DbLoggerCategory.Model> diagnostics,
[NotNull] IProperty property)
{
var definition = CoreResources.LogConflictingKeylessAndKeyAttributes(diagnostics);

if (diagnostics.ShouldLog(definition))
{
definition.Log(diagnostics, property.Name, property.DeclaringEntityType.DisplayName());
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new PropertyEventData(
definition,
ConflictingKeylessAndKeyAttributesWarning,
property);

diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
}
}

private static string ConflictingKeylessAndKeyAttributesWarning(EventDefinitionBase definition, EventData payload)
{
var d = (EventDefinition<string, string>)definition;
var p = (PropertyEventData)payload;
return d.GenerateMessage(
p.Property.Name,
p.Property.DeclaringEntityType.DisplayName());
}
}
}
9 changes: 9 additions & 0 deletions src/EFCore/Diagnostics/LoggingDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -636,5 +636,14 @@ public abstract class LoggingDefinitions
/// </summary>
[EntityFrameworkInternal]
public EventDefinitionBase LogRedundantForeignKey;

/// <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
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
public EventDefinitionBase LogConflictingKeylessAndKeyAttributes;
}
}
15 changes: 15 additions & 0 deletions src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@ protected override void ProcessPropertyAdded(
IConventionContext context)
{
var entityType = propertyBuilder.Metadata.DeclaringEntityType;
if (entityType.IsKeyless)
{
switch (entityType.GetIsKeylessConfigurationSource())
{
case ConfigurationSource.DataAnnotation:
Dependencies.Logger
.ConflictingKeylessAndKeyAttributesWarning(propertyBuilder.Metadata);
return;

case ConfigurationSource.Explicit:
// fluent API overrides the attribute - no warning
return;
}
}

if (entityType.BaseType != null)
{
return;
Expand Down
6 changes: 6 additions & 0 deletions src/EFCore/Metadata/IConventionEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ public interface IConventionEntityType : IEntityType, IConventionTypeBase
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
void HasNoKey(bool? keyless, bool fromDataAnnotation = false);

/// <summary>
/// Returns the configuration source for the IsKeyless property.
/// </summary>
/// <returns> The configuration source for the IsKeyless property. </returns>
ConfigurationSource? GetIsKeylessConfigurationSource();

/// <summary>
/// Sets the primary key for this entity type.
/// </summary>
Expand Down
26 changes: 25 additions & 1 deletion src/EFCore/Properties/CoreStrings.Designer.cs

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

4 changes: 4 additions & 0 deletions src/EFCore/Properties/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1269,4 +1269,8 @@
<data name="LastUsedWithoutOrderBy" xml:space="preserve">
<value>Queries performing '{method}' operation must have a deterministic sort order. Rewrite the query to apply an OrderBy clause on the sequence before calling '{method}'.</value>
</data>
<data name="LogConflictingKeylessAndKeyAttributes" xml:space="preserve">
<value>Conflicting attributes have been applied: the 'Key' attribute on property '{property}' and the 'Keyless' attribute on its entity '{entity}'. Note that the entity will have no key unless you use fluent API to override this.</value>
<comment>Warning CoreEventId.ConflictingKeylessAndKeyAttributesWarning string string</comment>
</data>
</root>
68 changes: 67 additions & 1 deletion test/EFCore.Specification.Tests/DataAnnotationTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,49 @@ public virtual ModelBuilder Key_specified_on_multiple_properties_can_be_overridd
return modelBuilder;
}

[ConditionalFact]
public virtual void Keyless_and_key_attributes_which_conflict_cause_warning()
{
var modelBuilder = CreateModelBuilder();
var entity = modelBuilder.Entity<KeylessAndKeyAttributes>();

Assert.True(entity.Metadata.IsKeyless);
Assert.Null(entity.Metadata.FindPrimaryKey());

var logEntry = Fixture.ListLoggerFactory.Log.Single();
Assert.Equal(LogLevel.Warning, logEntry.Level);
Assert.Equal(
CoreResources.LogConflictingKeylessAndKeyAttributes(new TestLogger<TestLoggingDefinitions>())
.GenerateMessage("NotAKey", nameof(KeylessAndKeyAttributes)),
logEntry.Message);
}

[ConditionalFact]
public virtual void Keyless_fluent_api_and_key_attribute_do_not_cause_warning()
{
var modelBuilder = CreateModelBuilder();
var entity = modelBuilder.Entity<KeylessFluentApiAndKeyAttribute>();
entity.HasNoKey();

Assert.True(entity.Metadata.IsKeyless);
Assert.Null(entity.Metadata.FindPrimaryKey());

Assert.Empty(Fixture.ListLoggerFactory.Log);
}

[ConditionalFact]
public virtual void Key_fluent_api_and_keyless_attribute_do_not_cause_warning()
{
var modelBuilder = CreateModelBuilder();
var entity = modelBuilder.Entity<KeyFluentApiAndKeylessAttribute>();
entity.HasKey("MyKey");

Assert.False(entity.Metadata.IsKeyless);
Assert.NotNull(entity.Metadata.FindPrimaryKey());

Assert.Empty(Fixture.ListLoggerFactory.Log);
}

private class CompositeKeyAttribute
{
[Key]
Expand Down Expand Up @@ -2319,14 +2362,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
modelBuilder.Ignore<BookLabel>();
modelBuilder.Entity<BookDetails>();
modelBuilder.Entity<Book>().Property(d => d.Id).ValueGeneratedNever();
modelBuilder.Entity<KeylessAndKeyAttributes>();
modelBuilder.Entity<KeylessFluentApiAndKeyAttribute>();
modelBuilder.Entity<KeyFluentApiAndKeylessAttribute>();
}

public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
=> base.AddOptions(builder).ConfigureWarnings(
c => c
.Log(CoreEventId.ConflictingForeignKeyAttributesOnNavigationAndPropertyWarning)
.Log(CoreEventId.ForeignKeyAttributesOnBothNavigationsWarning)
.Log(CoreEventId.ForeignKeyAttributesOnBothPropertiesWarning));
.Log(CoreEventId.ForeignKeyAttributesOnBothPropertiesWarning)
.Log(CoreEventId.ConflictingKeylessAndKeyAttributesWarning));

protected override bool ShouldLogCategory(string logCategory)
=> logCategory == DbLoggerCategory.Model.Name;
Expand Down Expand Up @@ -2404,5 +2451,24 @@ protected class Details
{
public string Name { get; set; }
}

[Keyless]
protected class KeylessAndKeyAttributes
{
[Key]
public int NotAKey { get; set; }
}

protected class KeylessFluentApiAndKeyAttribute
{
[Key]
public int NotAKey { get; set; }
}

[Keyless]
protected class KeyFluentApiAndKeylessAttribute
{
public int MyKey { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,14 @@ public void KeylessAttribute_can_be_overriden_using_explicit_configuration()
}

[ConditionalFact]
public void KeyAttribute_overrides_keyless_attribute()
public void KeyAttribute_does_not_override_keyless_attribute()
{
var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder();

var entityBuilder = modelBuilder.Entity<KeyClash>();

Assert.False(entityBuilder.Metadata.IsKeyless);
Assert.NotNull(entityBuilder.Metadata.FindPrimaryKey());
Assert.True(entityBuilder.Metadata.IsKeyless);
Assert.Null(entityBuilder.Metadata.FindPrimaryKey());
}

#endregion
Expand Down