Skip to content

Commit

Permalink
Add NativeAOT support to the compiled relational model
Browse files Browse the repository at this point in the history
Throw when invoking model building or migrations in a NativeAOT context

Part of #29761
  • Loading branch information
AndriySvyryd committed Jul 22, 2023
1 parent 84b734d commit 13f798c
Show file tree
Hide file tree
Showing 20 changed files with 903 additions and 472 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1128,7 +1128,7 @@ private void Create(
{
mainBuilder
.Append($"RelationalModel.CreateColumnMapping(")
.Append($"(ColumnBase<ColumnMappingBase>){tableVariable}.FindColumn({code.Literal(columnMapping.Column.Name)})!, ")
.Append($"(ColumnBase<ColumnMappingBase>){metadataVariables[columnMapping.Column]}, ")
.Append($"{typeBaseVariable}.FindProperty({code.Literal(columnMapping.Property.Name)})!, ")
.Append(tableMappingVariable).AppendLine(");");
}
Expand Down Expand Up @@ -1175,7 +1175,7 @@ private void Create(
foreach (var columnMapping in tableMapping.ColumnMappings)
{
mainBuilder
.Append($"RelationalModel.CreateColumnMapping({tableVariable}.FindColumn({code.Literal(columnMapping.Column.Name)})!, ")
.Append($"RelationalModel.CreateColumnMapping({metadataVariables[columnMapping.Column]}, ")
.Append($"{typeBaseVariable}.FindProperty({code.Literal(columnMapping.Property.Name)})!, ")
.Append(tableMappingVariable).AppendLine(");");
}
Expand Down Expand Up @@ -1221,7 +1221,7 @@ private void Create(
foreach (var columnMapping in viewMapping.ColumnMappings)
{
mainBuilder
.Append($"RelationalModel.CreateViewColumnMapping({viewVariable}.FindColumn({code.Literal(columnMapping.Column.Name)})!, ")
.Append($"RelationalModel.CreateViewColumnMapping({metadataVariables[columnMapping.Column]}, ")
.Append($"{typeBaseVariable}.FindProperty({code.Literal(columnMapping.Property.Name)})!, ")
.Append(viewMappingVariable).AppendLine(");");
}
Expand Down Expand Up @@ -1273,7 +1273,7 @@ private void Create(
foreach (var columnMapping in sqlQueryMapping.ColumnMappings)
{
mainBuilder
.Append($"RelationalModel.CreateSqlQueryColumnMapping({sqlQueryVariable}.FindColumn({code.Literal(columnMapping.Column.Name)})!, ")
.Append($"RelationalModel.CreateSqlQueryColumnMapping({metadataVariables[columnMapping.Column]}, ")
.Append($"{typeBaseVariable}.FindProperty({code.Literal(columnMapping.Property.Name)})!, ")
.Append(sqlQueryMappingVariable).AppendLine(");");
}
Expand Down Expand Up @@ -1327,7 +1327,7 @@ private void Create(
foreach (var columnMapping in functionMapping.ColumnMappings)
{
mainBuilder
.Append($"RelationalModel.CreateFunctionColumnMapping({functionVariable}.FindColumn({code.Literal(columnMapping.Column.Name)})!, ")
.Append($"RelationalModel.CreateFunctionColumnMapping({metadataVariables[columnMapping.Column]}, ")
.Append($"{typeBaseVariable}.FindProperty({code.Literal(columnMapping.Property.Name)})!, ")
.Append(functionMappingVariable).AppendLine(");");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Query.Internal;

Expand All @@ -14,23 +15,6 @@ namespace Microsoft.EntityFrameworkCore;
/// </summary>
public static class RelationalDatabaseFacadeExtensions
{
/// <summary>
/// Applies any pending migrations for the context to the database. Will create the database
/// if it does not already exist.
/// </summary>
/// <remarks>
/// <para>
/// Note that this API is mutually exclusive with <see cref="DatabaseFacade.EnsureCreated" />. EnsureCreated does not use migrations
/// to create the database and therefore the database that is created cannot be later updated using migrations.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-migrations">Database migrations</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="databaseFacade">The <see cref="DatabaseFacade" /> for the context.</param>
public static void Migrate(this DatabaseFacade databaseFacade)
=> databaseFacade.GetRelationalService<IMigrator>().Migrate();

/// <summary>
/// Gets all the migrations that are defined in the configured migrations assembly.
/// </summary>
Expand All @@ -40,7 +24,9 @@ public static void Migrate(this DatabaseFacade databaseFacade)
/// <param name="databaseFacade">The <see cref="DatabaseFacade" /> for the context.</param>
/// <returns>The list of migrations.</returns>
public static IEnumerable<string> GetMigrations(this DatabaseFacade databaseFacade)
=> databaseFacade.GetRelationalService<IMigrationsAssembly>().Migrations.Keys;
=> RuntimeFeature.IsDynamicCodeSupported
? databaseFacade.GetRelationalService<IMigrationsAssembly>().Migrations.Keys
: throw new InvalidOperationException(CoreStrings.NativeAotNoMigrations);

/// <summary>
/// Gets all migrations that have been applied to the target database.
Expand All @@ -51,8 +37,10 @@ public static IEnumerable<string> GetMigrations(this DatabaseFacade databaseFaca
/// <param name="databaseFacade">The <see cref="DatabaseFacade" /> for the context.</param>
/// <returns>The list of migrations.</returns>
public static IEnumerable<string> GetAppliedMigrations(this DatabaseFacade databaseFacade)
=> databaseFacade.GetRelationalService<IHistoryRepository>()
.GetAppliedMigrations().Select(hr => hr.MigrationId);
=> RuntimeFeature.IsDynamicCodeSupported
? databaseFacade.GetRelationalService<IHistoryRepository>()
.GetAppliedMigrations().Select(hr => hr.MigrationId)
: throw new InvalidOperationException(CoreStrings.NativeAotNoMigrations);

/// <summary>
/// Asynchronously gets all migrations that have been applied to the target database.
Expand All @@ -67,8 +55,10 @@ public static IEnumerable<string> GetAppliedMigrations(this DatabaseFacade datab
public static async Task<IEnumerable<string>> GetAppliedMigrationsAsync(
this DatabaseFacade databaseFacade,
CancellationToken cancellationToken = default)
=> (await databaseFacade.GetRelationalService<IHistoryRepository>()
.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false)).Select(hr => hr.MigrationId);
=> RuntimeFeature.IsDynamicCodeSupported
? (await databaseFacade.GetRelationalService<IHistoryRepository>()
.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false)).Select(hr => hr.MigrationId)
: throw new InvalidOperationException(CoreStrings.NativeAotNoMigrations);

/// <summary>
/// Gets all migrations that are defined in the assembly but haven't been applied to the target database.
Expand All @@ -79,7 +69,9 @@ public static async Task<IEnumerable<string>> GetAppliedMigrationsAsync(
/// <param name="databaseFacade">The <see cref="DatabaseFacade" /> for the context.</param>
/// <returns>The list of migrations.</returns>
public static IEnumerable<string> GetPendingMigrations(this DatabaseFacade databaseFacade)
=> GetMigrations(databaseFacade).Except(GetAppliedMigrations(databaseFacade));
=> RuntimeFeature.IsDynamicCodeSupported
? GetMigrations(databaseFacade).Except(GetAppliedMigrations(databaseFacade))
: throw new InvalidOperationException(CoreStrings.NativeAotNoMigrations);

/// <summary>
/// Asynchronously gets all migrations that are defined in the assembly but haven't been applied to the target database.
Expand All @@ -94,8 +86,36 @@ public static IEnumerable<string> GetPendingMigrations(this DatabaseFacade datab
public static async Task<IEnumerable<string>> GetPendingMigrationsAsync(
this DatabaseFacade databaseFacade,
CancellationToken cancellationToken = default)
=> GetMigrations(databaseFacade).Except(
await GetAppliedMigrationsAsync(databaseFacade, cancellationToken).ConfigureAwait(false));
=> RuntimeFeature.IsDynamicCodeSupported
? GetMigrations(databaseFacade).Except(
await GetAppliedMigrationsAsync(databaseFacade, cancellationToken).ConfigureAwait(false))
: throw new InvalidOperationException(CoreStrings.NativeAotNoMigrations);

/// <summary>
/// Applies any pending migrations for the context to the database. Will create the database
/// if it does not already exist.
/// </summary>
/// <remarks>
/// <para>
/// Note that this API is mutually exclusive with <see cref="DatabaseFacade.EnsureCreated" />. EnsureCreated does not use migrations
/// to create the database and therefore the database that is created cannot be later updated using migrations.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-migrations">Database migrations</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="databaseFacade">The <see cref="DatabaseFacade" /> for the context.</param>
public static void Migrate(this DatabaseFacade databaseFacade)
{
if (RuntimeFeature.IsDynamicCodeSupported)
{
databaseFacade.GetRelationalService<IMigrator>().Migrate();
}
else
{
throw new InvalidOperationException(CoreStrings.NativeAotNoMigrations);
}
}

/// <summary>
/// Asynchronously applies any pending migrations for the context to the database. Will create the database
Expand All @@ -118,8 +138,9 @@ public static async Task<IEnumerable<string>> GetPendingMigrationsAsync(
public static Task MigrateAsync(
this DatabaseFacade databaseFacade,
CancellationToken cancellationToken = default)
=> databaseFacade.GetRelationalService<IMigrator>()
.MigrateAsync(cancellationToken: cancellationToken);
=> RuntimeFeature.IsDynamicCodeSupported
? databaseFacade.GetRelationalService<IMigrator>().MigrateAsync(cancellationToken: cancellationToken)
: throw new InvalidOperationException(CoreStrings.NativeAotNoMigrations);

/// <summary>
/// Executes the given SQL against the database and returns the number of rows affected.
Expand Down Expand Up @@ -938,7 +959,9 @@ public static void SetCommandTimeout(this DatabaseFacade databaseFacade, TimeSpa
/// A SQL script.
/// </returns>
public static string GenerateCreateScript(this DatabaseFacade databaseFacade)
=> databaseFacade.GetRelationalService<IRelationalDatabaseCreator>().GenerateCreateScript();
=> RuntimeFeature.IsDynamicCodeSupported
? databaseFacade.GetRelationalService<IRelationalDatabaseCreator>().GenerateCreateScript()
: throw new InvalidOperationException(CoreStrings.NativeAotNoMigrations);

/// <summary>
/// Returns <see langword="true" /> if the database provider currently in use is a relational database.
Expand All @@ -962,6 +985,11 @@ public static bool IsRelational(this DatabaseFacade databaseFacade)
/// </returns>
public static bool HasPendingModelChanges(this DatabaseFacade databaseFacade)
{
if (!RuntimeFeature.IsDynamicCodeSupported)
{
throw new InvalidOperationException(CoreStrings.NativeAotNoMigrations);
}

var modelDiffer = databaseFacade.GetRelationalService<IMigrationsModelDiffer>();
var migrationsAssembly = databaseFacade.GetRelationalService<IMigrationsAssembly>();

Expand Down
16 changes: 10 additions & 6 deletions src/EFCore.Relational/Metadata/Internal/Column.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Update.Internal;

Expand All @@ -25,11 +26,9 @@ public class Column : ColumnBase<ColumnMapping>, IColumn
/// </summary>
public Column(string name, string type, Table table,
RelationalTypeMapping? storeTypeMapping = null,
ValueComparer? providerValueComparer = null,
ColumnAccessors? accessors = null)
ValueComparer? providerValueComparer = null)
: base(name, type, table, storeTypeMapping, providerValueComparer)
{
_accessors = accessors;
}

/// <summary>
Expand All @@ -48,9 +47,14 @@ public Column(string name, string type, Table table,
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual ColumnAccessors Accessors
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _accessors, this, static column =>
ColumnAccessorsFactory.Create(column));
{
get => NonCapturingLazyInitializer.EnsureInitialized(
ref _accessors, this, static column =>
RuntimeFeature.IsDynamicCodeSupported
? ColumnAccessorsFactory.Create(column)
: throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel));
set { _accessors = value; }
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
5 changes: 2 additions & 3 deletions src/EFCore.Relational/Metadata/Internal/JsonColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ public class JsonColumn : Column, IColumn
/// </summary>
public JsonColumn(string name, string type, Table table,
RelationalTypeMapping? storeTypeMapping = null,
ValueComparer? providerValueComparer = null,
ColumnAccessors? accessors = null)
: base(name, type, table, storeTypeMapping, providerValueComparer, accessors)
ValueComparer? providerValueComparer = null)
: base(name, type, table, storeTypeMapping, providerValueComparer)
{
}

Expand Down
17 changes: 16 additions & 1 deletion src/EFCore.Relational/Metadata/Internal/UniqueConstraint.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Update.Internal;

Expand Down Expand Up @@ -77,7 +78,21 @@ public override bool IsReadOnly
public virtual IRowKeyValueFactory GetRowKeyValueFactory()
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _rowKeyValueFactory, this,
static constraint => constraint.Table.Model.Model.GetRelationalDependencies().RowKeyValueFactoryFactory.Create(constraint));
static constraint =>
RuntimeFeature.IsDynamicCodeSupported
? constraint.Table.Model.Model.GetRelationalDependencies().RowKeyValueFactoryFactory.Create(constraint)
: throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel));

/// <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>

[EntityFrameworkInternal]
public virtual void SetRowKeyValueFactory(IRowKeyValueFactory factory)
=> _rowKeyValueFactory = factory;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ public static ColumnAccessors Create(IColumn column)
private static readonly MethodInfo GenericCreate
= typeof(ColumnAccessorsFactory).GetTypeInfo().GetDeclaredMethod(nameof(CreateGeneric))!;

[UsedImplicitly]
private static ColumnAccessors CreateGeneric<TColumn>(IColumn column)
/// <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>
public static ColumnAccessors CreateGeneric<TColumn>(IColumn column)
=> new(
CreateCurrentValueGetter<TColumn>(column),
CreateOriginalValueGetter<TColumn>(column));
Expand Down
Loading

0 comments on commit 13f798c

Please sign in to comment.