Skip to content

Instantly share code, notes, and snippets.

@worldspawn
Last active May 2, 2019 05:42
Show Gist options
  • Save worldspawn/9773700c8581b3b458f74ecf6abb2dcf to your computer and use it in GitHub Desktop.
Save worldspawn/9773700c8581b3b458f74ecf6abb2dcf to your computer and use it in GitHub Desktop.
Extensible implementation of ICSharpEntityTypeGenerator. Tested with 2.2.4
public class EntityTypeGenerationContext
{
public EntityTypeGenerationContext(IEntityType entityType, bool useDataAnnotations)
{
EntityType = entityType;
StringBuilder = new IndentedStringBuilder();
UseDataAnnotations = useDataAnnotations;
}
public IEntityType EntityType { get; }
public IndentedStringBuilder StringBuilder { get; }
public bool UseDataAnnotations { get; }
}
public class ExtensibleCSharpEntityTypeGenerator : ICSharpEntityTypeGenerator
{
private readonly ICSharpHelper _code;
public ExtensibleCSharpEntityTypeGenerator(ICSharpHelper cSharpHelper)
{
_code = cSharpHelper;
}
public virtual string WriteCode(
IEntityType entityType,
string @namespace,
bool useDataAnnotations)
{
var context = new EntityTypeGenerationContext(entityType, useDataAnnotations);
GenerateNamespaces(entityType, context);
context.StringBuilder.AppendLine();
context.StringBuilder.AppendLine("namespace " + @namespace);
context.StringBuilder.AppendLine("{");
using (context.StringBuilder.Indent())
GenerateClass(entityType, context);
context.StringBuilder.AppendLine("}");
return context.StringBuilder.ToString();
}
protected virtual void GenerateNamespaces(IEntityType entityType, EntityTypeGenerationContext context)
{
context.StringBuilder.AppendLine("using System;");
context.StringBuilder.AppendLine("using System.Collections.Generic;");
if (context.UseDataAnnotations)
{
context.StringBuilder.AppendLine("using System.ComponentModel.DataAnnotations;");
context.StringBuilder.AppendLine("using System.ComponentModel.DataAnnotations.Schema;");
}
foreach (string str in entityType.GetProperties().SelectMany(p => p.ClrType.GetNamespaces()).Where(ns =>
{
if (ns != "System")
return ns != "System.Collections.Generic";
return false;
}).Distinct().OrderBy(x => x, new NamespaceComparer()))
{
context.StringBuilder.AppendLine("using " + str + ";");
}
}
protected virtual void GenerateClass(IEntityType entityType, EntityTypeGenerationContext context)
{
GenerateEntityTypeDataAnnotations(entityType, context);
context.StringBuilder.AppendLine("public partial class " + entityType.Name);
context.StringBuilder.AppendLine("{");
using (context.StringBuilder.Indent())
{
GenerateConstructor(entityType, context);
GenerateProperties(entityType, context);
GenerateNavigationProperties(entityType, context);
}
context.StringBuilder.AppendLine("}");
}
protected virtual void GenerateEntityTypeDataAnnotations(IEntityType entityType,
EntityTypeGenerationContext context)
{
if (context.UseDataAnnotations)
{
GenerateTableAttribute(entityType, context);
}
}
protected void GenerateTableAttribute(IEntityType entityType, EntityTypeGenerationContext context)
{
var tableName = entityType.Relational().TableName;
var schema = entityType.Relational().Schema;
var defaultSchema = entityType.Model.Relational().DefaultSchema;
var flag = schema != null && schema != defaultSchema;
if ((flag ? 1 : (tableName == null ? 0 : (tableName != entityType.Scaffolding().DbSetName ? 1 : 0))) == 0)
return;
var attributeWriter = new AttributeWriter("TableAttribute");
attributeWriter.AddParameter(_code.Literal(tableName));
if (flag)
attributeWriter.AddParameter("Schema = " + _code.Literal(schema));
context.StringBuilder.AppendLine(attributeWriter.ToString());
}
protected virtual void GenerateConstructor(IEntityType entityType, EntityTypeGenerationContext context)
{
var list = entityType.GetNavigations().Where(n => n.IsCollection()).ToList();
if (list.Count <= 0)
return;
context.StringBuilder.AppendLine("public " + entityType.Name + "()");
context.StringBuilder.AppendLine("{");
using (context.StringBuilder.Indent())
{
foreach (var navigation in list)
{
context.StringBuilder.AppendLine(
navigation.Name + " = new HashSet<" + navigation.GetTargetType().Name + ">();");
}
}
context.StringBuilder.AppendLine("}");
context.StringBuilder.AppendLine();
}
protected virtual void GenerateProperties(IEntityType entityType, EntityTypeGenerationContext context)
{
foreach (var property in entityType.GetProperties().OrderBy(x=> (int)x.GetAnnotations().First(z=>z.Name == "Scaffolding:ColumnOrdinal").Value))
{
GeneratePropertyDataAnnotations(property, context);
GenerateProperty(property, context);
}
}
protected virtual void GenerateProperty(IProperty property, EntityTypeGenerationContext context)
{
context.StringBuilder.AppendLine("public " + _code.Reference(property.ClrType) + " " + property.Name +
" { get; set; }");
}
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected virtual void GeneratePropertyDataAnnotations(IProperty property, EntityTypeGenerationContext context)
{
if (context.UseDataAnnotations)
{
GenerateKeyAttribute(property, context);
GenerateRequiredAttribute(property, context);
GenerateColumnAttribute(property, context);
GenerateMaxLengthAttribute(property, context);
}
}
protected virtual void GenerateKeyAttribute(IProperty property, EntityTypeGenerationContext context)
{
var primaryKey = property.AsProperty(nameof(GenerateKeyAttribute)).PrimaryKey;
if (primaryKey == null || primaryKey.Properties.Count != 1 || primaryKey is Key key &&
primaryKey.Properties.SequenceEqual(
new KeyDiscoveryConvention(null).DiscoverKeyProperties(key.DeclaringEntityType,
key.DeclaringEntityType.GetProperties().ToList())) || primaryKey.Relational().Name !=
ConstraintNamer.GetDefaultName(primaryKey))
return;
context.StringBuilder.AppendLine(new AttributeWriter("KeyAttribute"));
}
protected virtual void GenerateColumnAttribute(IProperty property, EntityTypeGenerationContext context)
{
var columnName = property.Relational().ColumnName;
var configuredColumnType = property.GetConfiguredColumnType();
var parameter = columnName == null || columnName == property.Name ? null : _code.Literal(columnName);
var str = configuredColumnType != null ? _code.Literal(configuredColumnType) : null;
switch (parameter ?? str)
{
case null:
break;
default:
var attributeWriter = new AttributeWriter("ColumnAttribute");
if (parameter != null)
attributeWriter.AddParameter(parameter);
if (str != null)
attributeWriter.AddParameter("TypeName = " + str);
context.StringBuilder.AppendLine(attributeWriter);
break;
}
}
protected virtual void GenerateMaxLengthAttribute(IProperty property, EntityTypeGenerationContext context)
{
var maxLength = property.GetMaxLength();
if (!maxLength.HasValue)
return;
var attributeWriter =
new AttributeWriter(property.ClrType == typeof(string)
? "StringLengthAttribute"
: "MaxLengthAttribute");
attributeWriter.AddParameter(_code.Literal(maxLength.Value));
context.StringBuilder.AppendLine(attributeWriter.ToString());
}
protected virtual void GenerateRequiredAttribute(IProperty property, EntityTypeGenerationContext context)
{
if (property.IsNullable || !IsNullableType(property.ClrType) ||
property.IsPrimaryKey())
return;
context.StringBuilder.AppendLine(new AttributeWriter("RequiredAttribute").ToString());
}
protected static bool IsNullableType(Type type)
{
var typeInfo = type.GetTypeInfo();
if (!typeInfo.IsValueType)
return true;
if (typeInfo.IsGenericType)
return typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>);
return false;
}
protected virtual void GenerateNavigationProperties(IEntityType entityType, EntityTypeGenerationContext context)
{
var source = entityType.GetNavigations().OrderBy(n => !n.IsDependentToPrincipal() ? 1 : 0)
.ThenBy(n => !n.IsCollection() ? 0 : 1);
if (!source.Any())
return;
context.StringBuilder.AppendLine();
foreach (var navigation in source)
{
GenerateNavigationDataAnnotations(navigation, context);
GenerateNavigationProperty(navigation, context);
}
}
protected virtual void GenerateNavigationProperty(INavigation navigation, EntityTypeGenerationContext context)
{
var name = navigation.GetTargetType().Name;
context.StringBuilder.AppendLine((object) ("public virtual " +
(navigation.IsCollection()
? "ICollection<" + name + ">"
: name) + " " + navigation.Name + " { get; set; }"));
}
protected virtual void GenerateNavigationDataAnnotations(INavigation navigation,
EntityTypeGenerationContext context)
{
if (context.UseDataAnnotations)
{
GenerateForeignKeyAttribute(navigation, context);
GenerateInversePropertyAttribute(navigation, context);
}
}
protected virtual void GenerateForeignKeyAttribute(INavigation navigation, EntityTypeGenerationContext context)
{
if (!navigation.IsDependentToPrincipal() || !navigation.ForeignKey.PrincipalKey.IsPrimaryKey())
return;
var attributeWriter = new AttributeWriter("ForeignKeyAttribute");
attributeWriter.AddParameter(_code.Literal(string.Join(",",
navigation.ForeignKey.Properties.Select(p => p.Name))));
context.StringBuilder.AppendLine((object) attributeWriter.ToString());
}
protected virtual void GenerateInversePropertyAttribute(INavigation navigation, EntityTypeGenerationContext context)
{
if (!navigation.ForeignKey.PrincipalKey.IsPrimaryKey())
return;
var inverse = navigation.FindInverse();
if (inverse == null)
return;
var attributeWriter = new AttributeWriter("InversePropertyAttribute");
attributeWriter.AddParameter(_code.Literal(inverse.Name));
context.StringBuilder.AppendLine((object) attributeWriter.ToString());
}
private class AttributeWriter
{
private readonly List<string> _parameters = new List<string>();
private readonly string _attributeName;
public AttributeWriter(string attributeName)
{
_attributeName = attributeName;
}
public void AddParameter(string parameter)
{
_parameters.Add(parameter);
}
public override string ToString()
{
return "[" + (_parameters.Count == 0
? StripAttribute(_attributeName)
: StripAttribute(_attributeName) + "(" + string.Join(", ", _parameters) + ")") + "]";
}
private static string StripAttribute(string attributeName)
{
if (!attributeName.EndsWith("Attribute", StringComparison.Ordinal))
return attributeName;
return attributeName.Substring(0, attributeName.Length - 9);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment