Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ steps:
is1ESPipeline: true

${{ each parameter in parameters }}:
${{ parameter.key }}: ${{ parameter.value }}
${{ parameter.key }}: ${{ parameter.value }}
25 changes: 23 additions & 2 deletions src/EFCore.Relational/Migrations/Internal/MigrationsAssembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,27 @@ public MigrationsAssembly(
_logger = logger;
}

private static Type? GetDbContextType(TypeInfo typeInfo)
{
// Walk the inheritance chain to find the first DbContextAttribute.
// This supports inheritance while avoiding AmbiguousMatchException
// that would occur with GetCustomAttribute(inherit: true) when multiple
// attributes exist in the hierarchy.
var currentType = typeInfo.AsType();
while (currentType != null && currentType != typeof(object))
{
var attribute = currentType.GetCustomAttribute<DbContextAttribute>(inherit: false);
if (attribute != null)
{
return attribute.ContextType;
}

currentType = currentType.BaseType;
}

return null;
}

/// <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 All @@ -60,7 +81,7 @@ IReadOnlyDictionary<string, TypeInfo> Create()
var items
= from t in Assembly.GetConstructibleTypes()
where t.IsSubclassOf(typeof(Migration))
&& t.GetCustomAttribute<DbContextAttribute>(inherit: false)?.ContextType == _contextType
&& GetDbContextType(t) == _contextType
let id = t.GetCustomAttribute<MigrationAttribute>()?.Id
orderby id
select (id, t);
Expand Down Expand Up @@ -94,7 +115,7 @@ public virtual ModelSnapshot? ModelSnapshot
=> _modelSnapshot
??= (from t in Assembly.GetConstructibleTypes()
where t.IsSubclassOf(typeof(ModelSnapshot))
&& t.GetCustomAttribute<DbContextAttribute>(inherit: false)?.ContextType == _contextType
&& GetDbContextType(t) == _contextType
select (ModelSnapshot)Activator.CreateInstance(t.AsType())!)
.FirstOrDefault();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ protected virtual Expression CreateSnapshotExpression(
continue;

case IProperty property:
// Shadow property materialization on complex types is not currently supported (see #35613).
if (propertyBase.DeclaringType is IComplexType && property.IsShadowProperty())
{
arguments[i] = propertyBase.ClrType.GetDefaultValueConstant();
types[i] = propertyBase.ClrType;
continue;
}

arguments[i] = CreateSnapshotValueExpression(CreateReadValueExpression(parameter, property), property);
continue;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,47 @@ public void Migrations_handles_inherited_DbContextAttribute()
Assert.Contains(result, t => t.Key == "20150302103200_InheritedMigration");
}

[ConditionalFact]
public void Migrations_finds_attribute_on_base_class_only()
{
var assembly = CreateMigrationsAssemblyWithAttributeOnBaseOnly();

// This should find the migration even though the attribute is only on the base class
var result = assembly.Migrations;

Assert.Single(result);
Assert.Contains(result, t => t.Key == "20150302103300_DerivedMigrationWithBaseAttribute");
}

private IMigrationsAssembly CreateMigrationsAssemblyWithAttributeOnBaseOnly()
=> new MigrationsAssembly(
new CurrentDbContext(new AttributeOnBaseContext()),
new DbContextOptions<DbContext>(
new Dictionary<Type, IDbContextOptionsExtension>
{
{ typeof(FakeRelationalOptionsExtension), new FakeRelationalOptionsExtension() }
}),
new MigrationsIdGenerator(),
new FakeDiagnosticsLogger<DbLoggerCategory.Migrations>());

private class AttributeOnBaseContext : DbContext;

[DbContext(typeof(AttributeOnBaseContext))]
private class BaseMigrationWithAttribute : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
}
}

[Migration("20150302103300_DerivedMigrationWithBaseAttribute")]
private class DerivedMigrationWithBaseAttribute : BaseMigrationWithAttribute
{
protected override void Up(MigrationBuilder migrationBuilder)
{
}
}

private IMigrationsAssembly CreateInheritedMigrationsAssembly()
=> new MigrationsAssembly(
new CurrentDbContext(new DerivedContext()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,65 @@ public class ComplexTypeWithAllNulls

#endregion 37162

#region Issue37337

[ConditionalFact]
public virtual async Task Nullable_complex_type_with_discriminator_and_shadow_property()
{
var contextFactory = await InitializeAsync<Context37337>(
seed: context =>
{
context.Add(
new Context37337.EntityType
{
Prop = new Context37337.OptionalComplexProperty
{
OptionalValue = true
}
});
return context.SaveChangesAsync();
});

await using var context = contextFactory.CreateContext();

var entities = await context.Set<Context37337.EntityType>().ToArrayAsync();

Assert.Single(entities);
var entity = entities[0];
Assert.NotNull(entity.Prop);
Assert.True(entity.Prop.OptionalValue);
}

private class Context37337(DbContextOptions options) : DbContext(options)
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<EntityType>();
entity.Property(p => p.Id);
entity.HasKey(p => p.Id);

var compl = entity.ComplexProperty(p => p.Prop);
compl.Property(p => p.OptionalValue);
compl.HasDiscriminator();

// Shadow property added via convention (e.g., audit field)
entity.Property<string>("CreatedBy").IsRequired(false);
}

public class EntityType
{
public Guid Id { get; set; }
public OptionalComplexProperty? Prop { get; set; }
}

public class OptionalComplexProperty
{
public bool? OptionalValue { get; set; }
}
}

#endregion Issue37337

protected override string StoreName
=> "AdHocComplexTypeQueryTest";
}