163 lines
4.7 KiB
C#
163 lines
4.7 KiB
C#
using System.ComponentModel.DataAnnotations.Schema;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using AMWD.Common.EntityFrameworkCore.Attributes;
|
|
using Microsoft.EntityFrameworkCore;
|
|
#if NET5_0_OR_GREATER
|
|
using Microsoft.EntityFrameworkCore.Metadata;
|
|
#endif
|
|
|
|
namespace AMWD.Common.EntityFrameworkCore.Extensions
|
|
{
|
|
/// <summary>
|
|
/// Extensions for the <see cref="ModelBuilder"/> of entity framework core.
|
|
/// </summary>
|
|
public static class ModelBuilderExtensions
|
|
{
|
|
/// <summary>
|
|
/// Applies indices and unique constraints to the properties.
|
|
/// </summary>
|
|
/// <param name="builder">The database model builder.</param>
|
|
/// <returns>A reference to this instance after the operation has completed.</returns>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0019", Justification = "No pattern comparison in this case due to readability.")]
|
|
public static ModelBuilder ApplyIndexAttributes(this ModelBuilder builder)
|
|
{
|
|
foreach (var entityType in builder.Model.GetEntityTypes())
|
|
{
|
|
foreach (var property in entityType.GetProperties())
|
|
{
|
|
var indexAttribute = entityType.ClrType
|
|
.GetProperty(property.Name)
|
|
?.GetCustomAttribute(typeof(DatabaseIndexAttribute), false) as DatabaseIndexAttribute;
|
|
if (indexAttribute != null)
|
|
{
|
|
var index = entityType.AddIndex(property);
|
|
index.IsUnique = indexAttribute.IsUnique;
|
|
|
|
if (!string.IsNullOrWhiteSpace(indexAttribute.Name))
|
|
{
|
|
#if NET5_0_OR_GREATER
|
|
index.SetDatabaseName(indexAttribute.Name.Trim());
|
|
#else
|
|
index.SetName(indexAttribute.Name.Trim());
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return builder;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts all table and column names to snake_case_names.
|
|
/// </summary>
|
|
/// <param name="builder">The database model builder.</param>
|
|
/// <returns>A reference to this instance after the operation has completed.</returns>
|
|
public static ModelBuilder ApplySnakeCase(this ModelBuilder builder)
|
|
{
|
|
foreach (var entityType in builder.Model.GetEntityTypes())
|
|
{
|
|
// skip conversion when table name is explicitly set
|
|
if ((entityType.ClrType.GetCustomAttribute(typeof(TableAttribute), false) as TableAttribute) == null)
|
|
{
|
|
#if NET5_0_OR_GREATER
|
|
entityType.SetTableName(ConvertToSnakeCase(entityType.GetTableName()));
|
|
#else
|
|
entityType.SetTableName(ConvertToSnakeCase(entityType.GetTableName()));
|
|
#endif
|
|
}
|
|
|
|
#if NET5_0_OR_GREATER
|
|
var identifier = StoreObjectIdentifier.Table(entityType.GetTableName(), entityType.GetSchema());
|
|
#endif
|
|
foreach (var property in entityType.GetProperties())
|
|
{
|
|
// skip conversion when column name is explicitly set
|
|
if ((entityType.ClrType.GetProperty(property.Name)?.GetCustomAttribute(typeof(ColumnAttribute), false) as ColumnAttribute) == null)
|
|
{
|
|
#if NET5_0_OR_GREATER
|
|
property.SetColumnName(ConvertToSnakeCase(property.GetColumnName(identifier)));
|
|
#else
|
|
property.SetColumnName(ConvertToSnakeCase(property.GetColumnName()));
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
return builder;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a string to its snake_case equivalent.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Code borrowed from Npgsql.NameTranslation.NpgsqlSnakeCaseNameTranslator.
|
|
/// See https://github.com/npgsql/npgsql/blob/f2b2c98f45df6d2a78eec00ae867f18944d717ca/src/Npgsql/NameTranslation/NpgsqlSnakeCaseNameTranslator.cs#L76-L136.
|
|
/// </remarks>
|
|
/// <param name="value">The value to convert.</param>
|
|
private static string ConvertToSnakeCase(string value)
|
|
{
|
|
var sb = new StringBuilder();
|
|
var state = SnakeCaseState.Start;
|
|
|
|
for (int i = 0; i < value.Length; i++)
|
|
{
|
|
if (value[i] == ' ')
|
|
{
|
|
if (state != SnakeCaseState.Start)
|
|
state = SnakeCaseState.NewWord;
|
|
}
|
|
else if (char.IsUpper(value[i]))
|
|
{
|
|
switch (state)
|
|
{
|
|
case SnakeCaseState.Upper:
|
|
bool hasNext = (i + 1 < value.Length);
|
|
if (i > 0 && hasNext)
|
|
{
|
|
char nextChar = value[i + 1];
|
|
if (!char.IsUpper(nextChar) && nextChar != '_')
|
|
{
|
|
sb.Append('_');
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SnakeCaseState.Lower:
|
|
case SnakeCaseState.NewWord:
|
|
sb.Append('_');
|
|
break;
|
|
}
|
|
|
|
sb.Append(char.ToLowerInvariant(value[i]));
|
|
state = SnakeCaseState.Upper;
|
|
}
|
|
else if (value[i] == '_')
|
|
{
|
|
sb.Append('_');
|
|
state = SnakeCaseState.Start;
|
|
}
|
|
else
|
|
{
|
|
if (state == SnakeCaseState.NewWord)
|
|
sb.Append('_');
|
|
|
|
sb.Append(value[i]);
|
|
state = SnakeCaseState.Lower;
|
|
}
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private enum SnakeCaseState
|
|
{
|
|
Start,
|
|
Lower,
|
|
Upper,
|
|
NewWord
|
|
}
|
|
}
|
|
}
|