Skip to content

Commit 5fbeb1f

Browse files
[release/10.0] Fix migration discovery when [DbContext] is only on base class (#37437)
Fixes #37431 --------- Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
1 parent c218ae1 commit 5fbeb1f

File tree

2 files changed

+64
-2
lines changed

2 files changed

+64
-2
lines changed

src/EFCore.Relational/Migrations/Internal/MigrationsAssembly.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,27 @@ public MigrationsAssembly(
4343
_logger = logger;
4444
}
4545

46+
private static Type? GetDbContextType(TypeInfo typeInfo)
47+
{
48+
// Walk the inheritance chain to find the first DbContextAttribute.
49+
// This supports inheritance while avoiding AmbiguousMatchException
50+
// that would occur with GetCustomAttribute(inherit: true) when multiple
51+
// attributes exist in the hierarchy.
52+
var currentType = typeInfo.AsType();
53+
while (currentType != null && currentType != typeof(object))
54+
{
55+
var attribute = currentType.GetCustomAttribute<DbContextAttribute>(inherit: false);
56+
if (attribute != null)
57+
{
58+
return attribute.ContextType;
59+
}
60+
61+
currentType = currentType.BaseType;
62+
}
63+
64+
return null;
65+
}
66+
4667
/// <summary>
4768
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
4869
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -60,7 +81,7 @@ IReadOnlyDictionary<string, TypeInfo> Create()
6081
var items
6182
= from t in Assembly.GetConstructibleTypes()
6283
where t.IsSubclassOf(typeof(Migration))
63-
&& t.GetCustomAttribute<DbContextAttribute>(inherit: false)?.ContextType == _contextType
84+
&& GetDbContextType(t) == _contextType
6485
let id = t.GetCustomAttribute<MigrationAttribute>()?.Id
6586
orderby id
6687
select (id, t);
@@ -94,7 +115,7 @@ public virtual ModelSnapshot? ModelSnapshot
94115
=> _modelSnapshot
95116
??= (from t in Assembly.GetConstructibleTypes()
96117
where t.IsSubclassOf(typeof(ModelSnapshot))
97-
&& t.GetCustomAttribute<DbContextAttribute>(inherit: false)?.ContextType == _contextType
118+
&& GetDbContextType(t) == _contextType
98119
select (ModelSnapshot)Activator.CreateInstance(t.AsType())!)
99120
.FirstOrDefault();
100121

test/EFCore.Relational.Tests/Migrations/Internal/MigrationsAssemblyTest.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,47 @@ public void Migrations_handles_inherited_DbContextAttribute()
9797
Assert.Contains(result, t => t.Key == "20150302103200_InheritedMigration");
9898
}
9999

100+
[ConditionalFact]
101+
public void Migrations_finds_attribute_on_base_class_only()
102+
{
103+
var assembly = CreateMigrationsAssemblyWithAttributeOnBaseOnly();
104+
105+
// This should find the migration even though the attribute is only on the base class
106+
var result = assembly.Migrations;
107+
108+
Assert.Single(result);
109+
Assert.Contains(result, t => t.Key == "20150302103300_DerivedMigrationWithBaseAttribute");
110+
}
111+
112+
private IMigrationsAssembly CreateMigrationsAssemblyWithAttributeOnBaseOnly()
113+
=> new MigrationsAssembly(
114+
new CurrentDbContext(new AttributeOnBaseContext()),
115+
new DbContextOptions<DbContext>(
116+
new Dictionary<Type, IDbContextOptionsExtension>
117+
{
118+
{ typeof(FakeRelationalOptionsExtension), new FakeRelationalOptionsExtension() }
119+
}),
120+
new MigrationsIdGenerator(),
121+
new FakeDiagnosticsLogger<DbLoggerCategory.Migrations>());
122+
123+
private class AttributeOnBaseContext : DbContext;
124+
125+
[DbContext(typeof(AttributeOnBaseContext))]
126+
private class BaseMigrationWithAttribute : Migration
127+
{
128+
protected override void Up(MigrationBuilder migrationBuilder)
129+
{
130+
}
131+
}
132+
133+
[Migration("20150302103300_DerivedMigrationWithBaseAttribute")]
134+
private class DerivedMigrationWithBaseAttribute : BaseMigrationWithAttribute
135+
{
136+
protected override void Up(MigrationBuilder migrationBuilder)
137+
{
138+
}
139+
}
140+
100141
private IMigrationsAssembly CreateInheritedMigrationsAssembly()
101142
=> new MigrationsAssembly(
102143
new CurrentDbContext(new DerivedContext()),

0 commit comments

Comments
 (0)