Skip to content

Commit

Permalink
Enable configuring a non-key column as identity.
Browse files Browse the repository at this point in the history
Throw for multiple columns configured as identity.

Fixes #8271
  • Loading branch information
AndriySvyryd committed Jun 9, 2017
1 parent aced607 commit 9dcfdcb
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 19 deletions.
31 changes: 24 additions & 7 deletions src/EFCore.SqlServer/Internal/SqlServerModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
Expand Down Expand Up @@ -57,10 +58,9 @@ protected virtual void ValidateNonKeyValueGeneration([NotNull] IModel model)
{
foreach (var property in model.GetEntityTypes()
.SelectMany(t => t.GetDeclaredProperties())
.Where(
p =>
((SqlServerPropertyAnnotations)p.SqlServer()).GetSqlServerValueGenerationStrategy(fallbackToModel: false) != null
&& !p.IsKey()))
.Where(p =>
((SqlServerPropertyAnnotations)p.SqlServer()).GetSqlServerValueGenerationStrategy(fallbackToModel: false) == SqlServerValueGenerationStrategy.SequenceHiLo
&& !p.IsKey()))
{
throw new InvalidOperationException(
SqlServerStrings.NonKeyValueGeneration(property.Name, property.DeclaringEntityType.DisplayName()));
Expand All @@ -78,13 +78,30 @@ protected override void ValidateSharedTableCompatibility(
{
throw new InvalidOperationException(
SqlServerStrings.IncompatibleTableMemoryOptimizedMismatch(
tableName, newEntityType.DisplayName(), otherMappedType.DisplayName(),
isMemoryOptimized ? newEntityType.DisplayName() : otherMappedType.DisplayName(),
!isMemoryOptimized ? newEntityType.DisplayName() : otherMappedType.DisplayName()));
tableName, newEntityType.DisplayName(), otherMappedType.DisplayName(),
isMemoryOptimized ? newEntityType.DisplayName() : otherMappedType.DisplayName(),
!isMemoryOptimized ? newEntityType.DisplayName() : otherMappedType.DisplayName()));
}
}

base.ValidateSharedTableCompatibility(newEntityType, otherMappedTypes, tableName);
}

protected override void ValidateSharedColumnsCompatibility(IReadOnlyList<IEntityType> mappedTypes, string tableName)
{
base.ValidateSharedColumnsCompatibility(mappedTypes, tableName);

var identityColumns = mappedTypes.SelectMany(et => et.GetDeclaredProperties())
.Where(p => p.SqlServer().ValueGenerationStrategy == SqlServerValueGenerationStrategy.IdentityColumn)
.Distinct((p1, p2) => p1.Name == p2.Name)
.ToList();

if (identityColumns.Count > 1)
{
var sb = new StringBuilder();
sb.AppendJoin(identityColumns.Select(p => "'" + p.DeclaringEntityType.DisplayName() + "." + p.Name + "'"));
throw new InvalidOperationException(SqlServerStrings.MultipleIdentityColumns(sb, tableName));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ public override IEnumerable<IAnnotation> For(IIndex index)
/// </summary>
public override IEnumerable<IAnnotation> For(IProperty property)
{
if (property.SqlServer().ValueGenerationStrategy == SqlServerValueGenerationStrategy.IdentityColumn
&& property.IsKey())
if (property.SqlServer().ValueGenerationStrategy == SqlServerValueGenerationStrategy.IdentityColumn)
{
yield return new Annotation(
SqlServerAnnotationNames.ValueGenerationStrategy,
Expand Down
10 changes: 9 additions & 1 deletion src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs

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

5 changes: 4 additions & 1 deletion src/EFCore.SqlServer/Properties/SqlServerStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@
<comment>Warning SqlServerEventId.ByteIdentityColumnWarning string string</comment>
</data>
<data name="NonKeyValueGeneration" xml:space="preserve">
<value>The property '{property}' on entity type '{entityType}' is configured to use 'Identity' or 'SequenceHiLo' value generator, which are only intended for keys. If this was intentional configure an alternate key on the property, otherwise call `ValueGeneratedNever` or configure store generation for this property.</value>
<value>The property '{property}' on entity type '{entityType}' is configured to use 'SequenceHiLo' value generator, which is only intended for keys. If this was intentional configure an alternate key on the property, otherwise call 'ValueGeneratedNever' or configure store generation for this property.</value>
</data>
<data name="MultipleIdentityColumns" xml:space="preserve">
<value>The properties {properties} are configured to use 'Identity' value generator and are mapped to the same table '{table}'. Only one column per table can be configured as 'Identity'. Call 'ValueGeneratedNever' for properties that should not use 'Identity'.</value>
</data>
<data name="IncompatibleTableMemoryOptimizedMismatch" xml:space="preserve">
<value>Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and entity type '{memoryOptimizedEntityType}' is marked as memory-optimized, but entity type '{nonMemoryOptimizedEntityType}' is not.</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ public virtual ResultSetMapping AppendBulkInsertOperation(

var defaultValuesOnly = writeOperations.Count == 0;
var nonIdentityOperations = modificationCommands[0].ColumnModifications
.Where(o => o.Property.SqlServer().ValueGenerationStrategy != SqlServerValueGenerationStrategy.IdentityColumn
|| !o.Property.IsKey())
.Where(o => o.Property.SqlServer().ValueGenerationStrategy != SqlServerValueGenerationStrategy.IdentityColumn)
.ToList();

if (defaultValuesOnly)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// 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.Linq;

namespace Microsoft.EntityFrameworkCore
{
public class GraphUpdatesWithIdentitySqlServerTest : GraphUpdatesSqlServerTestBase<GraphUpdatesWithIdentitySqlServerTest.GraphUpdatesWithIdentitySqlServerFixture>
Expand Down
34 changes: 34 additions & 0 deletions test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,40 @@ public void Alter_column_identity()
});
}

[Fact]
public void Alter_column_non_key_identity()
{
Execute(
source => source.Entity(
"Lamb",
x =>
{
x.ToTable("Lamb", "bah");
x.Property<int>("Num").ValueGeneratedNever();
x.Property<int>("Id");
x.HasKey("Id");
}),
target => target.Entity(
"Lamb",
x =>
{
x.ToTable("Lamb", "bah");
x.Property<int>("Num").ValueGeneratedOnAdd();
x.Property<int>("Id");
x.HasKey("Id");
}),
operations =>
{
Assert.Equal(1, operations.Count);
var operation = Assert.IsType<AlterColumnOperation>(operations[0]);
Assert.Equal("bah", operation.Schema);
Assert.Equal("Lamb", operation.Table);
Assert.Equal("Num", operation.Name);
Assert.Equal(SqlServerValueGenerationStrategy.IdentityColumn, operation["SqlServer:ValueGenerationStrategy"]);
});
}

[Fact]
public void Alter_column_computation()
{
Expand Down
19 changes: 15 additions & 4 deletions test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public void Detects_byte_identity_column()
var modelBuilder = new ModelBuilder(TestRelationalConventionSetBuilder.Build());
modelBuilder.Entity<Dog>().Property<byte>("Bite").UseSqlServerIdentityColumn();

VerifyError(SqlServerStrings.NonKeyValueGeneration("Bite", nameof(Dog)), modelBuilder.Model);
VerifyWarning(SqlServerStrings.LogByteIdentityColumn.GenerateMessage("Bite", nameof(Dog)), modelBuilder.Model);
}

[Fact]
Expand All @@ -162,16 +162,26 @@ public void Detects_nullable_byte_identity_column()
var modelBuilder = new ModelBuilder(TestRelationalConventionSetBuilder.Build());
modelBuilder.Entity<Dog>().Property<byte?>("Bite").UseSqlServerIdentityColumn();

VerifyError(SqlServerStrings.NonKeyValueGeneration("Bite", nameof(Dog)), modelBuilder.Model);
VerifyWarning(SqlServerStrings.LogByteIdentityColumn.GenerateMessage("Bite", nameof(Dog)), modelBuilder.Model);
}

[Fact]
public void Throws_for_non_key_identity()
public void Passes_for_non_key_identity()
{
var modelBuilder = new ModelBuilder(TestRelationalConventionSetBuilder.Build());
modelBuilder.Entity<Dog>().Property(c => c.Type).UseSqlServerIdentityColumn();

VerifyError(SqlServerStrings.NonKeyValueGeneration(nameof(Dog.Type), nameof(Dog)), modelBuilder.Model);
Validate(modelBuilder.Model);
}

[Fact]
public void Throws_for_multiple_identity_properties()
{
var modelBuilder = new ModelBuilder(TestRelationalConventionSetBuilder.Build());
modelBuilder.Entity<Dog>().Property(c => c.Type).UseSqlServerIdentityColumn();
modelBuilder.Entity<Dog>().Property<int?>("Tag").UseSqlServerIdentityColumn();

VerifyError(SqlServerStrings.MultipleIdentityColumns("'Dog.Tag', 'Dog.Type'", nameof(Dog)), modelBuilder.Model);
}

[Fact]
Expand All @@ -188,6 +198,7 @@ public void Passes_for_non_key_identity_on_model()
{
var modelBuilder = new ModelBuilder(TestRelationalConventionSetBuilder.Build());
modelBuilder.ForSqlServerUseIdentityColumns();
modelBuilder.Entity<Dog>().Property(c => c.Id).ValueGeneratedNever();
modelBuilder.Entity<Dog>().Property(c => c.Type).ValueGeneratedOnAdd();

Validate(modelBuilder.Model);
Expand Down

0 comments on commit 9dcfdcb

Please sign in to comment.