forked from dotnet/efcore
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented dotnet#13088: Add ApplyConfiguration() overloads to Model…
…Builder that scans assemblies.
- Loading branch information
Showing
2 changed files
with
150 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// 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; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
|
||
namespace Microsoft.EntityFrameworkCore | ||
{ | ||
/// <summary> | ||
/// Entity Framework <see cref="ModelBuilder"/> related extension methods. | ||
/// </summary> | ||
public static class ModelBuilderExtensions | ||
{ | ||
/// <summary> | ||
/// Applies configuration from all <see cref="IEntityTypeConfiguration{TEntity}" /> instances that are defined in provided assembly. | ||
/// </summary> | ||
/// <param name="modelBuilder"> The instance of <see cref="ModelBuilder"/> to modify</param> | ||
/// <param name="assembly"> The assembly to scan </param> | ||
/// <param name="predicate"> Optional predicate to filter types within the assembly</param> | ||
/// <returns> | ||
/// The same <see cref="ModelBuilder" /> instance so that additional configuration calls can be chained. | ||
/// </returns> | ||
public static ModelBuilder ApplyEntityTypeConfigurations(this ModelBuilder modelBuilder, Assembly assembly, Func<Type, bool> predicate = null) | ||
{ | ||
var method = typeof(ModelBuilder) | ||
.GetMethods() | ||
.Single( | ||
e => e.Name == nameof(modelBuilder.ApplyConfiguration) | ||
&& e.ContainsGenericParameters | ||
&& e.GetParameters().SingleOrDefault()?.ParameterType?.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)); | ||
foreach (var type in assembly.GetTypes()) | ||
{ | ||
// Only accept types that contain a parameterless constrictor, are not abstract and satisfy a predicate if it was used. | ||
if (type.GetConstructor(Type.EmptyTypes) == null || type.IsAbstract || (!predicate?.Invoke(type) ?? false)) | ||
{ | ||
continue; | ||
} | ||
var @interface = type.GetInterfaces().FirstOrDefault( | ||
i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)); | ||
if (@interface == null) | ||
{ | ||
continue; | ||
} | ||
|
||
|
||
var configuration = Activator.CreateInstance(type); | ||
var target = method.MakeGenericMethod(@interface.GenericTypeArguments[0]); | ||
target.Invoke(modelBuilder, new[] { configuration }); | ||
} | ||
|
||
return modelBuilder; | ||
} | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
test/EFCore.Tests/ModelBuilding/IEntityTypeConfigurationAssemblyScanTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// 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; | ||
using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||
using Microsoft.EntityFrameworkCore.TestUtilities; | ||
using Xunit; | ||
|
||
namespace Microsoft.EntityFrameworkCore.ModelBuilding | ||
{ | ||
public abstract partial class ModelBuilderTest | ||
{ | ||
private static bool _customerConfigurationCalled; | ||
private static bool _customerConfiguration2Called; | ||
private static bool _abstractClassCalled; | ||
protected ModelBuilderTest() | ||
{ | ||
_customerConfigurationCalled = false; | ||
_customerConfiguration2Called = false; | ||
_abstractClassCalled = false; | ||
} | ||
|
||
public class ModelBuilderExtensionsTest : ModelBuilderTest | ||
{ | ||
[Fact] | ||
public void Should_scan_assemblies() | ||
{ | ||
var builder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); | ||
builder.ApplyEntityTypeConfigurations(typeof(IEntityTypeConfigurationTest).Assembly); | ||
|
||
Assert.True(_customerConfigurationCalled); | ||
Assert.True(_customerConfiguration2Called); | ||
Assert.False(_abstractClassCalled); | ||
} | ||
|
||
[Fact] | ||
public void Should_support_filtering() | ||
{ | ||
var builder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); | ||
builder.ApplyEntityTypeConfigurations(typeof(ModelBuilderExtensionsTest).Assembly, type => type.Name == "CustomerConfiguration"); | ||
|
||
Assert.True(_customerConfigurationCalled); | ||
Assert.False(_customerConfiguration2Called); | ||
Assert.False(_abstractClassCalled); | ||
|
||
var entityType = builder.Model.FindEntityType(typeof(Customer)); | ||
Assert.Equal(nameof(Customer.AlternateKey), entityType.GetKeys().Single().Properties.Single().Name); | ||
Assert.Equal(200, entityType.FindProperty(nameof(Customer.Name)).GetMaxLength()); | ||
} | ||
|
||
[Fact] | ||
public void Should_skip_abstract_classes() | ||
{ | ||
var builder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); | ||
builder.ApplyEntityTypeConfigurations(typeof(ModelBuilderExtensionsTest).Assembly, type => type.Name == "AbstractCustomerConfiguration"); | ||
|
||
Assert.False(_customerConfigurationCalled); | ||
Assert.False(_customerConfiguration2Called); | ||
Assert.False(_abstractClassCalled); | ||
} | ||
|
||
private class CustomerConfiguration : IEntityTypeConfiguration<Customer> | ||
{ | ||
public void Configure(EntityTypeBuilder<Customer> builder) | ||
{ | ||
builder.HasKey(c => c.AlternateKey); | ||
|
||
builder.Property(c => c.Name).HasMaxLength(200); | ||
|
||
_customerConfigurationCalled = true; | ||
} | ||
} | ||
|
||
private class CustomerConfiguration2 : IEntityTypeConfiguration<Customer> | ||
{ | ||
public void Configure(EntityTypeBuilder<Customer> builder) | ||
{ | ||
builder.Property(c => c.Name).HasMaxLength(1000); | ||
|
||
_customerConfiguration2Called = true; | ||
} | ||
} | ||
|
||
private abstract class AbstractCustomerConfiguration : IEntityTypeConfiguration<Customer> | ||
{ | ||
public void Configure(EntityTypeBuilder<Customer> builder) | ||
{ | ||
_abstractClassCalled = true; | ||
} | ||
} | ||
} | ||
} | ||
} |