From 0bb279287b04eb5d990f6508d6bbeec5b024a291 Mon Sep 17 00:00:00 2001 From: Dzmitry Safarau Date: Wed, 12 Sep 2018 16:10:53 +0300 Subject: [PATCH] Implemented #13088: Add ApplyConfiguration() overloads to ModelBuilder that scans assemblies. --- src/EFCore/ModelBuilderExtensions.cs | 55 +++++++++ ...EntityTypeConfigurationAssemblyScanTest.cs | 108 ++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 src/EFCore/ModelBuilderExtensions.cs create mode 100644 test/EFCore.Tests/ModelBuilding/IEntityTypeConfigurationAssemblyScanTest.cs diff --git a/src/EFCore/ModelBuilderExtensions.cs b/src/EFCore/ModelBuilderExtensions.cs new file mode 100644 index 00000000000..1f41d884d7c --- /dev/null +++ b/src/EFCore/ModelBuilderExtensions.cs @@ -0,0 +1,55 @@ +// 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.Reflection; + +namespace Microsoft.EntityFrameworkCore +{ + /// + /// Entity Framework related extension methods. + /// + public static class ModelBuilderExtensions + { + /// + /// Applies configuration from all instances that are defined in provided assembly. + /// + /// The instance of to modify + /// The assembly to scan + /// Optional predicate to filter types within the assembly + /// + /// The same instance so that additional configuration calls can be chained. + /// + public static ModelBuilder ApplyEntityTypeConfigurations(this ModelBuilder modelBuilder, Assembly assembly, Func 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; + } + } +} diff --git a/test/EFCore.Tests/ModelBuilding/IEntityTypeConfigurationAssemblyScanTest.cs b/test/EFCore.Tests/ModelBuilding/IEntityTypeConfigurationAssemblyScanTest.cs new file mode 100644 index 00000000000..d1535e1abf2 --- /dev/null +++ b/test/EFCore.Tests/ModelBuilding/IEntityTypeConfigurationAssemblyScanTest.cs @@ -0,0 +1,108 @@ +// 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; + private static bool _abstractClassImplCalled; + + protected ModelBuilderTest() + { + _customerConfigurationCalled = false; + _customerConfiguration2Called = false; + _abstractClassCalled = false; + _abstractClassImplCalled = 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); + Assert.True(_abstractClassImplCalled); + } + + [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); + Assert.False(_abstractClassImplCalled); + + 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); + Assert.False(_abstractClassImplCalled); + } + + private class CustomerConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(c => c.AlternateKey); + + builder.Property(c => c.Name).HasMaxLength(200); + + _customerConfigurationCalled = true; + } + } + + private class CustomerConfiguration2 : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(c => c.Name).HasMaxLength(1000); + + _customerConfiguration2Called = true; + } + } + + private abstract class AbstractCustomerConfiguration : IEntityTypeConfiguration + { + public virtual void Configure(EntityTypeBuilder builder) + { + _abstractClassCalled = true; + } + } + + private class AbstractCustomerConfigurationImpl : AbstractCustomerConfiguration + { + public override void Configure(EntityTypeBuilder builder) + { + _abstractClassImplCalled = true; + } + } + } + } +}