efcore / EFCore.NamingConventions

Entity Framework Core plugin to apply naming conventions to table and column names (e.g. snake_case)
Apache License 2.0
759 stars 75 forks source link

.NET 9 support #295

Open pdevito3 opened 1 week ago

pdevito3 commented 1 week ago

Error NU1107 : Version conflict detected for Microsoft.EntityFrameworkCore.Relational. Install/reference Microsoft.EntityFrameworkCore.Relational 9.0.0 directly to project PROJECT to resolve this issue. PROJECT -> Microsoft.EntityFrameworkCore.Design 9.0.0 -> Microsoft.EntityFrameworkCore.Relational (>= 9.0.0) PROJECT -> EFCore.NamingConventions 8.0.1 -> Microsoft.EntityFrameworkCore.Relational (>= 8.0.0 && < 9.0.0).

hey y'all, any thoughts on when we'd get a .NET 9 supported version live? even as a prerelease -- currently blocking my project upgrades

faramos commented 1 week ago

Same here

onionhammer commented 1 week ago

Ditto

sbwalker commented 1 week ago

Blocked by this as well when upgrading to official .NET 9 release. Why was this not a problem with .NET 9 RC2?

https://github.com/oqtane/oqtane.framework/discussions/4755#discussioncomment-11232375

EduardoBocada commented 1 week ago

Same thing here.

EduardoBocada commented 1 week ago

Blocked by this as well when upgrading to official .NET 9 release. Why was this not a problem with .NET 9 RC2?

oqtane/oqtane.framework#4755 (reply in thread)

Because the EntityFrameworkCore version constraint range defined in Directory.Packages.props is greater than or equal to 8.0.0 and less than 9.0.0 (which was the case of the 9.0.0-rc2)

roji commented 1 week ago

Everyone, please be patient - it may take a week or two, but the 9-compatible will be released soon.

atrauzzi commented 5 days ago

Here's hoping sooner than two :cry:

Quite blocked by this.

roji commented 5 days ago

@atrauzzi and others, you can use your own build as a temporary measure to get unblocked - the main branch already targets EF 9.

atrauzzi commented 5 days ago

Seems unwieldy given that dotnet doesn't offer any way to override nuget packages with local projects.

It looks like I'm waiting on some other npgsql related dependencies to be updated as well.

fergalmoran commented 5 days ago

Seems unwieldy given that dotnet doesn't offer any way to override nuget packages with local projects.

I think if you take a dependency on a package like this then you need to accept that it will not instantly track the core updates.

roji commented 5 days ago

Seems unwieldy given that dotnet doesn't offer any way to override nuget packages with local projects.

You should be able to simply comment out your <PackageReference>, replacing it with a <PackageReference>.

It looks like I'm waiting on some other npgsql related dependencies to be updated as well.

Npgsql 9.0 and EFCore.PG 9.0 should be released very soon (i.e. in the next couple of days).

Bohdandn commented 4 days ago

@atrauzzi

Seems unwieldy given that dotnet doesn't offer any way to override nuget packages with local projects.

<PackageReference Include="EFCore.NamingConventions" Version="8.0.3">
  <ExcludeAssets>All</ExcludeAssets>
</PackageReference>
<Reference Include="EFCore.NamingConventions">
  <HintPath>.\EFCore.NamingConventions.dll</HintPath>
  <Private>true</Private>
</Reference>
maurobernal commented 2 days ago

if main work in Net 9.0, when publish the package in Nuget?

roji commented 2 days ago

Everyone, please do not post "when will the new version be out"; there's tons of work happening at the moment, and I hope to get around to it in a week or two. As I wrote above, if this is urgent for you, you can compile your own version.

ralmsdeveloper commented 1 day ago

I know that some are super excited for the new version of the conversion, there is something you can do temporarily, and that way you can use EF Core 9, just for example you will use SnakeCase and LowerCase.

public static class ConvertionExtensions
{
    private static CultureInfo _culture;

    public static void ToSnakeCaseNames(this ModelBuilder modelBuilder)
    {
        _culture = CultureInfo.InvariantCulture;

        SetNames(modelBuilder, NamingConvention.SnakeCase);
    }

    public static void ToLowerCaseNames(this ModelBuilder modelBuilder)
    {
        _culture = CultureInfo.InvariantCulture;

        SetNames(modelBuilder, NamingConvention.LowerCase);
    }

    private static string? NameRewriter(this string name, NamingConvention naming)
    {
        if (string.IsNullOrEmpty(name)) return name;

        return naming == NamingConvention.SnakeCase
            ? SnakeCaseNameRewriter(name)
            : LowerCaseNameRewriter(name);
    }

    private enum NamingConvention
    {
        SnakeCase,
        LowerCase,
    }

    private static void SetNames(ModelBuilder modelBuilder, NamingConvention naming)
    {
        _culture = CultureInfo.InvariantCulture;

        foreach (var entity in modelBuilder.Model.GetEntityTypes())
        {
            entity.SetViewName(entity.GetViewName()?.NameRewriter(naming));
            entity.SetSchema(entity.GetSchema()?.NameRewriter(naming));
            entity.SetTableName(entity.GetTableName()?.NameRewriter(naming));

            foreach (var property in entity!.GetProperties())
            {
                property.SetColumnName(property.GetColumnName()?.NameRewriter(naming));
            }

            foreach (var key in entity.GetKeys())
            {
                key.SetName(key.GetName()?.NameRewriter(naming));
            }

            foreach (var key in entity.GetForeignKeys())
            {
                key.SetConstraintName(key.GetConstraintName()?.NameRewriter(naming));
            }

            foreach (var index in entity.GetIndexes())
            {
                index.SetDatabaseName(index.GetDatabaseName()?.NameRewriter(naming));
            }
        }
    }

    private static string? LowerCaseNameRewriter(string name)
        => name.ToLower(_culture);

    // https://github.com/efcore/EFCore.NamingConventions/blob/main/EFCore.NamingConventions/Internal/SnakeCaseNameRewriter.cs
    private static string SnakeCaseNameRewriter(string name)
    {
        var builder = new StringBuilder(name.Length + Math.Min(2, name.Length / 5));
        var previousCategory = default(UnicodeCategory?);

        for (var currentIndex = 0; currentIndex < name.Length; currentIndex++)
        {
            var currentChar = name[currentIndex];
            if (currentChar == '_')
            {
                builder.Append('_');
                previousCategory = null;
                continue;
            }

            var currentCategory = char.GetUnicodeCategory(currentChar);
            switch (currentCategory)
            {
                case UnicodeCategory.UppercaseLetter:
                case UnicodeCategory.TitlecaseLetter:
                    if (previousCategory == UnicodeCategory.SpaceSeparator ||
                        previousCategory == UnicodeCategory.LowercaseLetter ||
                        previousCategory != UnicodeCategory.DecimalDigitNumber &&
                        previousCategory != null &&
                        currentIndex > 0 &&
                        currentIndex + 1 < name.Length &&
                        char.IsLower(name[currentIndex + 1]))
                    {
                        builder.Append('_');
                    }

                    currentChar = char.ToLower(currentChar, _culture);
                    break;

                case UnicodeCategory.LowercaseLetter:
                case UnicodeCategory.DecimalDigitNumber:
                    if (previousCategory == UnicodeCategory.SpaceSeparator)
                    {
                        builder.Append('_');
                    }
                    break;

                default:
                    if (previousCategory != null)
                    {
                        previousCategory = UnicodeCategory.SpaceSeparator;
                    }
                    continue;
            }

            builder.Append(currentChar);
            previousCategory = currentCategory;
        }

        return builder.ToString().ToLower(_culture);
    }
}

In your OnModelCreating add the call!

protected override void OnModelCreating(ModelBuilder builder)
{ 
    builder.ToSnakeCaseNames();
   // or 
   //    builder.ToLowerCaseNames();
}

With this you should be able to update your application!

FYI: Remove the dependency on EFCore.NamingConventions temporarily.

martinib77 commented 20 hours ago

I'm having only as a warning, not an error

warning NU1608: Detected package version outside of dependency constraint: EFCore.NamingConventions 8.0.3 requires Microsoft.EntityFrameworkCore (>= 8.0.0 && < 9.0.0) but version Microsoft.EntityFrameworkCore 9.0.0 was resolved.
/Users/user/src/sisa2/backend/Sisa.Application.Data/Sisa.Application.Data.csproj : warning NU1608: Detected package version outside of dependency constraint: EFCore.NamingConventions 8.0.3 requires Microsoft.EntityFrameworkCore.Relational (>= 8.0.0 && < 9.0.0) but version Microsoft.EntityFrameworkCore.Relational 9.0.0 was resolved.

Any ideas why is that ?

mhosman commented 9 hours ago

I know that some are super excited for the new version of the conversion, there is something you can do temporarily, and that way you can use EF Core 9, just for example you will use SnakeCase and LowerCase.

public static class ConvertionExtensions
{
    private static CultureInfo _culture;

    public static void ToSnakeCaseNames(this ModelBuilder modelBuilder)
    {
        _culture = CultureInfo.InvariantCulture;

        SetNames(modelBuilder, NamingConvention.SnakeCase);
    }

    public static void ToLowerCaseNames(this ModelBuilder modelBuilder)
    {
        _culture = CultureInfo.InvariantCulture;

        SetNames(modelBuilder, NamingConvention.LowerCase);
    }

    private static string? NameRewriter(this string name, NamingConvention naming)
    {
        if (string.IsNullOrEmpty(name)) return name;

        return naming == NamingConvention.SnakeCase
            ? SnakeCaseNameRewriter(name)
            : LowerCaseNameRewriter(name);
    }

    private enum NamingConvention
    {
        SnakeCase,
        LowerCase,
    }

    private static void SetNames(ModelBuilder modelBuilder, NamingConvention naming)
    {
        _culture = CultureInfo.InvariantCulture;

        foreach (var entity in modelBuilder.Model.GetEntityTypes())
        {
            entity.SetViewName(entity.GetViewName()?.NameRewriter(naming));
            entity.SetSchema(entity.GetSchema()?.NameRewriter(naming));
            entity.SetTableName(entity.GetTableName()?.NameRewriter(naming));

            foreach (var property in entity!.GetProperties())
            {
                property.SetColumnName(property.GetColumnName()?.NameRewriter(naming));
            }

            foreach (var key in entity.GetKeys())
            {
                key.SetName(key.GetName()?.NameRewriter(naming));
            }

            foreach (var key in entity.GetForeignKeys())
            {
                key.SetConstraintName(key.GetConstraintName()?.NameRewriter(naming));
            }

            foreach (var index in entity.GetIndexes())
            {
                index.SetDatabaseName(index.GetDatabaseName()?.NameRewriter(naming));
            }
        }
    }

    private static string? LowerCaseNameRewriter(string name)
        => name.ToLower(_culture);

    // https://github.com/efcore/EFCore.NamingConventions/blob/main/EFCore.NamingConventions/Internal/SnakeCaseNameRewriter.cs
    private static string SnakeCaseNameRewriter(string name)
    {
        var builder = new StringBuilder(name.Length + Math.Min(2, name.Length / 5));
        var previousCategory = default(UnicodeCategory?);

        for (var currentIndex = 0; currentIndex < name.Length; currentIndex++)
        {
            var currentChar = name[currentIndex];
            if (currentChar == '_')
            {
                builder.Append('_');
                previousCategory = null;
                continue;
            }

            var currentCategory = char.GetUnicodeCategory(currentChar);
            switch (currentCategory)
            {
                case UnicodeCategory.UppercaseLetter:
                case UnicodeCategory.TitlecaseLetter:
                    if (previousCategory == UnicodeCategory.SpaceSeparator ||
                        previousCategory == UnicodeCategory.LowercaseLetter ||
                        previousCategory != UnicodeCategory.DecimalDigitNumber &&
                        previousCategory != null &&
                        currentIndex > 0 &&
                        currentIndex + 1 < name.Length &&
                        char.IsLower(name[currentIndex + 1]))
                    {
                        builder.Append('_');
                    }

                    currentChar = char.ToLower(currentChar, _culture);
                    break;

                case UnicodeCategory.LowercaseLetter:
                case UnicodeCategory.DecimalDigitNumber:
                    if (previousCategory == UnicodeCategory.SpaceSeparator)
                    {
                        builder.Append('_');
                    }
                    break;

                default:
                    if (previousCategory != null)
                    {
                        previousCategory = UnicodeCategory.SpaceSeparator;
                    }
                    continue;
            }

            builder.Append(currentChar);
            previousCategory = currentCategory;
        }

        return builder.ToString().ToLower(_culture);
    }
}

In your OnModelCreating add the call!

protected override void OnModelCreating(ModelBuilder builder)
{ 
    builder.ToSnakeCaseNames();
   // or 
   //    builder.ToLowerCaseNames();
}

With this you should be able to update your application!

FYI: Remove the dependency on EFCore.NamingConventions temporarily.

If this works fine, what's the point of EFCore.NamingConventions?

HaikAsatryan commented 2 hours ago

@mhosman the whole point is that this NuGet packages ha 28mln+ downloads and is battle tested. @roji Thank you for all your efforts on this project, it's greatly appreciated! 🙏 While we completely understand the workload and the time needed to ensure quality, it’s a bit challenging for production environments to rely on custom builds as a temporary measure. We’re looking forward to the release when you have the chance. Thanks again for all your hard work!