using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using System.Text;
using AMWD.Common.EntityFrameworkCore.Attributes;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
namespace AMWD.Common.EntityFrameworkCore.Extensions
{
///
/// Extensions for the of entity framework core.
///
public static class ModelBuilderExtensions
{
///
/// Applies indices and unique constraints to the properties.
///
/// The database model builder.
/// A reference to this instance after the operation has completed.
[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))
index.SetDatabaseName(indexAttribute.Name.Trim());
}
}
}
return builder;
}
///
/// Converts all table and column names to snake_case_names.
///
/// The database model builder.
/// A reference to this instance after the operation has completed.
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)
entityType.SetTableName(ConvertToSnakeCase(entityType.GetTableName()));
var identifier = StoreObjectIdentifier.Table(entityType.GetTableName(), entityType.GetSchema());
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)
property.SetColumnName(ConvertToSnakeCase(property.GetColumnName(identifier)));
}
}
return builder;
}
///
/// Converts a string to its snake_case equivalent.
///
///
/// Code borrowed from Npgsql.NameTranslation.NpgsqlSnakeCaseNameTranslator.
/// See https://github.com/npgsql/npgsql/blob/f2b2c98f45df6d2a78eec00ae867f18944d717ca/src/Npgsql/NameTranslation/NpgsqlSnakeCaseNameTranslator.cs#L76-L136.
///
/// The value to convert.
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
}
}
}