diff --git a/src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs b/src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs index 9a4ee6d7d3d..e382d56f221 100644 --- a/src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs +++ b/src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs @@ -98,7 +98,7 @@ private void ConfigureUserServices(IServiceCollection services) _reporter.WriteVerbose(DesignStrings.FindingDesignTimeServices(_startupAssembly.GetName().Name)); var designTimeServicesType = _startupAssembly.GetLoadableDefinedTypes() - .Where(t => typeof(IDesignTimeServices).IsAssignableFrom(t)).Select(t => t.AsType()) + .Where(t => typeof(IDesignTimeServices).IsAssignableFrom(t) && t.IsInstantiable()).Select(t => t.AsType()) .FirstOrDefault(); if (designTimeServicesType == null) { diff --git a/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs b/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs index 162a18e5e70..2efe1e4d4ae 100644 --- a/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs +++ b/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs @@ -314,6 +314,101 @@ public string GetInsertScript(HistoryRow row) => throw new NotImplementedException(); } + [ConditionalFact] + public void Abstract_design_time_services_are_ignored() + { + var serviceProvider = CreateDesignServiceProvider( + @" +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.DependencyInjection; + +#pragma warning disable EF1001 + +public abstract class DesignTimeServicesBase : IDesignTimeServices +{ + public void ConfigureDesignTimeServices(IServiceCollection serviceCollection) + { + DoSomething(); + } + + protected abstract void DoSomething(); +} + +public class ActualDesignTimeServices : DesignTimeServicesBase +{ + protected override void DoSomething() + { + // Actual implementation + } +} +").CreateScope().ServiceProvider; + + // Should not throw an exception when creating the service provider + // The abstract base class should be ignored and only concrete implementations should be used + Assert.NotNull(serviceProvider); + } + + [ConditionalFact] + public void Interface_design_time_services_are_ignored() + { + var serviceProvider = CreateDesignServiceProvider( + @" +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.DependencyInjection; + +#pragma warning disable EF1001 + +public interface IDesignTimeServicesInterface : IDesignTimeServices +{ +} + +public class ConcreteDesignTimeServices : IDesignTimeServicesInterface +{ + public void ConfigureDesignTimeServices(IServiceCollection serviceCollection) + { + // Concrete implementation + } +} +").CreateScope().ServiceProvider; + + // Should not throw an exception when creating the service provider + // Interfaces implementing IDesignTimeServices should be ignored + Assert.NotNull(serviceProvider); + } + + [ConditionalFact] + public void Prefers_concrete_class_over_abstract() + { + // This test verifies that when both abstract and concrete classes are present, + // the concrete class is used (since we're using FirstOrDefault which finds the first non-abstract) + var serviceProvider = CreateDesignServiceProvider( + @" +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.DependencyInjection; + +#pragma warning disable EF1001 + +public abstract class AbstractDesignTimeServices : IDesignTimeServices +{ + public void ConfigureDesignTimeServices(IServiceCollection serviceCollection) + { + throw new System.NotImplementedException(""Should not be called""); + } +} + +public class ConcreteDesignTimeServices : IDesignTimeServices +{ + public void ConfigureDesignTimeServices(IServiceCollection serviceCollection) + { + // This should be the one used + } +} +").CreateScope().ServiceProvider; + + // Should not throw an exception and should use the concrete implementation + Assert.NotNull(serviceProvider); + } + public class MyContext(DbContextOptions options) : DbContext(options); private ServiceProvider CreateDesignServiceProvider(