Last active
May 2, 2019 05:42
-
-
Save worldspawn/9773700c8581b3b458f74ecf6abb2dcf to your computer and use it in GitHub Desktop.
Extensible implementation of ICSharpEntityTypeGenerator. Tested with 2.2.4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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