1
0
Files
common/AMWD.Common.EntityFrameworkCore/Extensions/ModelBuilderExtensions.cs
2021-10-22 21:12:32 +02:00

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
}
}
}