Skip to content

Commit

Permalink
Fix table splitting for derived types in update pipeline.
Browse files Browse the repository at this point in the history
Make the columns nullable for dependent types involved in table splitting.
When uniquifying column names use the declaring entity type name as prefix.

Fixes #8907
  • Loading branch information
AndriySvyryd committed Jun 26, 2017
1 parent 7805579 commit 288ce2f
Show file tree
Hide file tree
Showing 24 changed files with 708 additions and 159 deletions.
106 changes: 106 additions & 0 deletions src/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// 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.Linq;
using Microsoft.EntityFrameworkCore.TestModels.TransportationModel;
using Xunit;

namespace Microsoft.EntityFrameworkCore
{
public abstract class TableSplittingTestBase<TTestStore>
where TTestStore : TestStore
{
[Fact]
public void Can_query_shared()
{
using (var store = CreateTestStore(OnModelCreating))
{
using (var context = CreateContext(store, OnModelCreating))
{
Assert.Equal(4, context.Set<Operator>().ToList().Count);
}
}
}

[Fact(Skip = "#8973")]
public void Can_query_shared_derived()
{
using (var store = CreateTestStore(OnModelCreating))
{
using (var context = CreateContext(store, OnModelCreating))
{
Assert.Equal(2, context.Set<Engine>().ToList().Count);
Assert.Equal(1, context.Set<FuelTank>().ToList().Count);
}
}
}

[Fact]
public void Can_use_with_redundant_relationships()
{
Test_roundtrip(OnModelCreating);
}

[Fact]
public void Can_use_with_chained_relationships()
{
Test_roundtrip(modelBuilder =>
{
OnModelCreating(modelBuilder);
modelBuilder.Entity<FuelTank>(eb =>
{
eb.Ignore(e => e.Vehicle);
});
});
}

[Fact]
public void Can_use_with_fanned_relationships()
{
Test_roundtrip(modelBuilder =>
{
OnModelCreating(modelBuilder);
modelBuilder.Entity<FuelTank>(eb =>
{
eb.Ignore(e => e.Engine);
});
modelBuilder.Entity<CombustionEngine>(eb =>
{
eb.Ignore(e => e.FuelTank);
});
});
}

protected virtual void OnModelCreating(ModelBuilder modelBuilder)
{
TransportationContext.OnModelCreatingBase(modelBuilder);

modelBuilder.Entity<Vehicle>(eb =>
{
eb.HasDiscriminator<string>("Discriminator");
eb.Property<string>("Discriminator").HasColumnName("Discriminator");
eb.ToTable("Vehicles");
});

modelBuilder.Entity<Engine>().ToTable("Vehicles");
modelBuilder.Entity<Operator>().ToTable("Vehicles");
modelBuilder.Entity<FuelTank>().ToTable("Vehicles");
}

protected void Test_roundtrip(Action<ModelBuilder> onModelCreating)
{
using (var store = CreateTestStore(onModelCreating))
{
using (var context = CreateContext(store, onModelCreating))
{
context.AssertSeeded();
}
}
}

protected static readonly string DatabaseName = "TableSplittingTest";
public abstract TTestStore CreateTestStore(Action<ModelBuilder> onModelCreating);
public abstract TransportationContext CreateContext(TTestStore testStore, Action<ModelBuilder> onModelCreating);
}
}
74 changes: 47 additions & 27 deletions src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,40 +209,60 @@ protected virtual void ValidateSharedTableCompatibility(
var firstValidatedType = mappedTypes[0];
var validatedTypes = new List<IEntityType> { firstValidatedType };
var unvalidatedTypes = new Queue<IEntityType>(mappedTypes.Skip(1));
while (unvalidatedTypes.Count > 0)
var invalidTypes = new Queue<IEntityType>();
var doAnotherPass = true;
while (doAnotherPass)
{
var entityType = unvalidatedTypes.Dequeue();
var key = entityType.FindPrimaryKey();
var otherKey = firstValidatedType.FindPrimaryKey();
if (key.Relational().Name != otherKey.Relational().Name)
doAnotherPass = false;
while (unvalidatedTypes.Count > 0)
{
throw new InvalidOperationException(
RelationalStrings.IncompatibleTableKeyNameMismatch(
tableName,
entityType.DisplayName(),
firstValidatedType.DisplayName(),
key.Relational().Name,
Property.Format(key.Properties),
otherKey.Relational().Name,
Property.Format(otherKey.Properties)));
var entityType = unvalidatedTypes.Dequeue();
var relationshipFound = validatedTypes.Any(validatedType =>
entityType.RootType() == validatedType.RootType()
|| IsIdentifyingPrincipal(entityType, validatedType)
|| IsIdentifyingPrincipal(validatedType, entityType));
if (!relationshipFound)
{
invalidTypes.Enqueue(entityType);
continue;
}

var key = entityType.FindPrimaryKey();
var otherKey = firstValidatedType.FindPrimaryKey();
if (key.Relational().Name != otherKey.Relational().Name)
{
throw new InvalidOperationException(
RelationalStrings.IncompatibleTableKeyNameMismatch(
tableName,
entityType.DisplayName(),
firstValidatedType.DisplayName(),
key.Relational().Name,
Property.Format(key.Properties),
otherKey.Relational().Name,
Property.Format(otherKey.Properties)));
}

doAnotherPass = true;
validatedTypes.Add(entityType);
}

var relationshipFound = validatedTypes.Any(validatedType =>
entityType.RootType() == validatedType.RootType()
|| IsIdentifyingPrincipal(entityType, validatedType)
|| IsIdentifyingPrincipal(validatedType, entityType));
if (!relationshipFound)
if (doAnotherPass)
{
throw new InvalidOperationException(
RelationalStrings.IncompatibleTableNoRelationship(
tableName,
entityType.DisplayName(),
firstValidatedType.DisplayName(),
Property.Format(key.Properties),
Property.Format(otherKey.Properties)));
var temp = unvalidatedTypes;
unvalidatedTypes = invalidTypes;
invalidTypes = temp;
}
}

validatedTypes.Add(entityType);
foreach (var entityType in invalidTypes)
{
throw new InvalidOperationException(
RelationalStrings.IncompatibleTableNoRelationship(
tableName,
entityType.DisplayName(),
firstValidatedType.DisplayName(),
Property.Format(entityType.FindPrimaryKey().Properties),
Property.Format(firstValidatedType.FindPrimaryKey().Properties)));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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 Microsoft.EntityFrameworkCore.Infrastructure;
Expand Down Expand Up @@ -118,31 +119,47 @@ private static void TryUniquifyColumnNames(EntityType entityType, Dictionary<str
foreach (var property in entityType.GetDeclaredProperties())
{
var columnName = property.Relational().ColumnName;
if (properties.TryGetValue(columnName, out var otherProperty)
&& !property.IsPrimaryKey())
if (!properties.TryGetValue(columnName, out var otherProperty))
{
properties[columnName] = property;
continue;
}

if (!property.IsPrimaryKey())
{
var relationalPropertyBuilder = property.Builder.Relational(ConfigurationSource.Convention);
if (relationalPropertyBuilder.CanSetColumnName(null))
{
relationalPropertyBuilder.ColumnName = Uniquify(columnName, properties);
columnName = Uniquify(columnName, property.DeclaringEntityType.ShortName(), properties);
relationalPropertyBuilder.ColumnName = columnName;
properties[columnName] = property;
continue;
}
}

if (!otherProperty.IsPrimaryKey())
{
var otherRelationalPropertyBuilder = otherProperty.Builder.Relational(ConfigurationSource.Convention);
if (!otherRelationalPropertyBuilder.CanSetColumnName(null))
if (otherRelationalPropertyBuilder.CanSetColumnName(null))
{
continue;
properties[columnName] = property;
columnName = Uniquify(columnName, otherProperty.DeclaringEntityType.ShortName(), properties);
otherRelationalPropertyBuilder.ColumnName = columnName;
properties[columnName] = otherProperty;
}
otherRelationalPropertyBuilder.ColumnName = Uniquify(columnName, properties);
}
properties[columnName] = property;
}
}

private static string Uniquify<T>(string baseIdentifier, Dictionary<string, T> existingIdentifiers)
private static string Uniquify<T>(string baseIdentifier, string prefix, Dictionary<string, T> existingIdentifiers)
{
var finalIdentifier = baseIdentifier;
if (!baseIdentifier.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
baseIdentifier = prefix + "_" + baseIdentifier;
}

var suffix = 1;
var finalIdentifier = baseIdentifier;
while (existingIdentifiers.ContainsKey(finalIdentifier))
{
finalIdentifier = baseIdentifier + suffix;
Expand Down
21 changes: 12 additions & 9 deletions src/EFCore.Relational/Metadata/RelationalPropertyAnnotations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,22 @@ public virtual string ColumnName

private string GetDefaultColumnName()
{
var entityType = Property.DeclaringEntityType;
var pk = Property.GetContainingPrimaryKey();
if (pk != null)
{
var entityType = Property.DeclaringEntityType;
var ownership = entityType.GetForeignKeys().SingleOrDefault(fk => fk.IsOwnership);
if (ownership != null)
foreach (var fk in entityType.FindForeignKeys(pk.Properties))
{
var ownerType = ownership.PrincipalEntityType;
if (!fk.PrincipalKey.IsPrimaryKey())
{
continue;
}

var principalEntityType = fk.PrincipalEntityType;
var entityTypeAnnotations = GetAnnotations(entityType);
var ownerTypeAnnotations = GetAnnotations(ownerType);
if (entityTypeAnnotations.TableName == ownerTypeAnnotations.TableName
&& entityTypeAnnotations.Schema == ownerTypeAnnotations.Schema)
var principalTypeAnnotations = GetAnnotations(principalEntityType);
if (entityTypeAnnotations.TableName == principalTypeAnnotations.TableName
&& entityTypeAnnotations.Schema == principalTypeAnnotations.Schema)
{
var index = -1;
for (var i = 0; i < pk.Properties.Count; i++)
Expand All @@ -71,13 +75,12 @@ private string GetDefaultColumnName()
}
}

return GetAnnotations(ownerType.FindPrimaryKey().Properties[index]).ColumnName;
return GetAnnotations(principalEntityType.FindPrimaryKey().Properties[index]).ColumnName;
}
}
}
else
{
var entityType = Property.DeclaringEntityType;
StringBuilder builder = null;
do
{
Expand Down
22 changes: 21 additions & 1 deletion src/EFCore.Relational/Metadata/RelationalPropertyExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
// 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;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Metadata
{
public static class RelationalPropertyExtensions
{
public static bool IsColumnNullable([NotNull] this IProperty property)
=> property.DeclaringEntityType.BaseType != null || property.IsNullable;
{
if (property.DeclaringEntityType.BaseType != null
|| property.IsNullable)
{
return true;
}

if (property.IsPrimaryKey())
{
return false;
}

var pk = property.DeclaringEntityType.FindPrimaryKey();
return pk != null
&& property.DeclaringEntityType.FindForeignKeys(pk.Properties)
.Any(fk => fk.PrincipalKey.IsPrimaryKey()
&& fk.DeclaringEntityType.Relational().TableName == fk.PrincipalEntityType.Relational().TableName
&& fk.DeclaringEntityType.Relational().Schema == fk.PrincipalEntityType.Relational().Schema);
}
}
}
16 changes: 8 additions & 8 deletions src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

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

Loading

0 comments on commit 288ce2f

Please sign in to comment.