Skip to content

Commit

Permalink
round 1 - model + mig + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
maumar committed Apr 12, 2021
1 parent 0c3d9f1 commit c3e9c6c
Show file tree
Hide file tree
Showing 18 changed files with 2,075 additions and 14 deletions.
25 changes: 22 additions & 3 deletions src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public class MigrationsModelDiffer : IMigrationsModelDiffer
public MigrationsModelDiffer(
IRelationalTypeMappingSource typeMappingSource,
IMigrationsAnnotationProvider migrationsAnnotations,
IRelationalAnnotationProvider relationalAnnotations,
#pragma warning disable EF1001 // Internal EF Core API usage.
IChangeDetector changeDetector,
#pragma warning restore EF1001 // Internal EF Core API usage.
Expand All @@ -88,6 +89,7 @@ public MigrationsModelDiffer(
{
Check.NotNull(typeMappingSource, nameof(typeMappingSource));
Check.NotNull(migrationsAnnotations, nameof(migrationsAnnotations));
Check.NotNull(relationalAnnotations, nameof(relationalAnnotations));
#pragma warning disable EF1001 // Internal EF Core API usage.
Check.NotNull(changeDetector, nameof(changeDetector));
#pragma warning restore EF1001 // Internal EF Core API usage.
Expand All @@ -96,6 +98,7 @@ public MigrationsModelDiffer(

TypeMappingSource = typeMappingSource;
MigrationsAnnotations = migrationsAnnotations;
RelationalAnnotations = relationalAnnotations;
ChangeDetector = changeDetector;
UpdateAdapterFactory = updateAdapterFactory;
CommandBatchPreparerDependencies = commandBatchPreparerDependencies;
Expand All @@ -117,6 +120,14 @@ public MigrationsModelDiffer(
/// </summary>
protected virtual IMigrationsAnnotationProvider MigrationsAnnotations { get; }

/// <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>
protected virtual IRelationalAnnotationProvider RelationalAnnotations { get; }

/// <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 Expand Up @@ -608,13 +619,18 @@ protected virtual IEnumerable<MigrationOperation> Diff(
if (source.Schema != target.Schema
|| source.Name != target.Name)
{
yield return new RenameTableOperation
var renameTableOperation = new RenameTableOperation
{
Schema = source.Schema,
Name = source.Name,
NewSchema = target.Schema,
NewName = target.Name
};

// TODO: should we use annotation provider here?
renameTableOperation.AddAnnotations(source.GetAnnotations());

yield return renameTableOperation;
}

var sourceMigrationsAnnotations = source.GetAnnotations();
Expand Down Expand Up @@ -992,8 +1008,10 @@ protected virtual IEnumerable<MigrationOperation> Diff(
var sourceColumnType = source.StoreType ?? sourceTypeMapping.StoreType;
var targetColumnType = target.StoreType ?? targetTypeMapping.StoreType;

var sourceMigrationsAnnotations = source.GetAnnotations();
var targetMigrationsAnnotations = target.GetAnnotations();
var sourceMigrationsAnnotations = RelationalAnnotations.For(source);
var targetMigrationsAnnotations = RelationalAnnotations.For(target);
//var sourceMigrationsAnnotations = source.GetAnnotations();
//var targetMigrationsAnnotations = target.GetAnnotations();

var isNullableChanged = source.IsNullable != target.IsNullable;
var columnTypeChanged = sourceColumnType != targetColumnType;
Expand Down Expand Up @@ -1055,6 +1073,7 @@ protected virtual IEnumerable<MigrationOperation> Add(
var targetMapping = target.PropertyMappings.First();
var targetTypeMapping = targetMapping.TypeMapping;

// TODO: use annotation provider here also?
Initialize(
operation, target, targetTypeMapping, target.IsNullable,
target.GetAnnotations(), inline);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ public static AddPrimaryKeyOperation CreateFrom(IPrimaryKeyConstraint primaryKey
Name = primaryKey.Name,
Columns = primaryKey.Columns.Select(c => c.Name).ToArray()
};

operation.AddAnnotations(primaryKey.GetAnnotations());
operation.AddAnnotations(primaryKey.Table.GetAnnotations());

return operation;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// 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.Expressions;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
Expand Down Expand Up @@ -116,5 +119,141 @@ public static bool CanSetIsMemoryOptimized(

return entityTypeBuilder.CanSetAnnotation(SqlServerAnnotationNames.MemoryOptimized, memoryOptimized, fromDataAnnotation);
}

/// <summary>
/// Configures the table that the entity maps to when targeting SQL Server as temporal.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="periodStartPropertyName"> A value specifying the property name representing start of the period.</param>
/// <param name="periodEndPropertyName"> A value specifying the property name representing end of the period.</param>
/// <param name="historyTableName"> A value specifying the history table name for this entity. Default name will be used if none is specified. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static EntityTypeBuilder IsTemporal(
this EntityTypeBuilder entityTypeBuilder,
string? periodStartPropertyName = null,
string? periodEndPropertyName = null,
string? historyTableName = null)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));

entityTypeBuilder.Metadata.SetIsTemporal(true);
entityTypeBuilder.Metadata.SetTemporalPeriodStartPropertyName(periodStartPropertyName);
entityTypeBuilder.Metadata.SetTemporalPeriodEndPropertyName(periodEndPropertyName);
entityTypeBuilder.Metadata.SetTemporalHistoryTableName(historyTableName);

return entityTypeBuilder;
}

/// <summary>
/// Configures the table that the entity maps to when targeting SQL Server as temporal.
/// </summary>
/// <typeparam name="TEntity"> The entity type being configured. </typeparam>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="periodStartPropertyExpression"> A value specifying the property representing start of the period.</param>
/// <param name="periodEndPropertyExpression"> A value specifying the property representing end of the period.</param>
/// <param name="historyTableName"> A value specifying the history table name for this entity. Default name will be used if none is specified. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static EntityTypeBuilder<TEntity> IsTemporal<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, DateTime>> periodStartPropertyExpression,
Expression<Func<TEntity, DateTime>> periodEndPropertyExpression,
string? historyTableName = null)
where TEntity : class
=> (EntityTypeBuilder<TEntity>)IsTemporal(
entityTypeBuilder,
periodStartPropertyExpression.GetMemberAccess().Name,
periodEndPropertyExpression.GetMemberAccess().Name,
historyTableName);

/// <summary>
/// Configures the table that the entity maps to when targeting SQL Server as temporal.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="periodStartPropertyName"> A value specifying the property name representing start of the period.</param>
/// <param name="periodEndPropertyName"> A value specifying the property name representing end of the period.</param>
/// <param name="historyTableName"> A value specifying the history table name for this entity. Default name will be used if none is specified. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns>
/// The same builder instance if the configuration was applied,
/// <see langword="null" /> otherwise.
/// </returns>
public static IConventionEntityTypeBuilder? IsTemporal(
this IConventionEntityTypeBuilder entityTypeBuilder,
string? periodStartPropertyName = null,
string? periodEndPropertyName = null,
string? historyTableName = null,
bool fromDataAnnotation = false)
{
if (entityTypeBuilder.CanSetIsTemporal(true, fromDataAnnotation)
&& entityTypeBuilder.CanSetTemporalPeriodStartPropertyName(periodStartPropertyName, fromDataAnnotation)
&& entityTypeBuilder.CanSetTemporalPeriodEndPropertyName(periodEndPropertyName, fromDataAnnotation)
&& entityTypeBuilder.CanSetTemporalHistoryTableName(historyTableName))
{
entityTypeBuilder.Metadata.SetIsTemporal(true, fromDataAnnotation);
entityTypeBuilder.Metadata.SetTemporalPeriodStartPropertyName(periodStartPropertyName, fromDataAnnotation);
entityTypeBuilder.Metadata.SetTemporalPeriodEndPropertyName(periodEndPropertyName, fromDataAnnotation);
entityTypeBuilder.Metadata.SetTemporalHistoryTableName(historyTableName, fromDataAnnotation);

return entityTypeBuilder;
}

return null;
}

/// <summary>
/// Returns a value indicating whether the mapped table can be configured as temporal.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="temporal"> A value indicating whether the table is temporal. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the mapped table can be configured as temporal. </returns>
public static bool CanSetIsTemporal(
this IConventionEntityTypeBuilder entityTypeBuilder,
bool? temporal,
bool fromDataAnnotation = false)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));

return entityTypeBuilder.CanSetAnnotation(SqlServerAnnotationNames.IsTemporal, temporal, fromDataAnnotation);
}

/// <summary>
/// TODO: add comment
/// </summary>
public static bool CanSetTemporalPeriodStartPropertyName(
this IConventionEntityTypeBuilder entityTypeBuilder,
string? periodStartPropertyName,
bool fromDataAnnotation = false)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));

return entityTypeBuilder.CanSetAnnotation(SqlServerAnnotationNames.TemporalPeriodStartPropertyName, periodStartPropertyName, fromDataAnnotation);
}

/// <summary>
/// TODO: add comment
/// </summary>
public static bool CanSetTemporalPeriodEndPropertyName(
this IConventionEntityTypeBuilder entityTypeBuilder,
string? periodEndPropertyName,
bool fromDataAnnotation = false)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));

return entityTypeBuilder.CanSetAnnotation(SqlServerAnnotationNames.TemporalPeriodEndPropertyName, periodEndPropertyName, fromDataAnnotation);
}

/// <summary>
/// TODO: add comment
/// </summary>
public static bool CanSetTemporalHistoryTableName(
this IConventionEntityTypeBuilder entityTypeBuilder,
string? historyTableName,
bool fromDataAnnotation = false)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));

return entityTypeBuilder.CanSetAnnotation(SqlServerAnnotationNames.TemporalHistoryTableName, historyTableName, fromDataAnnotation);
}
}
}
Loading

0 comments on commit c3e9c6c

Please sign in to comment.