Skip to content

Commit

Permalink
Validate seed data
Browse files Browse the repository at this point in the history
Fix issues with discriminators and table splitting

Part of #629
  • Loading branch information
AndriySvyryd committed Oct 6, 2017
1 parent fee8a6f commit 095fa74
Show file tree
Hide file tree
Showing 25 changed files with 978 additions and 664 deletions.
Expand Up @@ -35,7 +35,7 @@ public static string GetConfiguredColumnType([NotNull] this IProperty property)
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static RelationalTypeMapping FindRelationalMapping(
[NotNull] this IProperty property)
[NotNull] this IProperty property)
=> property[CoreAnnotationNames.TypeMapping] as RelationalTypeMapping;
}
}
350 changes: 225 additions & 125 deletions src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs

Large diffs are not rendered by default.

163 changes: 77 additions & 86 deletions src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs
Expand Up @@ -14,9 +14,6 @@

namespace Microsoft.EntityFrameworkCore.Update.Internal
{
using ModificationCommandIdentityMapFactory
= Func<string, string, Func<string>, bool, ModificationCommandIdentityMap>;

/// <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 All @@ -30,7 +27,7 @@ public class CommandBatchPreparer : ICommandBatchPreparer
private IStateManager _stateManager;
private readonly bool _sensitiveLoggingEnabled;

private IReadOnlyDictionary<IEntityType, ModificationCommandIdentityMapFactory> _tableSharingIdentityMapFactories;
private IReadOnlyDictionary<(string Schema, string Name), SharedTableEntryMapFactory<ModificationCommand>> _sharedTableEntryMapFactories;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
Expand Down Expand Up @@ -96,133 +93,127 @@ public virtual IEnumerable<ModificationCommandBatch> BatchCommands(IReadOnlyList
[NotNull] Func<string> generateParameterName)
{
var commands = new List<ModificationCommand>();
var tableSharingMapFactories = GetTableSharingIdentityMapFactories(entries[0].EntityType.Model);
Dictionary<(string Schema, string Name), ModificationCommandIdentityMap> sharedCommandsMap = null;
if (_sharedTableEntryMapFactories == null)
{
_sharedTableEntryMapFactories = SharedTableEntryMap<ModificationCommand>.CreateSharedTableEntryMapFactories(
entries[0].EntityType.Model, StateManager);
}

Dictionary<(string Schema, string Name), SharedTableEntryMap<ModificationCommand>> sharedTablesCommandsMap = null;
foreach (var entry in entries)
{
var entityType = entry.EntityType;
var table = entityType.Relational().TableName;
var schema = entityType.Relational().Schema;
var relationalExtensions = entityType.Relational();
var table = relationalExtensions.TableName;
var schema = relationalExtensions.Schema;
var tableKey = (schema, table);

ModificationCommand command;
if (tableSharingMapFactories.TryGetValue(entityType, out var commandIdentityMapFactory))
if (_sharedTableEntryMapFactories.TryGetValue(tableKey, out var commandIdentityMapFactory))
{
if (sharedCommandsMap == null)
if (sharedTablesCommandsMap == null)
{
sharedCommandsMap = new Dictionary<(string Schema, string Name), ModificationCommandIdentityMap>();
sharedTablesCommandsMap = new Dictionary<(string Schema, string Name), SharedTableEntryMap<ModificationCommand>>();
}
if (!sharedCommandsMap.TryGetValue((schema, table), out var sharedCommands))
if (!sharedTablesCommandsMap.TryGetValue(tableKey, out var sharedCommandsMap))
{
sharedCommands = commandIdentityMapFactory(
table, schema, generateParameterName, _sensitiveLoggingEnabled);
sharedCommandsMap.Add((schema, table), sharedCommands);
sharedCommandsMap = commandIdentityMapFactory((t, s, c) => new ModificationCommand(
t, s, generateParameterName, _sensitiveLoggingEnabled, c));
sharedTablesCommandsMap.Add((schema, table), sharedCommandsMap);
}

command = sharedCommands.GetOrAddCommand(entry);
command = sharedCommandsMap.GetOrAddValue(entry);
}
else
{
command = new ModificationCommand(
table, schema, generateParameterName, _sensitiveLoggingEnabled, comparer: null);
command = new ModificationCommand(table, schema, generateParameterName, _sensitiveLoggingEnabled, comparer: null);
}

command.AddEntry(entry);
commands.Add(command);
}

if (sharedCommandsMap != null)
if (sharedTablesCommandsMap != null)
{
foreach (var modificationCommandIdentityMap in sharedCommandsMap.Values)
{
modificationCommandIdentityMap.Validate(_sensitiveLoggingEnabled);
}
Validate(sharedTablesCommandsMap);
}

return commands.Where(
c => c.EntityState != EntityState.Modified
|| c.ColumnModifications.Any(m => m.IsWrite));
}

private IReadOnlyDictionary<IEntityType, ModificationCommandIdentityMapFactory> GetTableSharingIdentityMapFactories(
IModel model)
private void Validate(Dictionary<(string Schema, string Name), SharedTableEntryMap<ModificationCommand>> sharedTablesCommandsMap)
{
if (_tableSharingIdentityMapFactories != null)
{
return _tableSharingIdentityMapFactories;
}

var tables = new Dictionary<(string Schema, string TableName), HashSet<IEntityType>>();
foreach (var entityType in model.GetEntityTypes())
foreach (var modificationCommandIdentityMap in sharedTablesCommandsMap.Values)
{
var fullName = (entityType.Relational().Schema, entityType.Relational().TableName);
if (!tables.TryGetValue(fullName, out var mappedEntityTypes))
foreach (var command in modificationCommandIdentityMap.Values)
{
mappedEntityTypes = new HashSet<IEntityType>();
tables.Add(fullName, mappedEntityTypes);
}

mappedEntityTypes.Add(entityType);
}
if ((command.EntityState != EntityState.Added
&& command.EntityState != EntityState.Deleted)
|| (command.Entries.Any(e => modificationCommandIdentityMap.GetPrincipals(e.EntityType).Count == 0)
&& command.Entries.Any(e => modificationCommandIdentityMap.GetDependents(e.EntityType).Count == 0)))
{
continue;
}

var sharedTablesMap = new Dictionary<IEntityType, ModificationCommandIdentityMapFactory>();
foreach (var tableMapping in tables)
{
var entityTypes = tableMapping.Value;
if (entityTypes.Count > 1)
{
var principals = new Dictionary<IEntityType, IReadOnlyList<IEntityType>>(entityTypes.Count);
var dependents = new Dictionary<IEntityType, IReadOnlyList<IEntityType>>(entityTypes.Count);
foreach (var entityType in entityTypes)
var tableName = (string.IsNullOrEmpty(command.Schema) ? "" : command.Schema + ".") + command.TableName;
foreach (var entry in command.Entries)
{
var principalList = new List<IEntityType>();
if (!dependents.TryGetValue(entityType, out var dependentList))
foreach (var principalEntityType in modificationCommandIdentityMap.GetPrincipals(entry.EntityType))
{
dependentList = new List<IEntityType>();
dependents[entityType] = dependentList;
if (!command.Entries.Any(
principalEntry => principalEntry != entry
&& principalEntityType.IsAssignableFrom(principalEntry.EntityType)))
{
if (_sensitiveLoggingEnabled)
{
throw new InvalidOperationException(
RelationalStrings.SharedRowEntryCountMismatchSensitive(
entry.EntityType.DisplayName(),
tableName,
principalEntityType.DisplayName(),
entry.BuildCurrentValuesString(entry.EntityType.FindPrimaryKey().Properties),
command.EntityState));
}

throw new InvalidOperationException(
RelationalStrings.SharedRowEntryCountMismatch(
entry.EntityType.DisplayName(),
tableName,
principalEntityType.DisplayName(),
command.EntityState));
}
}

foreach (var foreignKey in entityType.FindForeignKeys(entityType.FindPrimaryKey().Properties))
foreach (var dependentEntityType in modificationCommandIdentityMap.GetDependents(entry.EntityType))
{
if (foreignKey.PrincipalKey.IsPrimaryKey()
&& entityTypes.Contains(foreignKey.PrincipalEntityType))
if (!command.Entries.Any(
dependentEntry => dependentEntry != entry
&& dependentEntityType.IsAssignableFrom(dependentEntry.EntityType)))
{
var principalEntityType = foreignKey.PrincipalEntityType;
principalList.Add(principalEntityType);
if (!dependents.TryGetValue(principalEntityType, out dependentList))
if (_sensitiveLoggingEnabled)
{
dependentList = new List<IEntityType>();
dependents[principalEntityType] = dependentList;
throw new InvalidOperationException(
RelationalStrings.SharedRowEntryCountMismatchSensitive(
entry.EntityType.DisplayName(),
tableName,
dependentEntityType.DisplayName(),
entry.BuildCurrentValuesString(entry.EntityType.FindPrimaryKey().Properties),
command.EntityState));
}
((List<IEntityType>)dependentList).Add(entityType);

throw new InvalidOperationException(
RelationalStrings.SharedRowEntryCountMismatch(
entry.EntityType.DisplayName(),
tableName,
dependentEntityType.DisplayName(),
command.EntityState));
}
}

principals[entityType] = principalList;
}

ModificationCommandIdentityMap CommandIdentityMapFactory(
string name,
string schema,
Func<string> generateParameterName,
bool sensitiveLoggingEnabled)
=> new ModificationCommandIdentityMap(
StateManager,
principals,
dependents,
name,
schema,
generateParameterName,
sensitiveLoggingEnabled);

foreach (var entityType in entityTypes)
{
sharedTablesMap.Add(entityType, CommandIdentityMapFactory);
}
}
}

_tableSharingIdentityMapFactories = sharedTablesMap;
return sharedTablesMap;
}

// To avoid violating store constraints the modification commands must be sorted
Expand Down

0 comments on commit 095fa74

Please sign in to comment.