Skip to content

Commit

Permalink
Merge branch 'release/9.0-staging' => 'release/9.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
AndriySvyryd authored Jan 13, 2025
2 parents ca34008 + cc16006 commit 32f03a4
Show file tree
Hide file tree
Showing 57 changed files with 8,344 additions and 4,594 deletions.
2 changes: 1 addition & 1 deletion azure-pipelines-public.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ stages:
- name: _HelixBuildConfig
value: $(_BuildConfig)
- name: HelixTargetQueues
value: Windows.10.Amd64.Open;OSX.1200.Amd64.Open;OSX.1200.ARM64.Open;[email protected]/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
value: Windows.10.Amd64.Open;OSX.13.ARM64.Open;[email protected]/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
- name: _HelixAccessToken
value: '' # Needed for public queues
steps:
Expand Down
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ extends:
- name: _HelixBuildConfig
value: $(_BuildConfig)
- name: HelixTargetQueues
value: Windows.10.Amd64;OSX.1200.Amd64;OSX.1200.ARM64;[email protected]/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
value: Windows.10.Amd64;OSX.13.ARM64;[email protected]/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
- name: _HelixAccessToken
# Needed for internal queues
value: $(HelixApiAccessToken)
Expand Down
224 changes: 215 additions & 9 deletions src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,25 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal;
/// </summary>
public sealed class StringDictionaryComparer<TDictionary, TElement> : ValueComparer<object>, IInfrastructure<ValueComparer>
{
private static readonly bool UseOldBehavior35239 =
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35239", out var enabled35239) && enabled35239;

private static readonly MethodInfo CompareMethod = typeof(StringDictionaryComparer<TDictionary, TElement>).GetMethod(
nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(object), typeof(Func<TElement, TElement, bool>)])!;

private static readonly MethodInfo LegacyCompareMethod = typeof(StringDictionaryComparer<TDictionary, TElement>).GetMethod(
nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(object), typeof(ValueComparer)])!;

private static readonly MethodInfo GetHashCodeMethod = typeof(StringDictionaryComparer<TDictionary, TElement>).GetMethod(
nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(Func<TElement, int>)])!;

private static readonly MethodInfo LegacyGetHashCodeMethod = typeof(StringDictionaryComparer<TDictionary, TElement>).GetMethod(
nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(ValueComparer)])!;

private static readonly MethodInfo SnapshotMethod = typeof(StringDictionaryComparer<TDictionary, TElement>).GetMethod(
nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(Func<TElement, TElement>)])!;

private static readonly MethodInfo LegacySnapshotMethod = typeof(StringDictionaryComparer<TDictionary, TElement>).GetMethod(
nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(ValueComparer)])!;

/// <summary>
Expand Down Expand Up @@ -52,14 +64,56 @@ ValueComparer IInfrastructure<ValueComparer>.Instance
var prm1 = Expression.Parameter(typeof(object), "a");
var prm2 = Expression.Parameter(typeof(object), "b");

if (UseOldBehavior35239)
{
// (a, b) => Compare(a, b, new Comparer(...))
return Expression.Lambda<Func<object?, object?, bool>>(
Expression.Call(
LegacyCompareMethod,
prm1,
prm2,
#pragma warning disable EF9100
elementComparer.ConstructorExpression),
#pragma warning restore EF9100
prm1,
prm2);
}

// we check the compatibility between element type we expect on the Equals methods
// vs what we actually get from the element comparer
// if the expected is assignable from actual we can just do simple call...
if (typeof(TElement).IsAssignableFrom(elementComparer.Type))
{
// (a, b) => Compare(a, b, elementComparer.Equals)
return Expression.Lambda<Func<object?, object?, bool>>(
Expression.Call(
CompareMethod,
prm1,
prm2,
elementComparer.EqualsExpression),
prm1,
prm2);
}

// ...otherwise we need to rewrite the actual lambda (as we can't change the expected signature)
// in that case we are rewriting the inner lambda parameters to TElement and cast to the element comparer
// type argument in the body, so that semantics of the element comparison func don't change
var newInnerPrm1 = Expression.Parameter(typeof(TElement), "a");
var newInnerPrm2 = Expression.Parameter(typeof(TElement), "b");

var newEqualsExpressionBody = elementComparer.ExtractEqualsBody(
Expression.Convert(newInnerPrm1, elementComparer.Type),
Expression.Convert(newInnerPrm2, elementComparer.Type));

return Expression.Lambda<Func<object?, object?, bool>>(
Expression.Call(
CompareMethod,
prm1,
prm2,
#pragma warning disable EF9100
elementComparer.ConstructorExpression),
#pragma warning restore EF9100
Expression.Lambda(
newEqualsExpressionBody,
newInnerPrm1,
newInnerPrm2)),
prm1,
prm2);
}
Expand All @@ -68,32 +122,144 @@ private static Expression<Func<object, int>> GetHashCodeLambda(ValueComparer ele
{
var prm = Expression.Parameter(typeof(object), "o");

if (UseOldBehavior35239)
{
// o => GetHashCode((IEnumerable)o, new Comparer(...))
return Expression.Lambda<Func<object, int>>(
Expression.Call(
LegacyGetHashCodeMethod,
Expression.Convert(
prm,
typeof(IEnumerable)),
#pragma warning disable EF9100
elementComparer.ConstructorExpression),
#pragma warning restore EF9100
prm);
}

if (typeof(TElement).IsAssignableFrom(elementComparer.Type))
{
// o => GetHashCode((IEnumerable)o, elementComparer.GetHashCode)
return Expression.Lambda<Func<object, int>>(
Expression.Call(
GetHashCodeMethod,
Expression.Convert(
prm,
typeof(IEnumerable)),
elementComparer.HashCodeExpression),
prm);
}

var newInnerPrm = Expression.Parameter(typeof(TElement), "o");

var newInnerBody = elementComparer.ExtractHashCodeBody(
Expression.Convert(
newInnerPrm,
elementComparer.Type));

return Expression.Lambda<Func<object, int>>(
Expression.Call(
GetHashCodeMethod,
Expression.Convert(
prm,
typeof(IEnumerable)),
#pragma warning disable EF9100
elementComparer.ConstructorExpression),
#pragma warning restore EF9100
Expression.Lambda(
newInnerBody,
newInnerPrm)),
prm);
}

private static Expression<Func<object, object>> SnapshotLambda(ValueComparer elementComparer)
{
var prm = Expression.Parameter(typeof(object), "source");

if (UseOldBehavior35239)
{
// source => Snapshot(source, new Comparer(..))
return Expression.Lambda<Func<object, object>>(
Expression.Call(
LegacySnapshotMethod,
prm,
#pragma warning disable EF9100
elementComparer.ConstructorExpression),
#pragma warning restore EF9100
prm);
}

// TElement is both argument and return type so the types need to be the same
if (typeof(TElement) == elementComparer.Type)
{
// source => Snapshot(source, elementComparer.Snapshot)
return Expression.Lambda<Func<object, object>>(
Expression.Call(
SnapshotMethod,
prm,
elementComparer.SnapshotExpression),
prm);
}

var newInnerPrm = Expression.Parameter(typeof(TElement), "source");

var newInnerBody = elementComparer.ExtractSnapshotBody(
Expression.Convert(
newInnerPrm,
elementComparer.Type));

// note we need to also convert the result of inner lambda back to TElement
return Expression.Lambda<Func<object, object>>(
Expression.Call(
SnapshotMethod,
prm,
#pragma warning disable EF9100
elementComparer.ConstructorExpression),
#pragma warning restore EF9100
Expression.Lambda(
Expression.Convert(
newInnerBody,
typeof(TElement)),
newInnerPrm)),
prm);
}

private static bool Compare(object? a, object? b, Func<TElement?, TElement?, bool> elementCompare)
{
if (ReferenceEquals(a, b))
{
return true;
}

if (a is null)
{
return b is null;
}

if (b is null)
{
return false;
}

if (a is IReadOnlyDictionary<string, TElement?> aDictionary && b is IReadOnlyDictionary<string, TElement?> bDictionary)
{
if (aDictionary.Count != bDictionary.Count)
{
return false;
}

foreach (var pair in aDictionary)
{
if (!bDictionary.TryGetValue(pair.Key, out var bValue)
|| !elementCompare(pair.Value, bValue))
{
return false;
}
}

return true;
}

throw new InvalidOperationException(
CosmosStrings.BadDictionaryType(
(a is IDictionary<string, TElement?> ? b : a).GetType().ShortDisplayName(),
typeof(IDictionary<,>).MakeGenericType(typeof(string), typeof(TElement)).ShortDisplayName()));
}

private static bool Compare(object? a, object? b, ValueComparer elementComparer)
{
if (ReferenceEquals(a, b))
Expand Down Expand Up @@ -136,6 +302,27 @@ private static bool Compare(object? a, object? b, ValueComparer elementComparer)
typeof(IDictionary<,>).MakeGenericType(typeof(string), elementComparer.Type).ShortDisplayName()));
}

private static int GetHashCode(IEnumerable source, Func<TElement?, int> elementGetHashCode)
{
if (source is not IReadOnlyDictionary<string, TElement?> sourceDictionary)
{
throw new InvalidOperationException(
CosmosStrings.BadDictionaryType(
source.GetType().ShortDisplayName(),
typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName()));
}

var hash = new HashCode();

foreach (var pair in sourceDictionary)
{
hash.Add(pair.Key);
hash.Add(pair.Value == null ? 0 : elementGetHashCode(pair.Value));
}

return hash.ToHashCode();
}

private static int GetHashCode(IEnumerable source, ValueComparer elementComparer)
{
if (source is not IReadOnlyDictionary<string, TElement?> sourceDictionary)
Expand All @@ -157,6 +344,25 @@ private static int GetHashCode(IEnumerable source, ValueComparer elementComparer
return hash.ToHashCode();
}

private static IReadOnlyDictionary<string, TElement?> Snapshot(object source, Func<TElement?, TElement?> elementSnapshot)
{
if (source is not IReadOnlyDictionary<string, TElement?> sourceDictionary)
{
throw new InvalidOperationException(
CosmosStrings.BadDictionaryType(
source.GetType().ShortDisplayName(),
typeof(IDictionary<,>).MakeGenericType(typeof(string), typeof(TElement)).ShortDisplayName()));
}

var snapshot = new Dictionary<string, TElement?>();
foreach (var pair in sourceDictionary)
{
snapshot[pair.Key] = pair.Value == null ? default : (TElement?)elementSnapshot(pair.Value);
}

return snapshot;
}

private static IReadOnlyDictionary<string, TElement?> Snapshot(object source, ValueComparer elementComparer)
{
if (source is not IReadOnlyDictionary<string, TElement?> sourceDictionary)
Expand Down
10 changes: 5 additions & 5 deletions src/EFCore.Relational/Migrations/Internal/Migrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Transactions;
using Microsoft.EntityFrameworkCore.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Migrations.Internal;

Expand Down Expand Up @@ -94,7 +93,7 @@ public Migrator(
public virtual void Migrate(string? targetMigration)
{
var useTransaction = _connection.CurrentTransaction is null;
ValidateMigrations(useTransaction);
ValidateMigrations(useTransaction, targetMigration);

using var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);

Expand Down Expand Up @@ -219,7 +218,7 @@ public virtual async Task MigrateAsync(
CancellationToken cancellationToken = default)
{
var useTransaction = _connection.CurrentTransaction is null;
ValidateMigrations(useTransaction);
ValidateMigrations(useTransaction, targetMigration);

using var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);

Expand Down Expand Up @@ -349,7 +348,7 @@ await _migrationCommandExecutor.ExecuteNonQueryAsync(
}
}

private void ValidateMigrations(bool useTransaction)
private void ValidateMigrations(bool useTransaction, string? targetMigration)
{
if (!useTransaction
&& _executionStrategy.RetriesOnFailure)
Expand All @@ -365,7 +364,8 @@ private void ValidateMigrations(bool useTransaction)
{
_logger.ModelSnapshotNotFound(this, _migrationsAssembly);
}
else if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
else if (targetMigration == null
&& RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
&& HasPendingModelChanges())
{
var modelSource = (ModelSource)_currentContext.Context.GetService<IModelSource>();
Expand Down
Loading

0 comments on commit 32f03a4

Please sign in to comment.