EFCore.BulkExtensions icon indicating copy to clipboard operation
EFCore.BulkExtensions copied to clipboard

SQL Server/PostgreSQL conflict: ambiguous reference for IndexBuilder

Open echernyavsky opened this issue 3 years ago • 13 comments

Starting from version 6.0.4 there is an issue with IncludeProperties method in IndexBuilder.

Ambiguous invocation: Microsoft.EntityFrameworkCore.Metadata.Builders.IndexBuilder<TEntity> IncludeProperties<TEntity>(this Microsoft.EntityFrameworkCore.Metadata.Builders.IndexBuilder<TEntity>, System.Linq.Expressions.Expression<System.Func<TEntity,object?>>) (in class SqlServerIndexBuilderExtensions) Microsoft.EntityFrameworkCore.Metadata.Builders.IndexBuilder<TEntity> IncludeProperties<TEntity>(this Microsoft.EntityFrameworkCore.Metadata.Builders.IndexBuilder<TEntity>, System.Linq.Expressions.Expression<System.Func<TEntity,object>>) (in class NpgsqlIndexBuilderExtensions) match

The problem is that the EFCore.BulkExtensions now refers Microsoft.EntityFrameworkCore.SqlServer and Microsoft.EntityFrameworkCore.PostgreSQL which has completely the same set of extensions for EF builders.

Looks like it will be better to create separate packages for Postgres and SQL Server implementations.

echernyavsky avatar Nov 29 '21 15:11 echernyavsky

How do you reproduce this problem? In RunUpdate test it works without issue: PropertiesToInclude = new List<string> { nameof(Item.Description) },

borisdj avatar Nov 29 '21 15:11 borisdj

public class MyEntityConfig : IEntityTypeConfiguration<MyEntity>
{
    protected override void ConfigureEntity(EntityTypeBuilder<MyEntity> builder)
    {
        builder.HasIndex(it => it.UserId)
            .IncludeProperties(it => new { it.DeletedDate });
    }
}

The problem is that even if I specify the extensions class implicitly there is an autogenerated snapshot that have the same issue.

echernyavsky avatar Nov 29 '21 15:11 echernyavsky

Try this fix at the moment: https://github.com/dotnet/efcore/issues/20780

borisdj avatar Nov 29 '21 16:11 borisdj

Use explicit call like this:

protected override void ConfigureEntity(EntityTypeBuilder<MyEntity> builder)
{
    builder.HasIndex(it => it.UserId);
    Expression<Func<MyEntity, object?>> expr = it => new { it.DeletedDate };
    SqlServerIndexBuilderExtensions.IncludeProperties(builder, expr);
}

borisdj avatar Nov 29 '21 17:11 borisdj

Thank you for the references. These solutions are ok as work-arounds, but is there any plans to fix this issues?

echernyavsky avatar Nov 30 '21 09:11 echernyavsky

Will consider all options.

borisdj avatar Dec 02 '21 17:12 borisdj

I am also running into this issue lately, after upgrading to .NET 6.

Will consider all options.

As @echernyavsky already opted, would it be an option to expose the Sql and Postgress depencies in an explicit package (e.g. like EF does it as well). So we get a BulkExtensions.Sql and BulkExtensions.Postgress package, which determines the provider used.

FrankHoogmans avatar Jan 14 '22 06:01 FrankHoogmans

As a workaround you can install Npgsql.EntityFrameworkCore.PostgreSQL explicitly and give it some kind of alias. Npgsql will be virtually disabled by doing that.

For more info see: extern-alias

<Project Sdk="Microsoft.NET.Sdk">

   <ItemGroup>
      <PackageReference Include="EFCore.BulkExtensions" Version="6.2.9" />

      <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.2">
         <Aliases>EfCoreNpgsql</Aliases>
      </PackageReference>
...

PawelGerr avatar Jan 15 '22 18:01 PawelGerr

As a workaround you can install Npgsql.EntityFrameworkCore.PostgreSQL explicitly and give it some kind of alias. Npgsql will be virtually disabled by doing that.

For more info see: extern-alias

<Project Sdk="Microsoft.NET.Sdk">

   <ItemGroup>
      <PackageReference Include="EFCore.BulkExtensions" Version="6.2.9" />

      <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.2">
         <Aliases>EfCoreNpgsql</Aliases>
      </PackageReference>
...

The workaround works, although I'd still prefer for this package not to force me to load a specific provider I am not using.

FrankHoogmans avatar Jan 17 '22 09:01 FrankHoogmans

We also just ran into this issue. I guess the common approach is to have a core package like EFCore.BulkExtensions which is provider independent, and then you have additional provider specific packages like EFCore.BulkExtensions.SqlServer and EFCore.BulkExtensions.PostgreSQL.

I'm fully on @FrankHoogmans side: I would like EFCore.BulkExtensions not to pull providers. According to this comment it is considered unusual to pull multiple providers.

Danielku15 avatar Apr 05 '22 10:04 Danielku15

I have created a proof of concept for splitting the library into multiple ones: https://github.com/FrankHoogmans/EFCore.BulkExtensions/tree/proof-of-concept/split-into-provider-packages

This would seperate the explicit dependencies and their respective code into seperate libraries. The only breaking change is that is is needed to call provider specific registration code on startup of your app (once), to ensure everything is setup correctly:

            SQLServer.BulkExtensions.EnableSQLServer();
            SQLite.BulkExtensions.EnableSQLite();
            PostgreSql.BulkExtensions.EnablePostgreSql();

There are some things I am not completely satisfied with, but it's a proof of concept that I think works and showcases the possibilities. Refactoring would always be an option. All units tests (except for integration tests, did not have all providers available locally) are working.

@borisdj Would any future development in this direction (multiple packages) be considered as an option?

FrankHoogmans avatar Apr 06 '22 10:04 FrankHoogmans

I've been considering that option. Second approach that came to mind is to keep main NuGet with all providers but to have separate packages that would just exclude other providers and its folders. This would be done with some automated procedure before doing publish. Reason for this is to keep maintaining easier. Lately don't have much spare time.

Another thing is, I have planned to do some larger refactoring first (no timeframe yet), which would include more code separation that would then enable easier implementation of either of these 'splitting' methods. Note: https://devblogs.microsoft.com/dotnet/app-trimming-in-net-5/

@FrankHoogmans will analyze your repo more when time comes for splits. Thx for the effort.

borisdj avatar Apr 07 '22 10:04 borisdj

@borisdj You could potentially set PrivateAssets="all" for the package references related to providers. This should then exclude the reference when packing and publishing. Found this blog post illustrating it nicely: https://www.jocheojeda.com/2019/07/22/how-to-exclude-package-dependencies-in-a-nuget-package/

But this would require that the code itself in the library is separated enough that no access to the provider is attempted as it would lead to TypeLoadExceptions unless you actually install the other provider packages as integrator.

Danielku15 avatar Apr 07 '22 15:04 Danielku15

I've encountered a similar problem after upgrading my application that uses DB Functions (DateDiff, etc.). I'm getting the error about EF.Functions.DateDiffDay being ambiguous between Microsoft.EntityFrameworkCore.SqlServerDbFunctionsExtensions.DateDiffDay and Microsoft.EntityFrameworkCore.MySqlDbFunctionsExtensions.DateDiffDay.

I've tried the workaround suggested above, by directly referencing the Pomelo MySql library that is used by this BulkExtensions project, and aliasing it, so my project file now looks like so:

<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.2">
			<Aliases>PomeloMySql</Aliases>
		</PackageReference>

But this has not resolved my problem. I still get the same build error.

Please advise what I may be missing? (For clarity, my application only uses SqlServer, and NOT MySql.)

shawndewet avatar Dec 06 '22 16:12 shawndewet

Next Structure (was only on version 6.6.0):

Num Project References NuGet
[0] EFCore.BulkExtensions -
[1] EFCore.BulkExtensions.SqlServer [0] EFCore.BulkExtensions.SqlServer
[2] EFCore.BulkExtensions.PostgreSql [0] EFCore.BulkExtensions.PostgreSql
[3] EFCore.BulkExtensions.MySql [0] EFCore.BulkExtensions.MySql
[4] EFCore.BulkExtensions.SQLite [0] EFCore.BulkExtensions.SQLite
[5] EFCore.BulkExtensions.Common [1,2,3,4] EFCore.BulkExtensions

[0] | EFCore.BulkExtensions is shared core project that has no Nuget. Instead project [5] EFCore.BulkExtensions.Common references all other projects thereby includes all providers and it has NuGet with base name: EFCore.BulkExtensions to keep full compatibility with previous versions.

** UPDATE ** Latest Structure:

Num Project NuGet
[0] EFCore.BulkExtensions EFCore.BulkExtensions
[1] EFCore.BulkExtensions.SqlServer EFCore.BulkExtensions.SqlServer
[2] EFCore.BulkExtensions.PostgreSql EFCore.BulkExtensions.PostgreSql
[3] EFCore.BulkExtensions.MySql EFCore.BulkExtensions.MySql
[4] EFCore.BulkExtensions.Sqlite EFCore.BulkExtensions.Sqlite

EFCore.BulkExtensions is main Project and Nuget that references all providers. Other per provider projects are empty shells (has just .csproj file) in repo and only on build common files and their adapter files are copied (with script) to the project that is then build for NuGet generation.

borisdj avatar Dec 07 '22 12:12 borisdj