From 1a61887da71fcff9df40f6e18200106900edabb8 Mon Sep 17 00:00:00 2001 From: Michael Ganss Date: Thu, 9 Jul 2015 11:54:38 +0200 Subject: [PATCH] Add basic support for Entity Framework Code First --- XmlSchemaClassGenerator.Console/Program.cs | 11 +++-- XmlSchemaClassGenerator.Tests/XmlTests.cs | 1 + XmlSchemaClassGenerator/Generator.cs | 6 +++ .../GeneratorConfiguration.cs | 4 ++ XmlSchemaClassGenerator/TypeModel.cs | 46 +++++++++++++++++++ 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/XmlSchemaClassGenerator.Console/Program.cs b/XmlSchemaClassGenerator.Console/Program.cs index 6bb62bb1..77abba19 100644 --- a/XmlSchemaClassGenerator.Console/Program.cs +++ b/XmlSchemaClassGenerator.Console/Program.cs @@ -24,6 +24,7 @@ static void Main(string[] args) var pclCompatible = false; var enableDataBinding = false; var emitOrder = false; + var entityFramework = false; var options = new OptionSet { { "h|help", "show this message and exit", v => showHelp = v != null }, @@ -35,7 +36,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l { "o|output=", "the {FOLDER} to write the resulting .cs files to", v => outputFolder = v }, { "i|integer=", @"map xs:integer and derived types to {TYPE} instead of string {TYPE} can be i[nt], l[ong], or d[ecimal].", v => { - switch (v) + switch (v) { case "i": case "int": @@ -51,12 +52,13 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l break; } } }, - { "edb|enable-data-binding", "Enable INotifyPropertyChanged data binding", v => enableDataBinding = v != null }, - { "order", "Emit order for all class members stored as XML element", v => emitOrder = v != null }, - { "pcl", "PCL compatible output", v => pclCompatible = v != null }, + { "e|edb|enable-data-binding", "Enable INotifyPropertyChanged data binding", v => enableDataBinding = v != null }, + { "r|order", "Emit order for all class members stored as XML element", v => emitOrder = v != null }, + { "c|pcl", "PCL compatible output", v => pclCompatible = v != null }, { "p|prefix=", "the {PREFIX} to prepend to auto-generated namespace names", v => namespacePrefix = v }, { "v|verbose", "print generated file names on stdout", v => verbose = v != null }, { "0|nullable", "generate nullable adapter properties for optional elements/attributes w/o default values", v => nullables = v != null }, + { "f|ef", "generate Entity Framework Code First compatible classes", v => entityFramework = v != null }, }; var files = options.Parse(args); @@ -89,6 +91,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l EnableDataBinding = enableDataBinding, EmitOrder = emitOrder, IntegerDataType = integerType, + EntityFramework = entityFramework }; if (pclCompatible) diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index 09bffb02..667f18b8 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -45,6 +45,7 @@ private Assembly Compile(string name, string pattern) IntegerDataType = typeof(int), DataAnnotationMode = DataAnnotationMode.Partial, GenerateDesignerCategoryAttribute = false, + EntityFramework = true }; var files = Glob.Glob.ExpandNames(pattern); diff --git a/XmlSchemaClassGenerator/Generator.cs b/XmlSchemaClassGenerator/Generator.cs index 4d1f525d..d448392f 100644 --- a/XmlSchemaClassGenerator/Generator.cs +++ b/XmlSchemaClassGenerator/Generator.cs @@ -121,6 +121,12 @@ public Type IntegerDataType set { _configuration.IntegerDataType = value; } } + public bool EntityFramework + { + get { return _configuration.EntityFramework; } + set { _configuration.EntityFramework = value; } + } + private readonly XmlSchemaSet Set = new XmlSchemaSet(); private Dictionary AttributeGroups; private readonly Dictionary Namespaces = new Dictionary(); diff --git a/XmlSchemaClassGenerator/GeneratorConfiguration.cs b/XmlSchemaClassGenerator/GeneratorConfiguration.cs index c5e90ba1..7eb1d994 100644 --- a/XmlSchemaClassGenerator/GeneratorConfiguration.cs +++ b/XmlSchemaClassGenerator/GeneratorConfiguration.cs @@ -97,6 +97,10 @@ public GeneratorConfiguration() /// Default data type for numeric fields /// public Type IntegerDataType { get; set; } + /// + /// Generate Entity Framework Code First compatible classes + /// + public bool EntityFramework { get; set; } /// /// Provides a fast and safe way to write to the Log diff --git a/XmlSchemaClassGenerator/TypeModel.cs b/XmlSchemaClassGenerator/TypeModel.cs index 1b4810ba..77b6a1ee 100644 --- a/XmlSchemaClassGenerator/TypeModel.cs +++ b/XmlSchemaClassGenerator/TypeModel.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics; using System.Linq; using System.Reflection; @@ -260,6 +261,28 @@ public override CodeTypeDeclaration Generate() if (EnableDataBinding) classDeclaration.BaseTypes.Add(new CodeTypeReference("INotifyPropertyChanged")); + if (Configuration.EntityFramework && (BaseClass == null || !(BaseClass is ClassModel))) + { + // generate key + var keyProperty = Properties.FirstOrDefault(p => p.Name.ToLowerInvariant() == "id") + ?? Properties.FirstOrDefault(p => p.Name.ToLowerInvariant() == (Name.ToLowerInvariant() + "id")); + + if (keyProperty == null) + { + keyProperty = new PropertyModel(Configuration) + { + Name = "Id", + Type = new SimpleModel(Configuration) { ValueType = typeof(long) }, + OwningType = this, + Documentation = { new DocumentationModel { Language = "en", Text = "Gets or sets a value uniquely identifying this entity." }, + new DocumentationModel { Language = "de", Text = "Ruft einen Wert ab, der diese Entität eindeutig identifiziert, oder legt diesen fest." } } + }; + Properties.Insert(0, keyProperty); + } + + keyProperty.IsKey = true; + } + foreach (var property in Properties) property.AddMembersTo(classDeclaration, EnableDataBinding); @@ -347,6 +370,7 @@ public class PropertyModel public XmlQualifiedName XmlSchemaName { get; set; } public bool IsAny { get; set; } public int? Order { get; set; } + public bool IsKey { get; set; } public GeneratorConfiguration Configuration { get; private set; } public PropertyModel(GeneratorConfiguration configuration) @@ -465,6 +489,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi Attributes = MemberAttributes.Private }; var ignoreAttribute = new CodeAttributeDeclaration(new CodeTypeReference(typeof(XmlIgnoreAttribute))); + var notMappedAttribute = new CodeAttributeDeclaration(new CodeTypeReference(typeof(NotMappedAttribute))); backingField.CustomAttributes.Add(ignoreAttribute); if (requiresBackingField) @@ -534,6 +559,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi var specifiedName = Configuration.GenerateNullables ? Name + "Value" : Name; var specifiedMember = new CodeMemberField(typeof(bool), specifiedName + "Specified { get; set; }"); specifiedMember.CustomAttributes.Add(ignoreAttribute); + if (Configuration.EntityFramework && Configuration.GenerateNullables) specifiedMember.CustomAttributes.Add(notMappedAttribute); specifiedMember.Attributes = MemberAttributes.Public; var specifiedDocs = new[] { new DocumentationModel { Language = "en", Text = string.Format("Gets or sets a value indicating whether the {0} property is specified.", Name) }, new DocumentationModel { Language = "de", Text = string.Format("Ruft einen Wert ab, der angibt, ob die {0}-Eigenschaft spezifiziert ist, oder legt diesen fest.", Name) } }; @@ -607,6 +633,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi editorBrowsableAttribute.Arguments.Add(new CodeAttributeArgument(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(EditorBrowsableState)), "Never"))); specifiedMember.CustomAttributes.Add(editorBrowsableAttribute); member.CustomAttributes.Add(editorBrowsableAttribute); + if (Configuration.EntityFramework) member.CustomAttributes.Add(notMappedAttribute); } } else if ((IsCollection || isArray) && IsNullable && !IsAttribute) @@ -619,6 +646,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi HasGet = true, }; specifiedProperty.CustomAttributes.Add(ignoreAttribute); + if (Configuration.EntityFramework) specifiedProperty.CustomAttributes.Add(notMappedAttribute); specifiedProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final; var listReference = new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), Name); @@ -665,11 +693,29 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi propertyAttribute.Arguments.Cast().Where(x => !string.Equals(x.Name, "Order", StringComparison.Ordinal)).ToArray()); member.CustomAttributes.Add(arrayItemAttribute); } + + if (IsKey) + { + var keyAttribute = new CodeAttributeDeclaration(new CodeTypeReference(typeof(KeyAttribute))); + member.CustomAttributes.Add(keyAttribute); + } + + if (IsAny && Configuration.EntityFramework) + { + member.CustomAttributes.Add(notMappedAttribute); + } } private CodeAttributeDeclaration GetAttribute(bool isArray) { CodeAttributeDeclaration attribute; + + if (IsKey && XmlSchemaName == null) + { + attribute = new CodeAttributeDeclaration(new CodeTypeReference(typeof(XmlIgnoreAttribute))); + return attribute; + } + if (IsAttribute) { if (IsAny)