AspNetCoreOData icon indicating copy to clipboard operation
AspNetCoreOData copied to clipboard

WebApiAssembliesResolver class missing causing slow model build

Open sherlock1982 opened this issue 1 year ago • 2 comments

Assemblies affected ASP.NET Core OData 8.x

Describe the bug ASP.NET Core OData 7.x had a special class WebApiAssembliesResolver that used to access only assemblies available to the application. Now ASP.NET Core OData 8.x moved ModelBuilder to a separate assemby but looks like this class is missing and now replaced with:

        services.TryAddSingleton<IAssemblyResolver, DefaultAssemblyResolver>();

Now this class is easy to implement:

public class WebApiAssembliesResolver : IAssemblyResolver
{
    public IEnumerable<Assembly> Assemblies { get; }

    /// <summary>
    /// Initializes a new instance of the WebApiAssembliesResolver class.
    /// </summary>
    /// <param name="applicationPartManager">The inner manager.</param>
    public WebApiAssembliesResolver(ApplicationPartManager applicationPartManager)
    {
        var parts = applicationPartManager.ApplicationParts;
        Assemblies = parts
            .OfType<AssemblyPart>()
            .Select(p => p.Assembly)
            .Distinct()
            .ToList();
    }

}

And provide to EDM builder (don't forget to add WebApiAssembliesResolver as singleton elsewhere):

        return builder.AddOData((opt, serviceProvider) =>
        {
            var resolver = serviceProvider.GetRequiredService<IAssemblyResolver>();
            var builder = new ODataConventionModelBuilder(resolver)

            opt.AddRouteComponents("xapi/v1", builder.GetEdmModel(), services =>
            {
            });
        });

On my relatively small project on my fast developer machine I managed to reduce model load time from 7 seconds (230 assemblies!) to 0.5 seconds (5 assemblies!). Am I doing something incorrect? Should this class be provided instead of DefaultAssemblyResolver how it was in OData 7.x ?

sherlock1982 avatar Apr 08 '24 08:04 sherlock1982

@habbes have you seen this in your performance investigations? Looks pretty massive at first glance although it might require a more elaborate project sample to reproduce the gains.

Scanning assemblies can be incredibly inefficient. This might be one of those areas where using a source generator could help immensely.

julealgon avatar Apr 08 '24 13:04 julealgon

The performance bottleneck is this:

        _allTypesWithDerivedTypeMapping = new Lazy<IDictionary<Type, Type[]>>(
            () => BuildDerivedTypesMapping(assembliesResolver),
            isThreadSafe: false);

I never used 7.x version but I believe original class WebApiAssembliesResolver was there to solve this issue.

sherlock1982 avatar Apr 08 '24 13:04 sherlock1982