diff --git a/README.md b/README.md index 9f5006d01..9cb9ce69e 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,18 @@ to +
2.0.5.5 diff --git a/VSMarketplace blurb.md b/VSMarketplace blurb.md index de6ed236b..3580803a4 100644 --- a/VSMarketplace blurb.md +++ b/VSMarketplace blurb.md @@ -30,6 +30,10 @@ For comprehensive documentation, please visit [the project's documentation site] **ChangeLog** +**2.0.5.7** + - **[NEW]** Added ability to select tabs or spaces for indentation in generated code (Tools/Options/Entity Framework Visual Editor/Visual Editor Options) (See https://github.com/msawczyn/EFDesigner/issues/221) + - Fixed an issue with changing visual grid size on design surface. + **2.0.5.6** - The project item templates for the model file had wandered away. They're back again. (See https://github.com/msawczyn/EFDesigner/issues/216) - Fixed a problem with existing models where class-level "AutoProperty: false" caused bad code generation. (See https://github.com/msawczyn/EFDesigner/issues/215) diff --git a/changelog.txt b/changelog.txt index 581efc8d9..d84b9723f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +2.0.5.7 + - Added ability to select tabs or spaces for indentation in generated code (Tools/Options/Entity Framework Visual Editor/Visual Editor Options) (See https://github.com/msawczyn/EFDesigner/issues/221) + - Fixed an issue with changing visual grid size on design surface. + 2.0.5.6 - The project item templates for the model file had wandered away. They're back again. (See https://github.com/msawczyn/EFDesigner/issues/216) - Fixed a problem with existing models where class-level "AutoProperty: false" caused bad code generation. (See https://github.com/msawczyn/EFDesigner/issues/215) diff --git a/dist/Sawczyn.EFDesigner.EFModel.DslPackage.vsix b/dist/Sawczyn.EFDesigner.EFModel.DslPackage.vsix index 846e86504..a0bfb9ec3 100644 Binary files a/dist/Sawczyn.EFDesigner.EFModel.DslPackage.vsix and b/dist/Sawczyn.EFDesigner.EFModel.DslPackage.vsix differ diff --git a/src/Dsl/CustomCode/Partials/EFModelDiagram.cs b/src/Dsl/CustomCode/Partials/EFModelDiagram.cs index 5e1ba06c1..69e394b72 100644 --- a/src/Dsl/CustomCode/Partials/EFModelDiagram.cs +++ b/src/Dsl/CustomCode/Partials/EFModelDiagram.cs @@ -29,8 +29,6 @@ public override void OnInitialize() ShowGrid = modelRoot?.ShowGrid ?? true; GridColor = modelRoot?.GridColor ?? Color.Gainsboro; SnapToGrid = modelRoot?.SnapToGrid ?? true; - GridSize = modelRoot?.GridSize ?? 0.125; - } /// diff --git a/src/Dsl/CustomCode/Partials/ModelRoot.cs b/src/Dsl/CustomCode/Partials/ModelRoot.cs index 3d17d8d64..1641147c7 100644 --- a/src/Dsl/CustomCode/Partials/ModelRoot.cs +++ b/src/Dsl/CustomCode/Partials/ModelRoot.cs @@ -27,6 +27,8 @@ public partial class ModelRoot: IHasStore public static Func WriteDiagramAsBinary = () => false; + public static Func UseTabs = () => false; + static ModelRoot() { try @@ -42,7 +44,7 @@ static ModelRoot() // ReSharper disable once UnusedMember.Global public string FullName => string.IsNullOrWhiteSpace(Namespace) ? $"global::{EntityContainerName}" : $"global::{Namespace}.{EntityContainerName}"; - public bool IsEFCore5Plus => EntityFrameworkVersion == EFVersion.EFCore && GetEntityFrameworkPackageVersionNum() >= 5; + public bool IsEFCore5Plus => EntityFrameworkVersion == EFVersion.EFCore && (EntityFrameworkPackageVersion == "Latest" || GetEntityFrameworkPackageVersionNum() >= 5); [Obsolete("Use ModelRoot.Classes instead")] public LinkedElementCollection Types => Classes; @@ -57,6 +59,22 @@ public EFModelDiagram[] GetDiagrams() .ToArray(); } + #region Filename + + private string filename; + + public void SetFileName(string fileName) + { + filename = fileName; + } + + public string GetFileName() + { + return filename; + } + + #endregion + #region OutputLocations private OutputLocations outputLocationsStorage; @@ -271,8 +289,9 @@ public NuGetDisplay NuGetPackageVersion { get { - return NuGetHelper.NuGetPackageDisplay.FirstOrDefault(x => x.EFVersion == EntityFrameworkVersion && - x.DisplayVersion == EntityFrameworkPackageVersion); + return NuGetHelper.NuGetPackageDisplay + .FirstOrDefault(x => x.EFVersion == EntityFrameworkVersion + && x.DisplayVersion == EntityFrameworkPackageVersion); } } @@ -479,17 +498,5 @@ protected override void OnValueChanged(ModelRoot element, string oldValue, strin } #endregion Namespace tracking property - - private string filename; - - public void SetFileName(string fileName) - { - filename = fileName; - } - - public string GetFileName() - { - return filename; - } } } diff --git a/src/Dsl/CustomCode/Rules/ModelAttributeChangeRules.cs b/src/Dsl/CustomCode/Rules/ModelAttributeChangeRules.cs index 4d0857c82..7a4a9f48e 100644 --- a/src/Dsl/CustomCode/Rules/ModelAttributeChangeRules.cs +++ b/src/Dsl/CustomCode/Rules/ModelAttributeChangeRules.cs @@ -35,7 +35,7 @@ public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) if (Equals(e.NewValue, e.OldValue)) return; - List errorMessages = EFCoreValidator.GetErrors(element).ToList(); + List errorMessages = new List(); switch (e.DomainProperty.Name) { diff --git a/src/Dsl/CustomCode/Rules/ModelClassChangeRules.cs b/src/Dsl/CustomCode/Rules/ModelClassChangeRules.cs index 102b18b2e..927462b66 100644 --- a/src/Dsl/CustomCode/Rules/ModelClassChangeRules.cs +++ b/src/Dsl/CustomCode/Rules/ModelClassChangeRules.cs @@ -28,7 +28,7 @@ public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) if (Equals(e.NewValue, e.OldValue)) return; - List errorMessages = EFCoreValidator.GetErrors(element).ToList(); + List errorMessages = new List(); switch (e.DomainProperty.Name) { diff --git a/src/Dsl/CustomCode/Rules/ModelRootChangeRules.cs b/src/Dsl/CustomCode/Rules/ModelRootChangeRules.cs index c04d9e1fe..5dd041363 100644 --- a/src/Dsl/CustomCode/Rules/ModelRootChangeRules.cs +++ b/src/Dsl/CustomCode/Rules/ModelRootChangeRules.cs @@ -1,4 +1,5 @@ -using System.CodeDom.Compiler; +using System; +using System.CodeDom.Compiler; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -125,14 +126,6 @@ public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) break; - case "GridSize": - foreach (EFModelDiagram diagram in element.GetDiagrams()) - diagram.GridSize = (double)e.NewValue/10.0; - - redraw = true; - - break; - case "InheritanceStrategy": if ((element.EntityFrameworkVersion == EFVersion.EFCore) && (element.NuGetPackageVersion.MajorMinorVersionNum < 2.1)) diff --git a/src/Dsl/CustomCode/Type Descriptors/AssociationTypeDescriptor.cs b/src/Dsl/CustomCode/Type Descriptors/AssociationTypeDescriptor.cs index 4d78361e1..57c346ad8 100644 --- a/src/Dsl/CustomCode/Type Descriptors/AssociationTypeDescriptor.cs +++ b/src/Dsl/CustomCode/Type Descriptors/AssociationTypeDescriptor.cs @@ -25,8 +25,6 @@ private PropertyDescriptorCollection GetCustomProperties(Attribute[] attributes) { storeDomainDataDirectory = association.Store.DomainDataDirectory; - EFCoreValidator.AdjustEFCoreProperties(propertyDescriptors, association); - // show FKPropertyName only when possible and required if (!association.Source.ModelRoot.ExposeForeignKeys || (association.SourceRole != EndpointRole.Dependent && association.TargetRole != EndpointRole.Dependent)) diff --git a/src/Dsl/CustomCode/Type Descriptors/ModelClassTypeDescriptor.cs b/src/Dsl/CustomCode/Type Descriptors/ModelClassTypeDescriptor.cs index 0c9b2a9f5..f350f44dc 100644 --- a/src/Dsl/CustomCode/Type Descriptors/ModelClassTypeDescriptor.cs +++ b/src/Dsl/CustomCode/Type Descriptors/ModelClassTypeDescriptor.cs @@ -22,12 +22,8 @@ private PropertyDescriptorCollection GetCustomProperties(Attribute[] attributes) if (ModelElement is ModelClass modelClass) { - EFCoreValidator.AdjustEFCoreProperties(propertyDescriptors, modelClass); - storeDomainDataDirectory = modelClass.Store.DomainDataDirectory; - - //Add the descriptors for the tracking properties propertyDescriptors.Add(new TrackingPropertyDescriptor(modelClass diff --git a/src/Dsl/CustomCode/Type Descriptors/ModelEnumTypeDescriptor.cs b/src/Dsl/CustomCode/Type Descriptors/ModelEnumTypeDescriptor.cs index ccc713fe4..a4eba79c0 100644 --- a/src/Dsl/CustomCode/Type Descriptors/ModelEnumTypeDescriptor.cs +++ b/src/Dsl/CustomCode/Type Descriptors/ModelEnumTypeDescriptor.cs @@ -23,8 +23,6 @@ private PropertyDescriptorCollection GetCustomProperties(Attribute[] attributes) { storeDomainDataDirectory = modelEnum.Store.DomainDataDirectory; - EFCoreValidator.AdjustEFCoreProperties(propertyDescriptors, modelEnum); - //Add the descriptors for the tracking properties propertyDescriptors.Add(new TrackingPropertyDescriptor(modelEnum diff --git a/src/Dsl/CustomCode/Type Descriptors/ModelRootTypeDescriptor.cs b/src/Dsl/CustomCode/Type Descriptors/ModelRootTypeDescriptor.cs index 61c6fc50e..0480e7c71 100644 --- a/src/Dsl/CustomCode/Type Descriptors/ModelRootTypeDescriptor.cs +++ b/src/Dsl/CustomCode/Type Descriptors/ModelRootTypeDescriptor.cs @@ -17,6 +17,11 @@ private PropertyDescriptorCollection GetCustomProperties(Attribute[] attributes) { EFCoreValidator.AdjustEFCoreProperties(propertyDescriptors, modelRoot); + if (!modelRoot.ShowGrid) + { + propertyDescriptors.Remove("GridColor"); + } + //Add in extra custom properties here... } diff --git a/src/Dsl/CustomCode/Utilities/EFCoreValidator.cs b/src/Dsl/CustomCode/Utilities/EFCoreValidator.cs index b0e40032b..9dd48eca4 100644 --- a/src/Dsl/CustomCode/Utilities/EFCoreValidator.cs +++ b/src/Dsl/CustomCode/Utilities/EFCoreValidator.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; + using Microsoft.VisualStudio.Modeling; using Sawczyn.EFDesigner.EFModel.Extensions; + #pragma warning disable 1591 namespace Sawczyn.EFDesigner.EFModel @@ -13,95 +15,8 @@ namespace Sawczyn.EFDesigner.EFModel /// public static class EFCoreValidator { - #region ModelClass - - public static IEnumerable GetErrors(ModelClass element) - { - return new string[0]; - - // for later - - //ModelRoot modelRoot = element.ModelRoot; - //Store store = modelRoot.Store; - //List errorMessages = new List(); - - //if (modelRoot.EntityFrameworkVersion == EFVersion.EFCore) - //{ - - //} - - //return errorMessages; - } - - public static void AdjustEFCoreProperties(PropertyDescriptorCollection propertyDescriptors, ModelClass element) - { - //ModelRoot modelRoot = element.ModelRoot; - //for (int index = 0; index < propertyDescriptors.Count; index++) - //{ - // bool shouldRemove = false; - // switch (propertyDescriptors[index].Name) - // { - // } - - // if (shouldRemove) - // propertyDescriptors.Remove(propertyDescriptors[index--]); - //} - } - - #endregion ModelClass - - #region ModelEnum - - public static IEnumerable GetErrors(ModelEnum element) - { - return new string[0]; - - // for later - - //ModelRoot modelRoot = element.ModelRoot; - //Store store = modelRoot.Store; - //List errorMessages = new List(); - - //if (modelRoot.EntityFrameworkVersion == EFVersion.EFCore) - //{ - - //} - - //return errorMessages; - } - - public static void AdjustEFCoreProperties(PropertyDescriptorCollection propertyDescriptors, ModelEnum element) - { - //ModelRoot modelRoot = element.ModelRoot; - //for (int index = 0; index < propertyDescriptors.Count; index++) - //{ - // bool shouldRemove = false; - // switch (propertyDescriptors[index].Name) - // { - // } - - // if (shouldRemove) - // propertyDescriptors.Remove(propertyDescriptors[index--]); - //} - } - - #endregion ModelEnum - #region ModelAttribute - public static IEnumerable GetErrors(ModelAttribute element) - { - // ReSharper disable once CollectionNeverUpdated.Local - List errorMessages = new List(); - - // now handled at ModelRoot.ValidTypes - //ModelRoot modelRoot = element.ModelClass?.ModelRoot; - //if (!modelRoot.ValidTypes.Contains(element.Type)) - // errorMessages.Add($"{element.Type} {element.ModelClass.Name}.{element.Name}: Unsupported type"); - - return errorMessages; - } - public static void AdjustEFCoreProperties(PropertyDescriptorCollection propertyDescriptors, ModelAttribute element) { ModelRoot modelRoot = element.ModelClass.ModelRoot; @@ -114,6 +29,7 @@ public static void AdjustEFCoreProperties(PropertyDescriptorCollection propertyD { case "PersistencePoint": shouldRemove = modelRoot.EntityFrameworkVersion == EFVersion.EF6; + break; // add more as needed @@ -133,35 +49,15 @@ public static IEnumerable GetErrors(Association element) ModelRoot modelRoot = element.Store.ModelRoot(); List errorMessages = new List(); - if (modelRoot?.EntityFrameworkVersion > EFVersion.EF6) + if (modelRoot?.EntityFrameworkVersion == EFVersion.EFCore && modelRoot?.IsEFCore5Plus == false) { - if ((element.SourceMultiplicity == Multiplicity.ZeroMany) && - (element.TargetMultiplicity == Multiplicity.ZeroMany)) + if ((element.SourceMultiplicity == Multiplicity.ZeroMany) && (element.TargetMultiplicity == Multiplicity.ZeroMany)) errorMessages.Add($"EFCore does not support many-to-many associations (found one between {element.Source.Name} and {element.Target.Name})"); } return errorMessages; } - public static void AdjustEFCoreProperties(PropertyDescriptorCollection propertyDescriptors, Association element) - { - //ModelRoot modelRoot = element.Source.ModelRoot; - - //for (int index = 0; index < propertyDescriptors.Count; index++) - //{ - // bool shouldRemove = false; - // switch (propertyDescriptors[index].Name) - // { - - // default: - // break; - // } - - // if (shouldRemove) - // propertyDescriptors.Remove(propertyDescriptors[index--]); - //} - } - #endregion Association #region ModelRoot @@ -175,23 +71,12 @@ public static IEnumerable GetErrors(ModelRoot element) foreach (Association association in store.GetAll().ToList()) errorMessages.AddRange(GetErrors(association)); - foreach (ModelClass modelClass in store.GetAll().ToList()) - { - errorMessages.AddRange(GetErrors(modelClass)); - - foreach (ModelAttribute modelAttribute in modelClass.Attributes) - errorMessages.AddRange(GetErrors(modelAttribute)); - } - - foreach (ModelEnum modelEnum in store.GetAll().ToList()) - errorMessages.AddRange(GetErrors(modelEnum)); - return errorMessages; } /// - /// Called by TypeDescriptors to determine what should be shown in a property editor. Removing a property hides - /// it from the property editor in Visual Studio, nothing more. + /// Called by TypeDescriptors to determine what should be shown in a property editor. Removing a property hides + /// it from the property editor in Visual Studio, nothing more. /// /// /// @@ -202,40 +87,45 @@ public static void AdjustEFCoreProperties(PropertyDescriptorCollection propertyD for (int index = 0; index < propertyDescriptors.Count; index++) { bool shouldRemove = false; + switch (propertyDescriptors[index].Name) { case "DatabaseInitializerType": shouldRemove = modelRoot.EntityFrameworkVersion == EFVersion.EFCore; + break; case "AutomaticMigrationsEnabled": shouldRemove = modelRoot.EntityFrameworkVersion == EFVersion.EFCore; + break; case "ProxyGenerationEnabled": shouldRemove = modelRoot.EntityFrameworkVersion == EFVersion.EFCore; + break; case "DatabaseType": shouldRemove = modelRoot.EntityFrameworkVersion == EFVersion.EFCore; + break; case "InheritanceStrategy": - shouldRemove = modelRoot.EntityFrameworkVersion == EFVersion.EFCore; + shouldRemove = modelRoot.EntityFrameworkVersion == EFVersion.EFCore && !modelRoot.IsEFCore5Plus; + break; case "LazyLoadingEnabled": shouldRemove = modelRoot.EntityFrameworkVersion == EFVersion.EFCore && modelRoot.GetEntityFrameworkPackageVersionNum() < 2.1; + break; } if (shouldRemove) propertyDescriptors.Remove(propertyDescriptors[index--]); } - } #endregion ModelRoot - } -} +} \ No newline at end of file diff --git a/src/Dsl/Dsl.GeneratedMSBuildEditorConfig.editorconfig b/src/Dsl/Dsl.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 000000000..89c8e35d0 --- /dev/null +++ b/src/Dsl/Dsl.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,8 @@ +is_global = true +build_property.TargetFramework = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.PublishSingleFile = +build_property.IncludeAllContentForSelfExtract = +build_property._SupportedPlatformList = diff --git a/src/Dsl/DslDefinition.dsl b/src/Dsl/DslDefinition.dsl index 63562b738..0cb306586 100644 --- a/src/Dsl/DslDefinition.dsl +++ b/src/Dsl/DslDefinition.dsl @@ -1,5 +1,5 @@  - + @@ -300,11 +300,6 @@ - - - - - @@ -1995,9 +1990,6 @@ - - - diff --git a/src/Dsl/DslDefinition.dsl.diagram b/src/Dsl/DslDefinition.dsl.diagram index 8cac7d8f5..98aa98a74 100644 --- a/src/Dsl/DslDefinition.dsl.diagram +++ b/src/Dsl/DslDefinition.dsl.diagram @@ -4,18 +4,18 @@ - + - + - + - + @@ -36,14 +36,14 @@ - + - + - + @@ -95,16 +95,16 @@ - + - + - + - + @@ -113,10 +113,10 @@ - + - + @@ -125,16 +125,16 @@ - + - + - + - + @@ -164,40 +164,40 @@ - + - + - + - + - + - + - + - + - + - + - + @@ -230,34 +230,34 @@ - + - + - + - + - + - + - + - + - + - + @@ -333,61 +333,61 @@ - + - + - + - + - + - + - + - + - + @@ -400,25 +400,25 @@ - + - + - + - + @@ -430,28 +430,28 @@ - + - + - + - + @@ -464,49 +464,49 @@ - + - + - + - + - + - + - + @@ -519,26 +519,26 @@ - + - + - + - + @@ -551,54 +551,54 @@ - + - + - + - + - + - + - + - + diff --git a/src/Dsl/GeneratedCode/DomainClasses.cs b/src/Dsl/GeneratedCode/DomainClasses.cs index 670ff6110..6dc339c8c 100644 --- a/src/Dsl/GeneratedCode/DomainClasses.cs +++ b/src/Dsl/GeneratedCode/DomainClasses.cs @@ -3621,94 +3621,6 @@ public override sealed void SetValue(ModelRoot element, global::System.Drawing.C } } - #endregion - #region GridSize domain property code - - /// - /// GridSize domain property Id. - /// - public static readonly global::System.Guid GridSizeDomainPropertyId = new global::System.Guid(0xe4121eca, 0x2a99, 0x4d3c, 0x81, 0x59, 0x17, 0x84, 0xfd, 0xb7, 0x2d, 0x28); - - /// - /// Storage for GridSize - /// - private global::System.Int16 gridSizePropertyStorage; - - /// - /// Gets or sets the value of GridSize domain property. - /// Size of display grid units, in inches - /// - [DslDesign::DisplayNameResource("Sawczyn.EFDesigner.EFModel.ModelRoot/GridSize.DisplayName", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] - [DslDesign::CategoryResource("Sawczyn.EFDesigner.EFModel.ModelRoot/GridSize.Category", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] - [DslDesign::DescriptionResource("Sawczyn.EFDesigner.EFModel.ModelRoot/GridSize.Description", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] - [DslModeling::DomainObjectId("e4121eca-2a99-4d3c-8159-1784fdb72d28")] - public global::System.Int16 GridSize - { - [global::System.Diagnostics.DebuggerStepThrough] - get - { - return gridSizePropertyStorage; - } - [global::System.Diagnostics.DebuggerStepThrough] - set - { - GridSizePropertyHandler.Instance.SetValue(this, value); - } - } - /// - /// Value handler for the ModelRoot.GridSize domain property. - /// - internal sealed partial class GridSizePropertyHandler : DslModeling::DomainPropertyValueHandler - { - private GridSizePropertyHandler() { } - - /// - /// Gets the singleton instance of the ModelRoot.GridSize domain property value handler. - /// - public static readonly GridSizePropertyHandler Instance = new GridSizePropertyHandler(); - - /// - /// Gets the Id of the ModelRoot.GridSize domain property. - /// - public sealed override global::System.Guid DomainPropertyId - { - [global::System.Diagnostics.DebuggerStepThrough] - get - { - return GridSizeDomainPropertyId; - } - } - - /// - /// Gets a strongly-typed value of the property on specified element. - /// - /// Element which owns the property. - /// Property value. - public override sealed global::System.Int16 GetValue(ModelRoot element) - { - if (element == null) throw new global::System.ArgumentNullException("element"); - return element.gridSizePropertyStorage; - } - - /// - /// Sets property value on an element. - /// - /// Element which owns the property. - /// New property value. - public override sealed void SetValue(ModelRoot element, global::System.Int16 newValue) - { - if (element == null) throw new global::System.ArgumentNullException("element"); - - global::System.Int16 oldValue = GetValue(element); - if (newValue != oldValue) - { - ValueChanging(element, oldValue, newValue); - element.gridSizePropertyStorage = newValue; - ValueChanged(element, oldValue, newValue); - } - } - } - #endregion #region ShowForeignKeyPropertyNames domain property code @@ -11474,6 +11386,6 @@ namespace Sawczyn.EFDesigner.EFModel { partial class ModelRoot { - public const string DSLVersion = "2.0.5.6"; + public const string DSLVersion = "2.0.5.7"; } } diff --git a/src/Dsl/GeneratedCode/DomainModel.cs b/src/Dsl/GeneratedCode/DomainModel.cs index 8977f87fc..5a035b74b 100644 --- a/src/Dsl/GeneratedCode/DomainModel.cs +++ b/src/Dsl/GeneratedCode/DomainModel.cs @@ -156,7 +156,6 @@ protected sealed override DomainMemberInfo[] GetGeneratedDomainProperties() new DomainMemberInfo(typeof(ModelRoot), "ShowGrid", ModelRoot.ShowGridDomainPropertyId, typeof(ModelRoot.ShowGridPropertyHandler)), new DomainMemberInfo(typeof(ModelRoot), "SnapToGrid", ModelRoot.SnapToGridDomainPropertyId, typeof(ModelRoot.SnapToGridPropertyHandler)), new DomainMemberInfo(typeof(ModelRoot), "GridColor", ModelRoot.GridColorDomainPropertyId, typeof(ModelRoot.GridColorPropertyHandler)), - new DomainMemberInfo(typeof(ModelRoot), "GridSize", ModelRoot.GridSizeDomainPropertyId, typeof(ModelRoot.GridSizePropertyHandler)), new DomainMemberInfo(typeof(ModelRoot), "ShowForeignKeyPropertyNames", ModelRoot.ShowForeignKeyPropertyNamesDomainPropertyId, typeof(ModelRoot.ShowForeignKeyPropertyNamesPropertyHandler)), new DomainMemberInfo(typeof(ModelClass), "IsAbstract", ModelClass.IsAbstractDomainPropertyId, typeof(ModelClass.IsAbstractPropertyHandler)), new DomainMemberInfo(typeof(ModelClass), "TableName", ModelClass.TableNameDomainPropertyId, typeof(ModelClass.TableNamePropertyHandler)), diff --git a/src/Dsl/GeneratedCode/DomainModelResx.resx b/src/Dsl/GeneratedCode/DomainModelResx.resx index 5eeef4301..fc7383031 100644 --- a/src/Dsl/GeneratedCode/DomainModelResx.resx +++ b/src/Dsl/GeneratedCode/DomainModelResx.resx @@ -609,18 +609,6 @@ Designer Category for DomainProperty 'GridColor' on DomainClass 'ModelRoot' - - Size of display grid units, in inches - Description for DomainProperty 'GridSize' on DomainClass 'ModelRoot' - - - Grid Size - DisplayName for DomainProperty 'GridSize' on DomainClass 'ModelRoot' - - - Designer - Category for DomainProperty 'GridSize' on DomainClass 'ModelRoot' - If true, will show declared foreign key property names (if any) on the association ends Description for DomainProperty 'ShowForeignKeyPropertyNames' on DomainClass 'ModelRoot' diff --git a/src/Dsl/GeneratedCode/EFModelSchema.xsd b/src/Dsl/GeneratedCode/EFModelSchema.xsd index 494b303a7..d4ebb81f9 100644 --- a/src/Dsl/GeneratedCode/EFModelSchema.xsd +++ b/src/Dsl/GeneratedCode/EFModelSchema.xsd @@ -315,12 +315,6 @@ Color for designer grid - - - - Size of display grid units, in inches - - diff --git a/src/Dsl/GeneratedCode/LanguageNameSchema.xsd b/src/Dsl/GeneratedCode/LanguageNameSchema.xsd index 494b303a7..d4ebb81f9 100644 --- a/src/Dsl/GeneratedCode/LanguageNameSchema.xsd +++ b/src/Dsl/GeneratedCode/LanguageNameSchema.xsd @@ -315,12 +315,6 @@ Color for designer grid - - - - Size of display grid units, in inches - - diff --git a/src/Dsl/GeneratedCode/SerializationHelper.cs b/src/Dsl/GeneratedCode/SerializationHelper.cs index a4ff15584..c3d985694 100644 --- a/src/Dsl/GeneratedCode/SerializationHelper.cs +++ b/src/Dsl/GeneratedCode/SerializationHelper.cs @@ -1168,7 +1168,7 @@ public virtual void WriteRootElement(DslModeling::SerializationContext serializa // Only model has schema, diagram has no schema. rootElementSettings.SchemaTargetNamespace = "http://schemas.microsoft.com/dsltools/EFModel"; } - rootElementSettings.Version = new global::System.Version("2.0.5.6"); + rootElementSettings.Version = new global::System.Version("2.0.5.7"); // Carry out the normal serialization. rootSerializer.Write(serializationContext, rootElement, writer, rootElementSettings); @@ -1190,7 +1190,7 @@ protected virtual void CheckVersion(DslModeling::SerializationContext serializat throw new global::System.ArgumentNullException("reader"); #endregion - global::System.Version expectedVersion = new global::System.Version("2.0.5.6"); + global::System.Version expectedVersion = new global::System.Version("2.0.5.7"); string dslVersionStr = reader.GetAttribute("dslVersion"); if (dslVersionStr != null) { diff --git a/src/Dsl/GeneratedCode/Serializer.cs b/src/Dsl/GeneratedCode/Serializer.cs index 805e6f76b..255415c3b 100644 --- a/src/Dsl/GeneratedCode/Serializer.cs +++ b/src/Dsl/GeneratedCode/Serializer.cs @@ -800,23 +800,6 @@ protected override void ReadPropertiesFromAttributes(DslModeling::SerializationC } } } - // GridSize - if (!serializationContext.Result.Failed) - { - string attribGridSize = EFModelSerializationHelper.Instance.ReadAttribute(serializationContext, element, reader, "gridSize"); - if (attribGridSize != null) - { - global::System.Int16 valueOfGridSize; - if (DslModeling::SerializationUtilities.TryGetValue(serializationContext, attribGridSize, out valueOfGridSize)) - { - instanceOfModelRoot.GridSize = valueOfGridSize; - } - else - { // Invalid property value, ignored. - EFModelSerializationBehaviorSerializationMessages.IgnoredPropertyValue(serializationContext, reader, "gridSize", typeof(global::System.Int16), attribGridSize); - } - } - } // ShowForeignKeyPropertyNames if (!serializationContext.Result.Failed) { @@ -1977,19 +1960,6 @@ protected override void WritePropertiesAsAttributes(DslModeling::SerializationCo } } } - // GridSize - if (!serializationContext.Result.Failed) - { - global::System.Int16 propValue = instanceOfModelRoot.GridSize; - string serializedPropValue = DslModeling::SerializationUtilities.GetString(serializationContext, propValue); - if (!serializationContext.Result.Failed) - { - if (serializationContext.WriteOptionalPropertiesWithDefaultValue || string.CompareOrdinal(serializedPropValue, "0") != 0) - { // No need to write the value out if it's the same as default value. - EFModelSerializationHelper.Instance.WriteAttributeString(serializationContext, element, writer, "gridSize", serializedPropValue); - } - } - } // ShowForeignKeyPropertyNames if (!serializationContext.Result.Failed) { diff --git a/src/Dsl/Properties/AssemblyInfo.cs b/src/Dsl/Properties/AssemblyInfo.cs index cb2cfe92b..b6d0c9624 100644 --- a/src/Dsl/Properties/AssemblyInfo.cs +++ b/src/Dsl/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyCulture("")] [assembly: System.Resources.NeutralResourcesLanguage("en")] -[assembly: AssemblyVersion("2.0.5.6")] -[assembly: AssemblyFileVersion("2.0.5.6")] +[assembly: AssemblyVersion("2.0.5.7")] +[assembly: AssemblyFileVersion("2.0.5.7")] [assembly: ComVisible(false)] [assembly: CLSCompliant(true)] [assembly: ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)] @@ -42,3 +42,5 @@ + + diff --git a/src/DslPackage/CustomCode/OptionsPage.cs b/src/DslPackage/CustomCode/OptionsPage.cs index e5f0cfe14..5831e2830 100644 --- a/src/DslPackage/CustomCode/OptionsPage.cs +++ b/src/DslPackage/CustomCode/OptionsPage.cs @@ -18,5 +18,10 @@ public class OptionsPage : DialogPage [DisplayName("Save diagram using legacy binary format")] [Description("If true, .diagramx files will be saved in compressed format. If false, they will not.")] public bool SaveDiagramsCompressed { get; set; } + + [Category("Code Generation")] + [DisplayName("Indent with tabs instead of spaces")] + [Description("If true, code will be generated using tabs and obey the editor's tab width")] + public bool UseTabs { get; set; } } } \ No newline at end of file diff --git a/src/DslPackage/CustomCode/Partials/EFModelDocData.cs b/src/DslPackage/CustomCode/Partials/EFModelDocData.cs index 4bada9350..8c3a9c413 100644 --- a/src/DslPackage/CustomCode/Partials/EFModelDocData.cs +++ b/src/DslPackage/CustomCode/Partials/EFModelDocData.cs @@ -215,6 +215,7 @@ protected override void OnDocumentLoaded() ModelRoot.ExecuteValidator = ValidateAll; ModelRoot.GetCurrentDiagram = GetCurrentDiagram; ModelRoot.WriteDiagramAsBinary = () => EFModelPackage.Options.SaveDiagramsCompressed; + ModelRoot.UseTabs = () => EFModelPackage.Options.UseTabs; ModelDiagramData.OpenDiagram = DisplayDiagram; ModelDiagramData.CloseDiagram = CloseDiagram; ModelDiagramData.RenameWindow = RenameWindow; diff --git a/src/DslPackage/DslPackage.csproj b/src/DslPackage/DslPackage.csproj index 382f78a21..12b34957a 100644 --- a/src/DslPackage/DslPackage.csproj +++ b/src/DslPackage/DslPackage.csproj @@ -173,6 +173,11 @@ True Constants.tt + + True + True + DocView.tt + True True @@ -295,11 +300,6 @@ TextTemplatingFileGenerator DocView.cs - - True - True - DocView.tt - True True diff --git a/src/DslPackage/GeneratedCode/Constants.cs b/src/DslPackage/GeneratedCode/Constants.cs index 71b849661..9ed4b6b32 100644 --- a/src/DslPackage/GeneratedCode/Constants.cs +++ b/src/DslPackage/GeneratedCode/Constants.cs @@ -18,7 +18,7 @@ internal static partial class Constants [global::System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] public const string CompanyName = @"Michael Sawczyn"; [global::System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] - public const string ProductVersion = "2.0.5.6"; + public const string ProductVersion = "2.0.5.7"; // Menu definitions public static readonly global::System.ComponentModel.Design.CommandID EFModelDiagramMenu = new global::System.ComponentModel.Design.CommandID(new global::System.Guid(EFModelCommandSetId), 0x10000); diff --git a/src/DslPackage/Parsers/EF6Parser.exe b/src/DslPackage/Parsers/EF6Parser.exe index 1e79aa59b..0e5a006c1 100644 Binary files a/src/DslPackage/Parsers/EF6Parser.exe and b/src/DslPackage/Parsers/EF6Parser.exe differ diff --git a/src/DslPackage/Parsers/EFCore2Parser.exe b/src/DslPackage/Parsers/EFCore2Parser.exe index 9d8485852..adcc4b78b 100644 Binary files a/src/DslPackage/Parsers/EFCore2Parser.exe and b/src/DslPackage/Parsers/EFCore2Parser.exe differ diff --git a/src/DslPackage/Parsers/EFCore3Parser.exe b/src/DslPackage/Parsers/EFCore3Parser.exe index 93ab5db3c..8cbd9deeb 100644 Binary files a/src/DslPackage/Parsers/EFCore3Parser.exe and b/src/DslPackage/Parsers/EFCore3Parser.exe differ diff --git a/src/DslPackage/ProjectItemTemplates/EFModel.xsd b/src/DslPackage/ProjectItemTemplates/EFModel.xsd index 494b303a7..d4ebb81f9 100644 --- a/src/DslPackage/ProjectItemTemplates/EFModel.xsd +++ b/src/DslPackage/ProjectItemTemplates/EFModel.xsd @@ -315,12 +315,6 @@ Color for designer grid - - - - Size of display grid units, in inches - - diff --git a/src/DslPackage/Properties/AssemblyInfo.cs b/src/DslPackage/Properties/AssemblyInfo.cs index e23061582..3c2ad24c6 100644 --- a/src/DslPackage/Properties/AssemblyInfo.cs +++ b/src/DslPackage/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyCulture("")] [assembly: System.Resources.NeutralResourcesLanguage("en")] -[assembly: AssemblyVersion("2.0.5.6")] -[assembly: AssemblyFileVersion("2.0.5.6")] +[assembly: AssemblyVersion("2.0.5.7")] +[assembly: AssemblyFileVersion("2.0.5.7")] [assembly: ComVisible(false)] [assembly: CLSCompliant(false)] [assembly: ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)] @@ -37,3 +37,5 @@ + + diff --git a/src/DslPackage/TextTemplates/EF6Designer.ttinclude b/src/DslPackage/TextTemplates/EF6Designer.ttinclude index cb846d84a..a0883e7af 100644 --- a/src/DslPackage/TextTemplates/EF6Designer.ttinclude +++ b/src/DslPackage/TextTemplates/EF6Designer.ttinclude @@ -8,7 +8,7 @@ #><#@ import namespace="System.Linq" #><#@ import namespace="System.Security" #><#+ - // EFDesigner v2.0.5.6 + // EFDesigner v2.0.5.7 // Copyright (c) 2017-2020 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -783,10 +783,4 @@ return $"HasForeignKey(p => {columnNames})"; } -#> - - - - - - +#> \ No newline at end of file diff --git a/src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude b/src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude index 5e417fe24..f1b01a949 100644 --- a/src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude +++ b/src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude @@ -8,7 +8,7 @@ #><#@ import namespace="System.Linq" #><#@ import namespace="System.Security" #><#+ - // EFDesigner v2.0.5.6 + // EFDesigner v2.0.5.7 // Copyright (c) 2017-2020 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -340,7 +340,7 @@ if (!modelAttribute.AutoProperty) { - segments.Add($"HasField(\"{(string.IsNullOrEmpty(modelAttribute.BackingFieldName) ? $"_{modelAttribute.Name}" : modelAttribute.BackingFieldName)}\")"); + segments.Add($"HasField(\"{modelAttribute.BackingFieldName}\")"); segments.Add($"UsePropertyAccessMode(PropertyAccessMode.{(modelAttribute.PersistencePoint == PersistencePointType.Field ? "Field" : "Property")})"); } @@ -700,10 +700,4 @@ : $"HasForeignKey({columnName})"; } -#> - - - - - - +#> \ No newline at end of file diff --git a/src/DslPackage/TextTemplates/EFDesigner.ttinclude b/src/DslPackage/TextTemplates/EFDesigner.ttinclude index e3541765a..39384e713 100644 --- a/src/DslPackage/TextTemplates/EFDesigner.ttinclude +++ b/src/DslPackage/TextTemplates/EFDesigner.ttinclude @@ -8,7 +8,7 @@ #><#@ import namespace="System.Linq" #><#@ import namespace="System.Security" #><#+ - // EFDesigner v2.0.5.6 + // EFDesigner v2.0.5.7 // Copyright (c) 2017-2020 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -281,7 +281,7 @@ WriteLine(text); if (text.EndsWith("{")) - PushIndent(" "); + PushIndent(ModelRoot.UseTabs() ? "\t" : " "); } void Output(string template, params object[] items) @@ -926,7 +926,7 @@ Output("/// "); Output($"/// Backing field for {modelAttribute.Name}"); Output("/// "); - Output($"{visibility} {modelAttribute.FQPrimitiveType}{nullable} {(string.IsNullOrEmpty(modelAttribute.BackingFieldName) ? $"_{modelAttribute.Name}" : modelAttribute.BackingFieldName)};"); + Output($"{visibility} {modelAttribute.FQPrimitiveType}{nullable} {modelAttribute.BackingFieldName};"); Output("/// "); Output($"/// When provided in a partial class, allows value of {modelAttribute.Name} to be changed before setting."); Output("/// "); @@ -978,17 +978,17 @@ Output("{"); Output("get"); Output("{"); - Output($"{modelAttribute.FQPrimitiveType}{nullable} value = {(string.IsNullOrEmpty(modelAttribute.BackingFieldName) ? $"_{modelAttribute.Name}" : modelAttribute.BackingFieldName)};"); + Output($"{modelAttribute.FQPrimitiveType}{nullable} value = {modelAttribute.BackingFieldName};"); Output($"Get{modelAttribute.Name}(ref value);"); - Output($"return ({(string.IsNullOrEmpty(modelAttribute.BackingFieldName) ? $"_{modelAttribute.Name}" : modelAttribute.BackingFieldName)} = value);"); + Output($"return ({modelAttribute.BackingFieldName} = value);"); Output("}"); Output($"{setterVisibility}set"); Output("{"); - Output($"{modelAttribute.FQPrimitiveType}{nullable} oldValue = {(string.IsNullOrEmpty(modelAttribute.BackingFieldName) ? $"_{modelAttribute.Name}" : modelAttribute.BackingFieldName)};"); + Output($"{modelAttribute.FQPrimitiveType}{nullable} oldValue = {modelAttribute.BackingFieldName};"); Output($"Set{modelAttribute.Name}(oldValue, ref value);"); Output("if (oldValue != value)"); Output("{"); - Output($"{(string.IsNullOrEmpty(modelAttribute.BackingFieldName) ? $"_{modelAttribute.Name}" : modelAttribute.BackingFieldName)} = value;"); + Output($"{modelAttribute.BackingFieldName} = value;"); if (modelAttribute.ImplementNotify) Output("OnPropertyChanged();"); @@ -1032,10 +1032,4 @@ Output("/// "); } -#> - - - - - - +#> \ No newline at end of file diff --git a/src/DslPackage/TextTemplates/EditingOnly/EF6Designer.cs b/src/DslPackage/TextTemplates/EditingOnly/EF6Designer.cs index 5e210e2dd..83c41a406 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EF6Designer.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EF6Designer.cs @@ -22,7 +22,7 @@ namespace Sawczyn.EFDesigner.EFModel.DslPackage.TextTemplates.EditingOnly [SuppressMessage("ReSharper", "UnusedMember.Global")] partial class EditOnly { - // EFDesigner v2.0.5.6 + // EFDesigner v2.0.5.7 // Copyright (c) 2017-2020 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -798,8 +798,3 @@ string CreateForeignKeySegmentEF6(Association association, List foreignK } } } - - - - - diff --git a/src/DslPackage/TextTemplates/EditingOnly/EF6Designer.ttinclude b/src/DslPackage/TextTemplates/EditingOnly/EF6Designer.ttinclude new file mode 100644 index 000000000..0c49d06a5 --- /dev/null +++ b/src/DslPackage/TextTemplates/EditingOnly/EF6Designer.ttinclude @@ -0,0 +1,802 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity.Design.PluralizationServices; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +// ReSharper disable RedundantNameQualifier + +namespace System.Data.Entity.Design.PluralizationServices +{ + internal class PluralizationService + { + internal bool IsSingular(string _) { return true; } + + internal string Pluralize(string _) { return string.Empty; } + } +} + +namespace Sawczyn.EFDesigner.EFModel.DslPackage.TextTemplates.EditingOnly +{ + [SuppressMessage("ReSharper", "UnusedMember.Local")] + [SuppressMessage("ReSharper", "UnusedMember.Global")] + partial class EditOnly + { + // EFDesigner v2.0.5.7 + // Copyright (c) 2017-2020 Michael Sawczyn + // https://github.com/msawczyn/EFDesigner + + /************************************************** + * EF6-specific code generation methods + */ + + void GenerateEF6(Manager manager, ModelRoot modelRoot) + { + // Entities + string fileNameMarker = string.IsNullOrEmpty(modelRoot.FileNameMarker) ? string.Empty : $".{modelRoot.FileNameMarker}"; + + foreach (ModelClass modelClass in modelRoot.Classes.Where(e => e.GenerateCode)) + { + manager.StartNewFile(Path.Combine(modelClass.EffectiveOutputDirectory, $"{modelClass.Name}{fileNameMarker}.cs")); + WriteClass(modelClass); + } + + // Enums + + foreach (ModelEnum modelEnum in modelRoot.Enums.Where(e => e.GenerateCode)) + { + manager.StartNewFile(Path.Combine(modelEnum.EffectiveOutputDirectory, $"{modelEnum.Name}{fileNameMarker}.cs")); + WriteEnum(modelEnum); + } + + // Context + + if (modelRoot.DatabaseInitializerType != DatabaseInitializerKind.None) + { + manager.StartNewFile(Path.Combine(modelRoot.ContextOutputDirectory, $"{modelRoot.EntityContainerName}DatabaseInitializer{fileNameMarker}.cs")); + WriteDatabaseInitializerEF6(modelRoot); + } + + manager.StartNewFile(Path.Combine(modelRoot.ContextOutputDirectory, $"{modelRoot.EntityContainerName}DbMigrationConfiguration{fileNameMarker}.cs")); + WriteMigrationConfigurationEF6(modelRoot); + + manager.StartNewFile(Path.Combine(modelRoot.ContextOutputDirectory, $"{modelRoot.EntityContainerName}{fileNameMarker}.cs")); + WriteDbContextEF6(modelRoot); + } + + string[] SpatialTypesEF6 + { + get + { + return new[] + { + "Geography" + , "GeographyCollection" + , "GeographyLineString" + , "GeographyMultiLineString" + , "GeographyMultiPoint" + , "GeographyMultiPolygon" + , "GeographyPoint" + , "GeographyPolygon" + , "Geometry" + , "GeometryCollection" + , "GeometryLineString" + , "GeometryMultiLineString" + , "GeometryMultiPoint" + , "GeometryMultiPolygon" + , "GeometryPoint" + , "GeometryPolygon" + }; + } + } + + List GetAdditionalUsingStatementsEF6(ModelRoot modelRoot) + { + List result = new List(); + List attributeTypes = modelRoot.Classes.SelectMany(c => c.Attributes).Select(a => a.Type).Distinct().ToList(); + + if (attributeTypes.Intersect(modelRoot.SpatialTypes).Any()) + result.Add("using System.Data.Entity.Spatial;"); + + return result; + } + + void WriteDatabaseInitializerEF6(ModelRoot modelRoot) + { + Output("using System.Data.Entity;"); + NL(); + + BeginNamespace(modelRoot.Namespace); + + Output("/// "); + + Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.MigrateDatabaseToLatestVersion + ? $"public partial class {modelRoot.EntityContainerName}DatabaseInitializer : MigrateDatabaseToLatestVersion<{modelRoot.Namespace}.{modelRoot.EntityContainerName}, {GetMigrationNamespace(modelRoot)}.{modelRoot.EntityContainerName}DbMigrationConfiguration>" + : $"public partial class {modelRoot.EntityContainerName}DatabaseInitializer : {modelRoot.DatabaseInitializerType}<{modelRoot.Namespace}.{modelRoot.EntityContainerName}>"); + + Output("{"); + Output("}"); + EndNamespace(modelRoot.Namespace); + } + + void WriteDbContextEF6(ModelRoot modelRoot) + { + ModelClass[] classesWithTables = null; + + switch (modelRoot.InheritanceStrategy) + { + case CodeStrategy.TablePerType: + classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType && mc.GenerateCode).OrderBy(x => x.Name).ToArray(); + + break; + + case CodeStrategy.TablePerConcreteType: + classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType && !mc.IsAbstract && mc.GenerateCode).OrderBy(x => x.Name).ToArray(); + + break; + + case CodeStrategy.TablePerHierarchy: + classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType && mc.Superclass == null && mc.GenerateCode).OrderBy(x => x.Name).ToArray(); + + break; + } + + Output("using System;"); + Output("using System.Collections.Generic;"); + Output("using System.Linq;"); + Output("using System.ComponentModel.DataAnnotations.Schema;"); + Output("using System.Data.Entity;"); + Output("using System.Data.Entity.Infrastructure.Annotations;"); + NL(); + + BeginNamespace(modelRoot.Namespace); + + WriteDbContextComments(modelRoot); + + string baseClass = string.IsNullOrWhiteSpace(modelRoot.BaseClass) ? "System.Data.Entity.DbContext" : modelRoot.BaseClass; + + Output($"{modelRoot.EntityContainerAccess.ToString().ToLower()} partial class {modelRoot.EntityContainerName} : {baseClass}"); + Output("{"); + + if (classesWithTables?.Any() == true) + WriteDbSetsEF6(modelRoot); + + WriteConstructorsEF6(modelRoot); + WriteModelCreateEF6(modelRoot, classesWithTables); + + Output("}"); + + EndNamespace(modelRoot.Namespace); + } + + private void WriteDbSetsEF6(ModelRoot modelRoot) + { + Output("#region DbSets"); + PluralizationService pluralizationService = ModelRoot.PluralizationService; + + foreach (ModelClass modelClass in modelRoot.Classes.Where(x => !x.IsDependentType).OrderBy(x => x.Name)) + { + string dbSetName; + + if (!string.IsNullOrEmpty(modelClass.DbSetName)) + dbSetName = modelClass.DbSetName; + else + { + dbSetName = pluralizationService?.IsSingular(modelClass.Name) == true + ? pluralizationService.Pluralize(modelClass.Name) + : modelClass.Name; + } + + if (!string.IsNullOrEmpty(modelClass.Summary)) + { + NL(); + Output("/// "); + WriteCommentBody($"Repository for {modelClass.FullName} - {modelClass.Summary}"); + Output("/// "); + } + + Output($"{modelRoot.DbSetAccess.ToString().ToLower()} virtual System.Data.Entity.DbSet<{modelClass.FullName}> {dbSetName} {{ get; set; }}"); + } + + Output("#endregion DbSets"); + NL(); + } + + private void WriteConstructorsEF6(ModelRoot modelRoot) + { + Output("#region Constructors"); + NL(); + Output("partial void CustomInit();"); + NL(); + + if (!string.IsNullOrEmpty(modelRoot.ConnectionString) || !string.IsNullOrEmpty(modelRoot.ConnectionStringName)) + { + string connectionString = string.IsNullOrEmpty(modelRoot.ConnectionString) + ? $"Name={modelRoot.ConnectionStringName}" + : modelRoot.ConnectionString; + + Output("/// "); + Output("/// Default connection string"); + Output("/// "); + Output($"public static string ConnectionString {{ get; set; }} = @\"{connectionString}\";"); + + Output("/// "); + Output($"public {modelRoot.EntityContainerName}() : base(ConnectionString)"); + Output("{"); + Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); + Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); + + Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None + ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" + : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); + + Output("CustomInit();"); + Output("}"); + NL(); + } + else + { + Output($"#warning Default constructor not generated for {modelRoot.EntityContainerName} since no default connection string was specified in the model"); + NL(); + } + + Output("/// "); + Output($"public {modelRoot.EntityContainerName}(string connectionString) : base(connectionString)"); + Output("{"); + Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); + Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); + + Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None + ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" + : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); + + Output("CustomInit();"); + Output("}"); + NL(); + + Output("/// "); + Output($"public {modelRoot.EntityContainerName}(string connectionString, System.Data.Entity.Infrastructure.DbCompiledModel model) : base(connectionString, model)"); + Output("{"); + Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); + Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); + + Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None + ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" + : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); + + Output("CustomInit();"); + Output("}"); + NL(); + + Output("/// "); + Output($"public {modelRoot.EntityContainerName}(System.Data.Common.DbConnection existingConnection, bool contextOwnsConnection) : base(existingConnection, contextOwnsConnection)"); + Output("{"); + Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); + Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); + + Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None + ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" + : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); + + Output("CustomInit();"); + Output("}"); + NL(); + + Output("/// "); + Output($"public {modelRoot.EntityContainerName}(System.Data.Common.DbConnection existingConnection, System.Data.Entity.Infrastructure.DbCompiledModel model, bool contextOwnsConnection) : base(existingConnection, model, contextOwnsConnection)"); + Output("{"); + Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); + Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); + + Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None + ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" + : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); + + Output("CustomInit();"); + Output("}"); + NL(); + + Output("/// "); + Output($"public {modelRoot.EntityContainerName}(System.Data.Entity.Infrastructure.DbCompiledModel model) : base(model)"); + Output("{"); + Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); + Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); + + Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None + ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" + : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); + + Output("CustomInit();"); + Output("}"); + NL(); + + Output("/// "); + Output($"public {modelRoot.EntityContainerName}(System.Data.Entity.Core.Objects.ObjectContext objectContext, bool dbContextOwnsObjectContext) : base(objectContext, dbContextOwnsObjectContext)"); + Output("{"); + Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); + Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); + + Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None + ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" + : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); + + Output("CustomInit();"); + Output("}"); + NL(); + Output("#endregion Constructors"); + NL(); + } + + private void WriteModelCreateEF6(ModelRoot modelRoot, ModelClass[] classesWithTables) + { + Output("partial void OnModelCreatingImpl(System.Data.Entity.DbModelBuilder modelBuilder);"); + Output("partial void OnModelCreatedImpl(System.Data.Entity.DbModelBuilder modelBuilder);"); + NL(); + + Output("/// "); + Output("protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)"); + Output("{"); + Output("base.OnModelCreating(modelBuilder);"); + Output("OnModelCreatingImpl(modelBuilder);"); + NL(); + + if (!string.IsNullOrEmpty(modelRoot.DatabaseSchema)) + Output($"modelBuilder.HasDefaultSchema(\"{modelRoot.DatabaseSchema}\");"); + + List segments = new List(); + + List visited = new List(); + List foreignKeyColumns = new List(); + List declaredShadowProperties = new List(); + + foreach (ModelClass modelClass in modelRoot.Classes.OrderBy(x => x.Name)) + { + segments.Clear(); + foreignKeyColumns.Clear(); + declaredShadowProperties.Clear(); + NL(); + + // class level + bool isDependent = modelClass.IsDependentType; + segments.Add($"modelBuilder.{(isDependent ? "ComplexType" : "Entity")}<{modelClass.FullName}>()"); + + foreach (ModelAttribute transient in modelClass.Attributes.Where(x => !x.Persistent)) + segments.Add($"Ignore(t => t.{transient.Name})"); + + if (!isDependent) + { + // note: this must come before the 'ToTable' call or there's a runtime error + if (modelRoot.InheritanceStrategy == CodeStrategy.TablePerConcreteType && modelClass.Superclass != null) + segments.Add("Map(x => x.MapInheritedProperties())"); + + if (classesWithTables.Contains(modelClass)) + { + if (modelRoot.InheritanceStrategy != CodeStrategy.TablePerConcreteType || !modelClass.IsAbstract) + { + segments.Add(string.IsNullOrEmpty(modelClass.DatabaseSchema) || modelClass.DatabaseSchema == modelClass.ModelRoot.DatabaseSchema + ? $"ToTable(\"{modelClass.TableName}\")" + : $"ToTable(\"{modelClass.TableName}\", \"{modelClass.DatabaseSchema}\")"); + } + + // primary key code segments must be output last, since HasKey returns a different type + List identityAttributes = modelClass.AllIdentityAttributes.ToList(); + + if (identityAttributes.Count == 1) + segments.Add($"HasKey(t => t.{identityAttributes[0].Name})"); + else if (identityAttributes.Count > 1) + segments.Add($"HasKey(t => new {{ t.{string.Join(", t.", identityAttributes.Select(ia => ia.Name))} }})"); + } + } + + if (segments.Count > 1 || isDependent) + { + if (modelRoot.ChopMethodChains) + OutputChopped(segments); + else + Output(string.Join(".", segments) + ";"); + } + + if (isDependent) + continue; + + // attribute level + foreach (ModelAttribute modelAttribute in modelClass.Attributes.Where(x => x.Persistent && !SpatialTypesEF6.Contains(x.Type))) + { + segments.Clear(); + + if ((modelAttribute.MaxLength ?? 0) > 0) + segments.Add($"HasMaxLength({modelAttribute.MaxLength})"); + + if (modelAttribute.Required) + segments.Add("IsRequired()"); + + if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) + segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); + + if (!string.IsNullOrEmpty(modelAttribute.ColumnType) && modelAttribute.ColumnType.ToLowerInvariant() != "default") + segments.Add($"HasColumnType(\"{modelAttribute.ColumnType}\")"); + + if (modelAttribute.Indexed && !modelAttribute.IsIdentity) + segments.Add("HasColumnAnnotation(\"Index\", new IndexAnnotation(new IndexAttribute()))"); + + if (modelAttribute.IsConcurrencyToken) + segments.Add("IsRowVersion()"); + + if (modelAttribute.IsIdentity) + { + segments.Add(modelAttribute.IdentityType == IdentityType.AutoGenerated + ? "HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)" + : "HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)"); + } + + if (segments.Any()) + { + segments.Insert(0, $"modelBuilder.{(isDependent ? "ComplexType" : "Entity")}<{modelClass.FullName}>()"); + segments.Insert(1, $"Property(t => t.{modelAttribute.Name})"); + + if (modelRoot.ChopMethodChains) + OutputChopped(segments); + else + Output(string.Join(".", segments) + ";"); + } + + if (modelAttribute.Indexed && !modelAttribute.IsIdentity) + { + segments.Clear(); + + segments.Add($"modelBuilder.Entity<{modelClass.FullName}>().HasIndex(t => t.{modelAttribute.Name})"); + + if (modelAttribute.IndexedUnique) + segments.Add("IsUnique()"); + + if (segments.Count > 1) + { + if (modelRoot.ChopMethodChains) + OutputChopped(segments); + else + Output(string.Join(".", segments) + ";"); + } + } + } + + if (!isDependent) + { + // Navigation endpoints are distingished as Source and Target. They are also distinguished as Principal + // and Dependent. How do these map? + // In the case of one-to-one or zero-to-one-to-zero-to-one, it's model dependent and the user has to tell us + // In all other cases, we can tell by the cardinalities of the associations + // What matters is the Principal and Dependent classifications, so we look at those. + // Source and Target are accidents of where the user started drawing the association. + + // navigation properties + + // ReSharper disable once LoopCanBePartlyConvertedToQuery + foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) + .OfType() + .Where(x => x.Persistent && !x.Target.IsDependentType)) + { + if (visited.Contains(association)) + continue; + + visited.Add(association); + + segments.Clear(); + segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); + + switch (association.TargetMultiplicity) // realized by property on source + { + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: + segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); + + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.One: + segments.Add($"HasRequired(x => x.{association.TargetPropertyName})"); + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: + segments.Add($"HasOptional(x => x.{association.TargetPropertyName})"); + + break; + + //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: + // segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); + // break; + } + + switch (association.SourceMultiplicity) // realized by property on target, but no property on target + { + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: + segments.Add("WithMany()"); + + if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) + { + string tableMap = string.IsNullOrEmpty(association.JoinTableName) ? $"{association.Source.Name}_x_{association.TargetPropertyName}" : association.JoinTableName; + string suffix1 = association.Source == association.Target ? "A" : ""; + string suffix2 = association.Source == association.Target ? "B" : ""; + string sourceMap = string.Join(", ", association.Source.AllIdentityAttributeNames.Select(n => $@"""{association.Source.Name}_{n}{suffix1}""").ToList()); + string targetMap = string.Join(", ", association.Target.AllIdentityAttributeNames.Select(n => $@"""{association.Target.Name}_{n}{suffix2}""").ToList()); + + segments.Add(modelClass == association.Source + ? $@"Map(x => {{ x.ToTable(""{tableMap}""); x.MapLeftKey({sourceMap}); x.MapRightKey({targetMap}); }})" + : $@"Map(x => {{ x.ToTable(""{tableMap}""); x.MapLeftKey({targetMap}); x.MapRightKey({sourceMap}); }})"); + } + + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.One: + if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One) + { + segments.Add(association.TargetRole == EndpointRole.Dependent + ? "WithRequiredDependent()" + : "WithRequiredPrincipal()"); + } + else + segments.Add("WithRequired()"); + + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: + if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne) + { + segments.Add(association.TargetRole == EndpointRole.Dependent + ? "WithOptionalDependent()" + : "WithOptionalPrincipal()"); + } + else + segments.Add("WithOptional()"); + + break; + + //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: + // segments.Add("HasMany()"); + // break; + } + + string foreignKeySegment = CreateForeignKeySegmentEF6(association, foreignKeyColumns); + + // can't include shadow properties twice + if (foreignKeySegment != null) + { + if (!foreignKeySegment.Contains("MapKey")) + segments.Add(foreignKeySegment); + else if (!declaredShadowProperties.Contains(foreignKeySegment)) + { + declaredShadowProperties.Add(foreignKeySegment); + segments.Add(foreignKeySegment); + } + } + + // Certain associations cascade delete automatically. Also, the user may ask for it. + // We only generate a cascade delete call if the user asks for it. + if ((association.TargetDeleteAction != DeleteAction.Default && association.TargetRole == EndpointRole.Principal) || (association.SourceDeleteAction != DeleteAction.Default && association.SourceRole == EndpointRole.Principal)) + { + string willCascadeOnDelete = association.TargetDeleteAction != DeleteAction.Default && association.TargetRole == EndpointRole.Principal + ? (association.TargetDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant() + : (association.SourceDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant(); + + segments.Add($"WillCascadeOnDelete({willCascadeOnDelete})"); + } + + if (modelRoot.ChopMethodChains) + OutputChopped(segments); + else + Output(string.Join(".", segments) + ";"); + } + + // ReSharper disable once LoopCanBePartlyConvertedToQuery + foreach (BidirectionalAssociation association in Association.GetLinksToSources(modelClass) + .OfType() + .Where(x => x.Persistent)) + { + if (visited.Contains(association)) + continue; + + visited.Add(association); + + segments.Clear(); + segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); + + switch (association.SourceMultiplicity) // realized by property on target + { + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: + segments.Add($"HasMany(x => x.{association.SourcePropertyName})"); + + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.One: + segments.Add($"HasRequired(x => x.{association.SourcePropertyName})"); + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: + segments.Add($"HasOptional(x => x.{association.SourcePropertyName})"); + + break; + + //one or more constraint not supported in EF. + // TODO: make this possible ... later + //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: + // segments.Add($"HasMany(x => x.{association.SourcePropertyName})"); + // break; + } + + switch (association.TargetMultiplicity) // realized by property on source + { + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: + segments.Add($"WithMany(x => x.{association.TargetPropertyName})"); + + if (association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) + { + string tableMap = string.IsNullOrEmpty(association.JoinTableName) ? $"{association.SourcePropertyName}_x_{association.TargetPropertyName}" : association.JoinTableName; + string suffix1 = association.Source == association.Target ? "A" : ""; + string suffix2 = association.Source == association.Target ? "B" : ""; + string sourceMap = string.Join(", ", association.Source.AllIdentityAttributeNames.Select(n => $@"""{association.Source.Name}_{n}{suffix1}""").ToList()); + string targetMap = string.Join(", ", association.Target.AllIdentityAttributeNames.Select(n => $@"""{association.Target.Name}_{n}{suffix2}""").ToList()); + + segments.Add(modelClass == association.Source + ? $@"Map(x => {{ x.ToTable(""{tableMap}""); x.MapLeftKey({sourceMap}); x.MapRightKey({targetMap}); }})" + : $@"Map(x => {{ x.ToTable(""{tableMap}""); x.MapLeftKey({targetMap}); x.MapRightKey({sourceMap}); }})"); + } + + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.One: + if (association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One) + { + segments.Add(association.SourceRole == EndpointRole.Dependent + ? $"WithRequiredDependent(x => x.{association.TargetPropertyName})" + : $"WithRequiredPrincipal(x => x.{association.TargetPropertyName})"); + } + else + segments.Add($"WithRequired(x => x.{association.TargetPropertyName})"); + + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: + if (association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne) + { + segments.Add(association.SourceRole == EndpointRole.Dependent + ? $"WithOptionalDependent(x => x.{association.TargetPropertyName})" + : $"WithOptionalPrincipal(x => x.{association.TargetPropertyName})"); + } + else + segments.Add($"WithOptional(x => x.{association.TargetPropertyName})"); + + break; + + //one or more constraint not supported in EF. TODO: make this possible ... later + //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: + // segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); + // break; + } + + string foreignKeySegment = CreateForeignKeySegmentEF6(association, foreignKeyColumns); + + // can't shadow properties twice + if (foreignKeySegment != null) + { + if (!foreignKeySegment.Contains("MapKey")) + segments.Add(foreignKeySegment); + else if (!declaredShadowProperties.Contains(foreignKeySegment)) + { + declaredShadowProperties.Add(foreignKeySegment); + segments.Add(foreignKeySegment); + } + } + + if ((association.TargetDeleteAction != DeleteAction.Default && association.TargetRole == EndpointRole.Principal) || (association.SourceDeleteAction != DeleteAction.Default && association.SourceRole == EndpointRole.Principal)) + { + string willCascadeOnDelete = association.TargetDeleteAction != DeleteAction.Default && association.TargetRole == EndpointRole.Principal + ? (association.TargetDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant() + : (association.SourceDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant(); + + segments.Add($"WillCascadeOnDelete({willCascadeOnDelete})"); + } + + if (modelRoot.ChopMethodChains) + OutputChopped(segments); + else + Output(string.Join(".", segments) + ";"); + } + } + } + + NL(); + + Output("OnModelCreatedImpl(modelBuilder);"); + Output("}"); + } + + string GetMigrationNamespace(ModelRoot modelRoot) + { + List nsParts = modelRoot.Namespace.Split('.').ToList(); + nsParts = nsParts.Take(nsParts.Count - 1).ToList(); + nsParts.Add("Migrations"); + return string.Join(".", nsParts); + } + + void WriteMigrationConfigurationEF6(ModelRoot modelRoot) + { + Output("using System.Data.Entity.Migrations;"); + NL(); + + BeginNamespace(GetMigrationNamespace(modelRoot)); + Output("/// "); + Output($"public sealed partial class {modelRoot.EntityContainerName}DbMigrationConfiguration : DbMigrationsConfiguration<{modelRoot.Namespace}.{modelRoot.EntityContainerName}>"); + + Output("{"); + Output("partial void Init();"); + NL(); + + Output("/// "); + Output($"public {modelRoot.EntityContainerName}DbMigrationConfiguration()"); + Output("{"); + Output($"AutomaticMigrationsEnabled = {modelRoot.AutomaticMigrationsEnabled.ToString().ToLower()};"); + Output("AutomaticMigrationDataLossAllowed = false;"); + Output("Init();"); + Output("}"); + + Output("}"); + EndNamespace(modelRoot.Namespace); + } + + string CreateForeignKeySegmentEF6(Association association, List foreignKeyColumns) + { + // foreign key definitions always go in the table representing the Dependent end of the association + // if there is no dependent end (i.e., many-to-many), there are no foreign keys + ModelClass principal; + ModelClass dependent; + + // declaring foreign keys can only happen on ..n multiplicities + // otherwise, primary keys are required to be used, and the framework takes care of that + if (association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany && association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) + return null; + + if (association.SourceRole == EndpointRole.Dependent) + { + dependent = association.Source; + principal = association.Target; + } + else if (association.TargetRole == EndpointRole.Dependent) + { + dependent = association.Target; + principal = association.Source; + } + else + return null; + + string columnNames; + + // shadow properties + if (string.IsNullOrWhiteSpace(association.FKPropertyName)) + { + columnNames = string.Join(", " + , principal.AllIdentityAttributes + .Select(a => $"\"{CreateShadowPropertyName(association, foreignKeyColumns, a)}\"")); + + string[] columnNameList = columnNames.Split(',').Select(n => n.Trim()).ToArray(); + string[] dependentPropertyNames = dependent.AllPropertyNames.ToArray(); + + int existingPropertyCount = columnNameList.Intersect(dependentPropertyNames).Count(); + + if (existingPropertyCount > 0) + { + return existingPropertyCount == 1 + ? $"HasForeignKey(p => {columnNames})" + : $"HasForeignKey(p => new {{ {string.Join(", ", columnNameList.Select(n => $"p.{n}"))} }}"; + } + else + return $"Map(x => x.MapKey({columnNames}))"; + } + + // defined properties + columnNames = association.FKPropertyName.Contains(",") + ? $"new {{ {string.Join(", ", association.FKPropertyName.Split(',').Select(n => $"p.{n.Trim()}"))} }}" + : $"p.{association.FKPropertyName.Trim()}"; + + return $"HasForeignKey(p => {columnNames})"; + } + } +} + + diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFCoreDesigner.cs b/src/DslPackage/TextTemplates/EditingOnly/EFCoreDesigner.cs index 489c46dd6..7af6280a1 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EFCoreDesigner.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EFCoreDesigner.cs @@ -11,7 +11,7 @@ namespace Sawczyn.EFDesigner.EFModel.DslPackage.TextTemplates.EditingOnly [SuppressMessage("ReSharper", "UnusedMember.Global")] partial class EditOnly { - // EFDesigner v2.0.5.6 + // EFDesigner v2.0.5.7 // Copyright (c) 2017-2020 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -704,8 +704,3 @@ string CreateForeignKeySegmentEFCore(Association association, List forei } } } - - - - - diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFCoreDesigner.ttinclude b/src/DslPackage/TextTemplates/EditingOnly/EFCoreDesigner.ttinclude new file mode 100644 index 000000000..428b6c45d --- /dev/null +++ b/src/DslPackage/TextTemplates/EditingOnly/EFCoreDesigner.ttinclude @@ -0,0 +1,705 @@ +using System.Collections.Generic; +using System.Data.Entity.Design.PluralizationServices; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +// ReSharper disable RedundantNameQualifier + +namespace Sawczyn.EFDesigner.EFModel.DslPackage.TextTemplates.EditingOnly +{ + [SuppressMessage("ReSharper", "UnusedMember.Local")] + [SuppressMessage("ReSharper", "UnusedMember.Global")] + partial class EditOnly + { + // EFDesigner v2.0.5.7 + // Copyright (c) 2017-2020 Michael Sawczyn + // https://github.com/msawczyn/EFDesigner + + /************************************************** + * EFCore-specific code generation methods + */ + + void GenerateEFCore(Manager manager, ModelRoot modelRoot) + { + // Entities + string fileNameMarker = string.IsNullOrEmpty(modelRoot.FileNameMarker) ? string.Empty : $".{modelRoot.FileNameMarker}"; + + foreach (ModelClass modelClass in modelRoot.Classes.Where(e => e.GenerateCode)) + { + manager.StartNewFile(Path.Combine(modelClass.EffectiveOutputDirectory, $"{modelClass.Name}{fileNameMarker}.cs")); + WriteClass(modelClass); + } + + // Enums + + foreach (ModelEnum modelEnum in modelRoot.Enums.Where(e => e.GenerateCode)) + { + manager.StartNewFile(Path.Combine(modelEnum.EffectiveOutputDirectory, $"{modelEnum.Name}{fileNameMarker}.cs")); + WriteEnum(modelEnum); + } + + // DatabaseInitializer not yet supported in EF Core + // manager.StartNewFile(Path.Combine(modelRoot.ContextOutputDirectory, $"{modelRoot.EntityContainerName}DatabaseInitializer{fileNameMarker}.cs")); + // WriteDatabaseInitializerEFCore(modelRoot); + + // MigrationConfiguration not yet supported in EF Core + // manager.StartNewFile(Path.Combine(modelRoot.ContextOutputDirectory, $"{modelRoot.EntityContainerName}DbMigrationConfiguration{fileNameMarker}.cs")); + // WriteMigrationConfigurationEFCore(modelRoot); + + manager.StartNewFile(Path.Combine(modelRoot.ContextOutputDirectory, $"{modelRoot.EntityContainerName}{fileNameMarker}.cs")); + WriteDbContextEFCore(modelRoot); + } + + string[] SpatialTypesEFCore + { + get + { + return new[] { + "Geometry" + , "GeometryPoint" + , "GeometryLineString" + , "GeometryPolygon" + , "GeometryCollection" + , "GeometryMultiPoint" + , "GeometryMultiLineString" + , "GeometryMultiPolygon" + }; + } + } + + List GetAdditionalUsingStatementsEFCore(ModelRoot modelRoot) + { + List result = new List(); + List attributeTypes = modelRoot.Classes.SelectMany(c => c.Attributes).Select(a => a.Type).Distinct().ToList(); + + if (attributeTypes.Intersect(modelRoot.SpatialTypes).Any()) + result.Add("using NetTopologySuite.Geometries;"); + + return result; + } + + // Revisit if/when supported in EFCore + + // void WriteDatabaseInitializerEFCore(ModelRoot modelRoot) + // { + // Output("using System.Data.Entity;"); + // NL(); + // + // BeginNamespace(modelRoot.Namespace); + // + // if (modelRoot.DatabaseInitializerType == DatabaseInitializerKind.MigrateDatabaseToLatestVersion) + // Output($"public partial class {modelRoot.EntityContainerName}DatabaseInitializer : MigrateDatabaseToLatestVersion<{modelRoot.EntityContainerName}, {modelRoot.EntityContainerName}DbMigrationConfiguration>"); + // else + // Output($"public partial class {modelRoot.EntityContainerName}DatabaseInitializer : {modelRoot.DatabaseInitializerType}<{modelRoot.EntityContainerName}>"); + // + // Output("{"); + // Output("}"); + // EndNamespace(modelRoot.Namespace); + // } + // + // void WriteMigrationConfigurationEFCore(ModelRoot modelRoot) + // { + // //if (modelRoot.DatabaseInitializerType != DatabaseInitializerKind.MigrateDatabaseToLatestVersion) + // // return; + // + // Output("using System.Data.Entity.Migrations;"); + // NL(); + // + // BeginNamespace(modelRoot.Namespace); + // Output("public sealed partial class {0}DbMigrationConfiguration : DbMigrationsConfiguration<{0}>", modelRoot.EntityContainerName); + // + // Output("{"); + // Output("partial void Init();"); + // NL(); + // + // Output("public {0}DbMigrationConfiguration()", modelRoot.EntityContainerName); + // Output("{"); + // Output("AutomaticMigrationsEnabled = {0};", modelRoot.AutomaticMigrationsEnabled.ToString().ToLower()); + // Output("AutomaticMigrationDataLossAllowed = false;"); + // Output("Init();"); + // Output("}"); + // + // Output("}"); + // EndNamespace(modelRoot.Namespace); + // } + + void WriteDbContextEFCore(ModelRoot modelRoot) + { + List segments = new List(); + ModelClass[] classesWithTables = null; + + // Note: TablePerType and TablePerConcreteType not yet available, but it doesn't hurt for them to be here since they shouldn't make it past the designer's validations + switch (modelRoot.InheritanceStrategy) + { + case CodeStrategy.TablePerType: + classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType && mc.GenerateCode).OrderBy(x => x.Name).ToArray(); + + break; + + case CodeStrategy.TablePerConcreteType: + classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType && !mc.IsAbstract && mc.GenerateCode).OrderBy(x => x.Name).ToArray(); + + break; + + case CodeStrategy.TablePerHierarchy: + classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType && mc.Superclass == null && mc.GenerateCode).OrderBy(x => x.Name).ToArray(); + + break; + } + + Output("using System;"); + Output("using System.Collections.Generic;"); + Output("using System.Linq;"); + Output("using System.ComponentModel.DataAnnotations.Schema;"); + Output("using Microsoft.EntityFrameworkCore;"); + NL(); + + BeginNamespace(modelRoot.Namespace); + + WriteDbContextComments(modelRoot); + + string baseClass = string.IsNullOrWhiteSpace(modelRoot.BaseClass) ? "Microsoft.EntityFrameworkCore.DbContext" : modelRoot.BaseClass; + Output($"{modelRoot.EntityContainerAccess.ToString().ToLower()} partial class {modelRoot.EntityContainerName} : {baseClass}"); + Output("{"); + + if (classesWithTables?.Any() == true) + WriteDbSetsEFCore(modelRoot); + + WriteConstructorsEFCore(modelRoot); + WriteOnConfiguringEFCore(modelRoot, segments); + WriteOnModelCreateEFCore(modelRoot, segments, classesWithTables); + + Output("}"); + + EndNamespace(modelRoot.Namespace); + } + + private void WriteDbSetsEFCore(ModelRoot modelRoot) + { + Output("#region DbSets"); + PluralizationService pluralizationService = ModelRoot.PluralizationService; + + foreach (ModelClass modelClass in modelRoot.Classes.Where(x => !x.IsDependentType).OrderBy(x => x.Name)) + { + string dbSetName; + + if (!string.IsNullOrEmpty(modelClass.DbSetName)) + dbSetName = modelClass.DbSetName; + else + { + dbSetName = pluralizationService?.IsSingular(modelClass.Name) == true + ? pluralizationService.Pluralize(modelClass.Name) + : modelClass.Name; + } + + if (!string.IsNullOrEmpty(modelClass.Summary)) + { + NL(); + Output("/// "); + WriteCommentBody($"Repository for {modelClass.FullName} - {modelClass.Summary}"); + Output("/// "); + } + + Output($"{modelRoot.DbSetAccess.ToString().ToLower()} virtual Microsoft.EntityFrameworkCore.DbSet<{modelClass.FullName}> {dbSetName} {{ get; set; }}"); + } + + Output("#endregion DbSets"); + NL(); + } + + private void WriteConstructorsEFCore(ModelRoot modelRoot) + { + if (!string.IsNullOrEmpty(modelRoot.ConnectionString) || !string.IsNullOrEmpty(modelRoot.ConnectionStringName)) + { + string connectionString = string.IsNullOrEmpty(modelRoot.ConnectionString) + ? $"Name={modelRoot.ConnectionStringName}" + : modelRoot.ConnectionString; + + Output("/// "); + Output("/// Default connection string"); + Output("/// "); + Output($"public static string ConnectionString {{ get; set; }} = @\"{connectionString}\";"); + NL(); + } + + Output("/// "); + Output($"public {modelRoot.EntityContainerName}(DbContextOptions<{modelRoot.EntityContainerName}> options) : base(options)"); + Output("{"); + Output("}"); + NL(); + + Output("partial void CustomInit(DbContextOptionsBuilder optionsBuilder);"); + NL(); + } + + private void WriteOnConfiguringEFCore(ModelRoot modelRoot, List segments) + { + Output("/// "); + Output("protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)"); + Output("{"); + + segments.Clear(); + + if (modelRoot.GetEntityFrameworkPackageVersionNum() >= 2.1 && modelRoot.LazyLoadingEnabled) + segments.Add("UseLazyLoadingProxies()"); + + if (segments.Any()) + { + segments.Insert(0, "optionsBuilder"); + + if (modelRoot.ChopMethodChains) + OutputChopped(segments); + else + Output(string.Join(".", segments) + ";"); + + NL(); + } + + Output("CustomInit(optionsBuilder);"); + Output("}"); + NL(); + } + + private void WriteOnModelCreateEFCore(ModelRoot modelRoot, List segments, ModelClass[] classesWithTables) + { + Output("partial void OnModelCreatingImpl(ModelBuilder modelBuilder);"); + Output("partial void OnModelCreatedImpl(ModelBuilder modelBuilder);"); + NL(); + + Output("/// "); + Output("protected override void OnModelCreating(ModelBuilder modelBuilder)"); + Output("{"); + Output("base.OnModelCreating(modelBuilder);"); + Output("OnModelCreatingImpl(modelBuilder);"); + NL(); + + if (!string.IsNullOrEmpty(modelRoot.DatabaseSchema)) + Output($"modelBuilder.HasDefaultSchema(\"{modelRoot.DatabaseSchema}\");"); + + List visited = new List(); + List foreignKeyColumns = new List(); + List declaredShadowProperties = new List(); + + foreach (ModelClass modelClass in modelRoot.Classes.OrderBy(x => x.Name)) + { + segments.Clear(); + foreignKeyColumns.Clear(); + declaredShadowProperties.Clear(); + NL(); + + // class level + bool isDependent = modelClass.IsDependentType; + segments.Add($"modelBuilder.{(isDependent ? "Owned" : "Entity")}<{modelClass.FullName}>()"); + + foreach (ModelAttribute transient in modelClass.Attributes.Where(x => !x.Persistent)) + segments.Add($"Ignore(t => t.{transient.Name})"); + + if (!isDependent) + { + // note: this must come before the 'ToTable' call or there's a runtime error + if (modelRoot.InheritanceStrategy == CodeStrategy.TablePerConcreteType && modelClass.Superclass != null) + segments.Add("Map(x => x.MapInheritedProperties())"); + + if (classesWithTables.Contains(modelClass)) + { + segments.Add(string.IsNullOrEmpty(modelClass.DatabaseSchema) || modelClass.DatabaseSchema == modelClass.ModelRoot.DatabaseSchema + ? $"ToTable(\"{modelClass.TableName}\")" + : $"ToTable(\"{modelClass.TableName}\", \"{modelClass.DatabaseSchema}\")"); + + // primary key code segments must be output last, since HasKey returns a different type + List identityAttributes = modelClass.AllIdentityAttributes.ToList(); + + if (identityAttributes.Count == 1) + segments.Add($"HasKey(t => t.{identityAttributes[0].Name})"); + else if (identityAttributes.Count > 1) + segments.Add($"HasKey(t => new {{ t.{string.Join(", t.", identityAttributes.Select(ia => ia.Name))} }})"); + } + } + + if (segments.Count > 1 || isDependent) + { + if (modelRoot.ChopMethodChains) + OutputChopped(segments); + else + Output(string.Join(".", segments) + ";"); + } + + if (isDependent) + continue; + + // attribute level + foreach (ModelAttribute modelAttribute in modelClass.Attributes.Where(x => x.Persistent && !SpatialTypesEFCore.Contains(x.Type))) + { + segments.Clear(); + + if ((modelAttribute.MaxLength ?? 0) > 0) + segments.Add($"HasMaxLength({modelAttribute.MaxLength.Value})"); + + if (modelAttribute.Required) + segments.Add("IsRequired()"); + + if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) + segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); + + if (!modelAttribute.AutoProperty) + { + segments.Add($"HasField(\"{modelAttribute.BackingFieldName}\")"); + segments.Add($"UsePropertyAccessMode(PropertyAccessMode.{(modelAttribute.PersistencePoint == PersistencePointType.Field ? "Field" : "Property")})"); + } + + if (!string.IsNullOrEmpty(modelAttribute.ColumnType) && modelAttribute.ColumnType.ToLowerInvariant() != "default") + { + if (modelAttribute.ColumnType.ToLowerInvariant() == "varchar" || modelAttribute.ColumnType.ToLowerInvariant() == "nvarchar" || modelAttribute.ColumnType.ToLowerInvariant() == "char") + segments.Add($"HasColumnType(\"{modelAttribute.ColumnType}({(modelAttribute.MaxLength > 0 ? modelAttribute.MaxLength.ToString() : "max")})\")"); + else + segments.Add($"HasColumnType(\"{modelAttribute.ColumnType}\")"); + } + + if (modelAttribute.IsConcurrencyToken) + segments.Add("IsRowVersion()"); + + if (modelAttribute.IsIdentity) + { + segments.Add(modelAttribute.IdentityType == IdentityType.AutoGenerated + ? "ValueGeneratedOnAdd()" + : "ValueGeneratedNever()"); + } + + if (segments.Any()) + { + segments.Insert(0, $"modelBuilder.{(modelClass.IsDependentType ? "Owned" : "Entity")}<{modelClass.FullName}>()"); + segments.Insert(1, $"Property(t => t.{modelAttribute.Name})"); + + if (modelRoot.ChopMethodChains) + OutputChopped(segments); + else + Output(string.Join(".", segments) + ";"); + } + + if (modelAttribute.Indexed && !modelAttribute.IsIdentity) + { + segments.Clear(); + + segments.Add($"modelBuilder.Entity<{modelClass.FullName}>().HasIndex(t => t.{modelAttribute.Name})"); + + if (modelAttribute.IndexedUnique) + segments.Add("IsUnique()"); + + if (modelRoot.ChopMethodChains) + OutputChopped(segments); + else + Output(string.Join(".", segments) + ";"); + } + } + + bool hasDefinedConcurrencyToken = modelClass.AllAttributes.Any(x => x.IsConcurrencyToken); + + if (!hasDefinedConcurrencyToken && modelClass.EffectiveConcurrency == ConcurrencyOverride.Optimistic) + Output($@"modelBuilder.Entity<{modelClass.FullName}>().Property(""Timestamp"").IsConcurrencyToken();"); + + // Navigation endpoints are distingished as Source and Target. They are also distinguished as Principal + // and Dependent. How do these map? + // In the case of one-to-one or zero-to-one-to-zero-to-one, it's model dependent and the user has to tell us + // In all other cases, we can tell by the cardinalities of the associations + // What matters is the Principal and Dependent classifications, so we look at those. + // Source and Target are accidents of where the user started drawing the association. + + // navigation properties + + // ReSharper disable once LoopCanBePartlyConvertedToQuery + foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) + .OfType() + .Where(x => x.Persistent && !x.Target.IsDependentType)) + { + if (visited.Contains(association)) + continue; + + visited.Add(association); + + segments.Clear(); + segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); + bool required = false; + + switch (association.TargetMultiplicity) // realized by property on source + { + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: + if (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany || association.Source.ModelRoot.IsEFCore5Plus) + segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); + + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.One: + segments.Add($"HasOne(x => x.{association.TargetPropertyName})"); + + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: + segments.Add($"HasOne(x => x.{association.TargetPropertyName})"); + + break; + + //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: + // segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); + // break; + } + + switch (association.SourceMultiplicity) // realized by shadow property on target + { + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: + if (association.Source.ModelRoot.IsEFCore5Plus || association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) + segments.Add("WithMany()"); + + if (association.Source.ModelRoot.IsEFCore5Plus && association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) + { + string tableMap = string.IsNullOrEmpty(association.JoinTableName) ? $"{association.Source.Name}_x_{association.TargetPropertyName}" : association.JoinTableName; + string suffix1 = association.Source == association.Target ? "A" : ""; + string suffix2 = association.Source == association.Target ? "B" : ""; + string sourceMap = string.Join(", ", association.Source.AllIdentityAttributeNames.Select(n => $@"""{association.Source.Name}_{n}{suffix1}""").ToList()); + string targetMap = string.Join(", ", association.Target.AllIdentityAttributeNames.Select(n => $@"""{association.Target.Name}_{n}{suffix2}""").ToList()); + + segments.Add(modelClass == association.Source + ? $@"Map(x => {{ x.ToTable(""{tableMap}""); x.MapLeftKey({sourceMap}); x.MapRightKey({targetMap}); }})" + : $@"Map(x => {{ x.ToTable(""{tableMap}""); x.MapLeftKey({targetMap}); x.MapRightKey({sourceMap}); }})"); + } break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.One: + segments.Add("WithOne()"); + + //segments.Add(association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany + // ? $"HasForeignKey<{association.Source.FullName}>(\"{columnPrefix}{association.TargetPropertyName}_Id\")" + // : $"HasForeignKey(\"{columnPrefix}{association.TargetPropertyName}_Id\")"); + + required = true; + + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: + segments.Add("WithOne()"); + + //segments.Add(association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany + // ? $"HasForeignKey<{association.Source.FullName}>(\"{columnPrefix}{association.TargetPropertyName}_Id\")" + // : $"HasForeignKey(\"{columnPrefix}{association.TargetPropertyName}_Id\")"); + + break; + + //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: + // segments.Add("HasMany()"); + // break; + } + + string foreignKeySegment = CreateForeignKeySegmentEFCore(association, foreignKeyColumns); + + // can't include shadow properties twice + if (foreignKeySegment != null) + { + if (!foreignKeySegment.Contains("MapKey")) + segments.Add(foreignKeySegment); + else if (!declaredShadowProperties.Contains(foreignKeySegment)) + { + declaredShadowProperties.Add(foreignKeySegment); + segments.Add(foreignKeySegment); + } + } + + if (required) + segments.Add("IsRequired()"); + + if (association.TargetRole == EndpointRole.Principal || association.SourceRole == EndpointRole.Principal) + { + DeleteAction deleteAction = association.SourceRole == EndpointRole.Principal + ? association.SourceDeleteAction + : association.TargetDeleteAction; + + switch (deleteAction) + { + case DeleteAction.None: + segments.Add("OnDelete(DeleteBehavior.Restrict)"); + + break; + + case DeleteAction.Cascade: + segments.Add("OnDelete(DeleteBehavior.Cascade)"); + + break; + } + } + + if (modelRoot.ChopMethodChains) + OutputChopped(segments); + else + Output(string.Join(".", segments) + ";"); + } + + foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) + .OfType() + .Where(x => x.Persistent && x.Target.IsDependentType)) + { + if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne || association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One) + Output($"modelBuilder.Entity<{modelClass.FullName}>().OwnsOne(x => x.{association.TargetPropertyName});"); + else + Output($"// Dependent 1-many association seen ({association.TargetPropertyName}). Code generation still unsupported in designer."); + } + + // ReSharper disable once LoopCanBePartlyConvertedToQuery + foreach (BidirectionalAssociation association in Association.GetLinksToSources(modelClass) + .OfType() + .Where(x => x.Persistent)) + { + if (visited.Contains(association)) + continue; + + visited.Add(association); + + // TODO: fix cascade delete + bool required = false; + + segments.Clear(); + segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); + + switch (association.SourceMultiplicity) // realized by property on target + { + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: + if (association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany || association.Source.ModelRoot.IsEFCore5Plus) + segments.Add($"HasMany(x => x.{association.SourcePropertyName})"); + + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.One: + segments.Add($"HasOne(x => x.{association.SourcePropertyName})"); + + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: + segments.Add($"HasOne(x => x.{association.SourcePropertyName})"); + + break; + + //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: + // segments.Add($"HasMany(x => x.{association.SourcePropertyName})"); + // break; + } + + switch (association.TargetMultiplicity) // realized by property on source + { + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: + // TODO: Implement many-to-many + if (association.Source.ModelRoot.IsEFCore5Plus || association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) + segments.Add($"WithMany(x => x.{association.TargetPropertyName})"); + + if (association.Source.ModelRoot.IsEFCore5Plus && association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) + { + string tableMap = string.IsNullOrEmpty(association.JoinTableName) ? $"{association.Source.Name}_x_{association.TargetPropertyName}" : association.JoinTableName; + string suffix1 = association.Source == association.Target ? "A" : ""; + string suffix2 = association.Source == association.Target ? "B" : ""; + string sourceMap = string.Join(", ", association.Source.AllIdentityAttributeNames.Select(n => $@"""{association.Source.Name}_{n}{suffix1}""").ToList()); + string targetMap = string.Join(", ", association.Target.AllIdentityAttributeNames.Select(n => $@"""{association.Target.Name}_{n}{suffix2}""").ToList()); + + segments.Add(modelClass == association.Source + ? $@"Map(x => {{ x.ToTable(""{tableMap}""); x.MapLeftKey({sourceMap}); x.MapRightKey({targetMap}); }})" + : $@"Map(x => {{ x.ToTable(""{tableMap}""); x.MapLeftKey({targetMap}); x.MapRightKey({sourceMap}); }})"); + } + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.One: + segments.Add($"WithOne(x => x.{association.TargetPropertyName})"); + required = true; + + break; + + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: + segments.Add($"WithOne(x => x.{association.TargetPropertyName})"); + + break; + + //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: + // segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); + // break; + } + + string foreignKeySegment = CreateForeignKeySegmentEFCore(association, foreignKeyColumns); + + if (foreignKeySegment != null) + segments.Add(foreignKeySegment); + + if (required) + segments.Add("IsRequired()"); + + if (association.TargetRole == EndpointRole.Principal || association.SourceRole == EndpointRole.Principal) + { + DeleteAction deleteAction = association.SourceRole == EndpointRole.Principal + ? association.SourceDeleteAction + : association.TargetDeleteAction; + + switch (deleteAction) + { + case DeleteAction.None: + segments.Add("OnDelete(DeleteBehavior.Restrict)"); + + break; + + case DeleteAction.Cascade: + segments.Add("OnDelete(DeleteBehavior.Cascade)"); + + break; + } + } + + if (modelRoot.ChopMethodChains) + OutputChopped(segments); + else + Output(string.Join(".", segments) + ";"); + } + } + + NL(); + + Output("OnModelCreatedImpl(modelBuilder);"); + Output("}"); + } + + string CreateForeignKeySegmentEFCore(Association association, List foreignKeyColumns) + { + // foreign key definitions always go in the table representing the Dependent end of the association + // if there is no dependent end (i.e., many-to-many), there are no foreign keys + ModelClass principal; + ModelClass dependent; + + if (association.SourceRole == EndpointRole.Dependent) + { + dependent = association.Source; + principal = association.Target; + } + else if (association.TargetRole == EndpointRole.Dependent) + { + dependent = association.Target; + principal = association.Source; + } + else + return null; + + string columnName; + + if (string.IsNullOrWhiteSpace(association.FKPropertyName)) + { + // shadow properties + columnName = string.Join(", " + , principal.AllIdentityAttributes + .Select(a => CreateShadowPropertyName(association, foreignKeyColumns, a)) + .Select(s => $@"""{s.Trim()}""")); + } + else + { + // defined properties + foreignKeyColumns.AddRange(association.FKPropertyName.Split(',')); + columnName = string.Join(", ", association.FKPropertyName.Split(',').Select(s => $@"""{s.Trim()}""")); + } + + return association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany + && association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany + ? $"HasForeignKey<{dependent.FullName}>({columnName})" + : $"HasForeignKey({columnName})"; + } + } +} + + diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFDesigner.cs b/src/DslPackage/TextTemplates/EditingOnly/EFDesigner.cs index f8c562922..d4bda4c57 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EFDesigner.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EFDesigner.cs @@ -12,7 +12,7 @@ namespace Sawczyn.EFDesigner.EFModel.DslPackage.TextTemplates.EditingOnly [SuppressMessage("ReSharper", "UnusedMember.Global")] partial class EditOnly { - // EFDesigner v2.0.5.6 + // EFDesigner v2.0.5.7 // Copyright (c) 2017-2020 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -285,7 +285,7 @@ void Output(string text) WriteLine(text); if (text.EndsWith("{")) - PushIndent(" "); + PushIndent(ModelRoot.UseTabs() ? "\t" : " "); } void Output(string template, params object[] items) @@ -1037,8 +1037,3 @@ private void WriteDbContextComments(ModelRoot modelRoot) } } } - - - - - diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFDesigner.ttinclude b/src/DslPackage/TextTemplates/EditingOnly/EFDesigner.ttinclude new file mode 100644 index 000000000..8a63743d6 --- /dev/null +++ b/src/DslPackage/TextTemplates/EditingOnly/EFDesigner.ttinclude @@ -0,0 +1,1041 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Security; + +// ReSharper disable RedundantNameQualifier + +namespace Sawczyn.EFDesigner.EFModel.DslPackage.TextTemplates.EditingOnly +{ + [SuppressMessage("ReSharper", "UnusedMember.Local")] + [SuppressMessage("ReSharper", "UnusedMember.Global")] + partial class EditOnly + { + // EFDesigner v2.0.5.7 + // Copyright (c) 2017-2020 Michael Sawczyn + // https://github.com/msawczyn/EFDesigner + + /************************************************** + * Code generation methods and data common to EF6 and EFCore + */ + + string[] NonNullableTypes + { + get + { + return new[] + { + "Binary" + , "Geography" + , "GeographyCollection" + , "GeographyLineString" + , "GeographyMultiLineString" + , "GeographyMultiPoint" + , "GeographyMultiPolygon" + , "GeographyPoint" + , "GeographyPolygon" + , "Geometry" + , "GeometryCollection" + , "GeometryLineString" + , "GeometryMultiLineString" + , "GeometryMultiPoint" + , "GeometryMultiPolygon" + , "GeometryPoint" + , "GeometryPolygon" + , "String" + }; + } + } + + private static string CreateShadowPropertyName(Association association, List foreignKeyColumns, ModelAttribute identityAttribute) + { + string shadowNameBase = association.SourceRole == EndpointRole.Dependent + ? association.TargetPropertyName + : association is BidirectionalAssociation b + ? b.SourcePropertyName + : $"{association.Source.Name}.{association.TargetPropertyName}"; + + string shadowPropertyName = $"{shadowNameBase}_{identityAttribute.Name}"; + + int index = 0; + + while (foreignKeyColumns.Contains(shadowPropertyName)) + shadowPropertyName = $"{shadowNameBase}{++index}_{identityAttribute.Name}"; + + return shadowPropertyName; + } + + bool AllSuperclassesAreNullOrAbstract(ModelClass modelClass) + { + ModelClass superClass = modelClass.Superclass; + + while (superClass != null) + { + if (!superClass.IsAbstract) + return false; + + superClass = superClass.Superclass; + } + + return true; + } + + void BeginNamespace(string ns) + { + if (!string.IsNullOrEmpty(ns)) + { + Output($"namespace {ns}"); + Output("{"); + } + } + + void EndNamespace(string ns) + { + if (!string.IsNullOrEmpty(ns)) + Output("}"); + } + + string FullyQualified(ModelRoot modelRoot, string typeName) + { + string[] parts = typeName.Split('.'); + + if (parts.Length == 1) + return typeName; + + string simpleName = parts[0]; + ModelEnum modelEnum = modelRoot.Store.ElementDirectory.AllElements.OfType().FirstOrDefault(e => e.Name == simpleName); + + return modelEnum != null + ? $"{modelEnum.FullName}.{parts.Last()}" + : typeName; + } + + void GeneratePropertyAnnotations(ModelAttribute modelAttribute) + { + if (modelAttribute.Persistent) + { + if (modelAttribute.IsIdentity) + Output("[Key]"); + + if (modelAttribute.IsConcurrencyToken) + Output("[ConcurrencyCheck]"); + } + else + Output("[NotMapped]"); + + if (modelAttribute.Required) + Output("[Required]"); + + if (modelAttribute.FQPrimitiveType == "string") + { + if (modelAttribute.MinLength > 0) + Output($"[MinLength({modelAttribute.MinLength})]"); + + if (modelAttribute.MaxLength > 0) + { + Output($"[MaxLength({modelAttribute.MaxLength})]"); + Output($"[StringLength({modelAttribute.MaxLength})]"); + } + } + + if (!string.IsNullOrWhiteSpace(modelAttribute.DisplayText)) + Output($"[Display(Name=\"{modelAttribute.DisplayText}\")]"); + } + + string GetFullContainerName(string containerType, string payloadType) + { + string result; + + switch (containerType) + { + case "HashSet": + result = "System.Collections.Generic.HashSet"; + + break; + + case "LinkedList": + result = "System.Collections.Generic.LinkedList"; + + break; + + case "List": + result = "System.Collections.Generic.List"; + + break; + + case "SortedSet": + result = "System.Collections.Generic.SortedSet"; + + break; + + case "Collection": + result = "System.Collections.ObjectModel.Collection"; + + break; + + case "ObservableCollection": + result = "System.Collections.ObjectModel.ObservableCollection"; + + break; + + case "BindingList": + result = "System.ComponentModel.BindingList"; + + break; + + default: + result = containerType; + + break; + } + + if (result.EndsWith("")) + result = result.Replace("", $"<{payloadType}>"); + + return result; + } + + List GetRequiredParameterNames(ModelClass modelClass, bool? publicOnly = null) + { + List requiredParameterNames = modelClass.AllRequiredAttributes + .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) + && !x.IsConcurrencyToken + && (x.SetterVisibility == SetterAccessModifier.Public || publicOnly != false) + && string.IsNullOrEmpty(x.InitialValue)) + .Select(x => x.Name.ToLower()) + .ToList(); + + requiredParameterNames.AddRange(modelClass.AllRequiredNavigationProperties() + .Where(np => np.AssociationObject.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One + || np.AssociationObject.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One) + .Select(x => x.PropertyName.ToLower())); + + requiredParameterNames.AddRange(modelClass.AllRequiredAttributes + .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) + && !x.IsConcurrencyToken + && (x.SetterVisibility == SetterAccessModifier.Public || publicOnly != false) + && !string.IsNullOrEmpty(x.InitialValue)) + .Select(x => x.Name.ToLower())); + + return requiredParameterNames; + } + + List GetRequiredParameters(ModelClass modelClass, bool? haveDefaults, bool? publicOnly = null) + { + List requiredParameters = new List(); + + if (haveDefaults != true) + { + requiredParameters.AddRange(modelClass.AllRequiredAttributes + .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) + && !x.IsConcurrencyToken + && (x.SetterVisibility == SetterAccessModifier.Public || publicOnly != false) + && string.IsNullOrEmpty(x.InitialValue)) + .Select(x => $"{x.FQPrimitiveType} {x.Name.ToLower()}")); + + // don't use 1..1 associations in constructor parameters. Becomes a Catch-22 scenario. + requiredParameters.AddRange(modelClass.AllRequiredNavigationProperties() + .Where(np => np.AssociationObject.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One + || np.AssociationObject.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One) + .Select(x => $"{x.ClassType.FullName} {x.PropertyName.ToLower()}")); + } + + if (haveDefaults != false) + { + requiredParameters.AddRange(modelClass.AllRequiredAttributes + .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) + && !x.IsConcurrencyToken + && (x.SetterVisibility == SetterAccessModifier.Public || publicOnly != false) + && !string.IsNullOrEmpty(x.InitialValue)) + .Select(x => + { + string quote = x.PrimitiveType == "string" + ? "\"" + : x.PrimitiveType == "char" + ? "'" + : string.Empty; + + string value = FullyQualified(modelClass.ModelRoot, quote.Length > 0 + ? x.InitialValue.Trim(quote[0]) + : x.InitialValue); + + return $"{x.FQPrimitiveType} {x.Name.ToLower()} = {quote}{value}{quote}"; + })); + } + + return requiredParameters; + } + + bool IsNullable(ModelAttribute modelAttribute) + { + return !modelAttribute.Required && !modelAttribute.IsIdentity && !modelAttribute.IsConcurrencyToken && !NonNullableTypes.Contains(modelAttribute.Type); + } + + void NL() + { + WriteLine(string.Empty); + } + + void Output(string text) + { + if (text.StartsWith("}")) + PopIndent(); + + WriteLine(text); + + if (text.EndsWith("{")) + PushIndent(ModelRoot.UseTabs() ? "\t" : " "); + } + + void Output(string template, params object[] items) + { + string text = string.Format(template, items); + Output(text); + } + + void OutputChopped(IEnumerable segments) + { + string[] segmentArray = segments?.ToArray() ?? new string[0]; + + if (!segmentArray.Any()) + return; + + int indent = segmentArray[0].IndexOf('.'); + + if (indent == -1) + { + if (segmentArray.Length > 1) + { + segmentArray[0] = $"{segmentArray[0]}.{segmentArray[1]}"; + indent = segmentArray[0].IndexOf('.'); + segmentArray = segmentArray.Where((source, index) => index != 1).ToArray(); + } + } + + for (int index = 1; index < segmentArray.Length; ++index) + segmentArray[index] = $"{new string(' ', indent)}.{segmentArray[index]}"; + + if (!segmentArray[segmentArray.Length - 1].Trim().EndsWith(";")) + segmentArray[segmentArray.Length - 1] = segmentArray[segmentArray.Length - 1] + ";"; + + foreach (string segment in segmentArray) + Output(segment); + } + + void WriteClass(ModelClass modelClass) + { + Output("using System;"); + Output("using System.Collections.Generic;"); + Output("using System.Collections.ObjectModel;"); + Output("using System.ComponentModel;"); + Output("using System.ComponentModel.DataAnnotations;"); + Output("using System.ComponentModel.DataAnnotations.Schema;"); + Output("using System.Linq;"); + Output("using System.Runtime.CompilerServices;"); + List additionalUsings = GetAdditionalUsingStatementsEF6(modelClass.ModelRoot); + + if (additionalUsings.Any()) + Output(string.Join("\n", additionalUsings)); + + NL(); + + BeginNamespace(modelClass.EffectiveNamespace); + + string isAbstract = modelClass.IsAbstract + ? "abstract " + : string.Empty; + + List bases = new List(); + + if (modelClass.Superclass != null) + bases.Add(modelClass.Superclass.FullName); + + if (!string.IsNullOrEmpty(modelClass.CustomInterfaces)) + bases.AddRange(modelClass.CustomInterfaces.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)); + + if (modelClass.ImplementNotify) + bases.Add("INotifyPropertyChanged"); + + string baseClass = string.Join(", ", bases.Select(x => x.Trim())); + + if (!string.IsNullOrEmpty(modelClass.Summary)) + { + Output("/// "); + WriteCommentBody(modelClass.Summary); + Output("/// "); + } + + if (!string.IsNullOrEmpty(modelClass.Description)) + { + Output("/// "); + WriteCommentBody(modelClass.Description); + Output("/// "); + } + + if (!string.IsNullOrWhiteSpace(modelClass.CustomAttributes)) + Output($"[{modelClass.CustomAttributes.Trim('[', ']')}]"); + + Output(baseClass.Length > 0 + ? $"public {isAbstract}partial class {modelClass.Name}: {baseClass}" + : $"public {isAbstract}partial class {modelClass.Name}"); + + Output("{"); + + WriteConstructor(modelClass); + WriteProperties(modelClass); + WriteNavigationProperties(modelClass); + WriteNotifyPropertyChanged(modelClass); + + Output("}"); + + EndNamespace(modelClass.EffectiveNamespace); + NL(); + } + + void WriteCommentBody(string comment) + { + int chunkSize = 80; + string[] parts = comment.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); + + foreach (string value in parts) + { + string text = value; + + while (text.Length > 0) + { + string outputText = text; + + if (outputText.Length > chunkSize) + { + outputText = (text.IndexOf(' ', chunkSize) > 0 + ? text.Substring(0, text.IndexOf(' ', chunkSize)) + : text).Trim(); + + text = text.Substring(outputText.Length).Trim(); + } + else + text = string.Empty; + + //Output("/// " + outputText.Replace("<", "{").Replace(">", "}")); + Output("/// " + SecurityElement.Escape(outputText)); + } + } + } + + void WriteConstructor(ModelClass modelClass) + { + Output("partial void Init();"); + NL(); + + /***********************************************************************/ + // Default constructor + /***********************************************************************/ + + bool hasRequiredParameters = GetRequiredParameters(modelClass, false).Any(); + + bool hasOneToOneAssociations = modelClass.AllRequiredNavigationProperties() + .Any(np => np.AssociationObject.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One + && np.AssociationObject.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One); + + string visibility = (hasRequiredParameters || modelClass.IsAbstract) && !modelClass.IsDependentType + ? "protected" + : "public"; + + if (visibility == "public") + { + Output("/// "); + Output("/// Default constructor"); + Output("/// "); + } + else if (modelClass.IsAbstract) + { + Output("/// "); + Output("/// Default constructor. Protected due to being abstract."); + Output("/// "); + } + else if (hasRequiredParameters) + { + Output("/// "); + Output("/// Default constructor. Protected due to required properties, but present because EF needs it."); + Output("/// "); + } + + List remarks = new List(); + + if (hasOneToOneAssociations) + { + List oneToOneAssociations = modelClass.AllRequiredNavigationProperties() + .Where(np => np.AssociationObject.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One + && np.AssociationObject.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One) + .Select(np => np.AssociationObject) + .ToList(); + + List otherEndsOneToOne = oneToOneAssociations.Where(a => a.Source != modelClass).Select(a => a.Target) + .Union(oneToOneAssociations.Where(a => a.Target != modelClass).Select(a => a.Source)) + .ToList(); + + if (oneToOneAssociations.Any(a => a.Source.Name == modelClass.Name && a.Target.Name == modelClass.Name)) + otherEndsOneToOne.Add(modelClass); + + if (otherEndsOneToOne.Any()) + { + string nameList = otherEndsOneToOne.Count == 1 + ? otherEndsOneToOne.First().Name + : string.Join(", ", otherEndsOneToOne.Take(otherEndsOneToOne.Count - 1).Select(c => c.Name)) + + " and " + + (otherEndsOneToOne.Last().Name != modelClass.Name + ? otherEndsOneToOne.Last().Name + : "itself"); + + remarks.Add($"// NOTE: This class has one-to-one associations with {nameList}."); + remarks.Add("// One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other."); + } + } + + Output(modelClass.Superclass != null + ? $"{visibility} {modelClass.Name}(): base()" + : $"{visibility} {modelClass.Name}()"); + + Output("{"); + + if (remarks.Count > 0) + { + foreach (string remark in remarks) + Output(remark); + + NL(); + } + + WriteDefaultConstructorBody(modelClass); + + Output("}"); + NL(); + + if (visibility != "public" && !modelClass.IsAbstract) + { + Output("/// "); + Output("/// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving."); + Output("/// "); + Output($"public static {modelClass.Name} Create{modelClass.Name}Unsafe()"); + Output("{"); + Output($"return new {modelClass.Name}();"); + Output("}"); + NL(); + } + + /***********************************************************************/ + // Constructor with required parameters (if necessary) + /***********************************************************************/ + + if (hasRequiredParameters) + { + visibility = modelClass.IsAbstract + ? "protected" + : "public"; + + Output("/// "); + Output("/// Public constructor with required data"); + Output("/// "); + + WriteConstructorComments(modelClass); + Output($"{visibility} {modelClass.Name}({string.Join(", ", GetRequiredParameters(modelClass, null))})"); + Output("{"); + + if (remarks.Count > 0) + { + foreach (string remark in remarks) + Output(remark); + + NL(); + } + + foreach (ModelAttribute requiredAttribute in modelClass.AllRequiredAttributes + .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) + && !x.IsConcurrencyToken + && x.SetterVisibility == SetterAccessModifier.Public)) + { + if (requiredAttribute.Type == "String") + Output($"if (string.IsNullOrEmpty({requiredAttribute.Name.ToLower()})) throw new ArgumentNullException(nameof({requiredAttribute.Name.ToLower()}));"); + else if (requiredAttribute.Type.StartsWith("Geo")) + Output($"if ({requiredAttribute.Name.ToLower()} == null) throw new ArgumentNullException(nameof({requiredAttribute.Name.ToLower()}));"); + + Output($"this.{requiredAttribute.Name} = {requiredAttribute.Name.ToLower()};"); + NL(); + } + + foreach (ModelAttribute modelAttribute in modelClass.Attributes.Where(x => x.SetterVisibility == SetterAccessModifier.Public + && !x.Required + && !string.IsNullOrEmpty(x.InitialValue) + && x.InitialValue != "null")) + { + string quote = modelAttribute.Type == "String" + ? "\"" + : modelAttribute.Type == "Char" + ? "'" + : string.Empty; + + Output(quote.Length > 0 + ? $"this.{modelAttribute.Name} = {quote}{FullyQualified(modelClass.ModelRoot, modelAttribute.InitialValue.Trim(quote[0]))}{quote};" + : $"this.{modelAttribute.Name} = {quote}{FullyQualified(modelClass.ModelRoot, modelAttribute.InitialValue)}{quote};"); + } + + foreach (NavigationProperty requiredNavigationProperty in modelClass.AllRequiredNavigationProperties() + .Where(np => np.AssociationObject.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One + || np.AssociationObject.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One)) + { + string parameterName = requiredNavigationProperty.PropertyName.ToLower(); + Output($"if ({parameterName} == null) throw new ArgumentNullException(nameof({parameterName}));"); + + if (requiredNavigationProperty.IsCollection) + Output($"{requiredNavigationProperty.PropertyName}.Add({parameterName});"); + else if (requiredNavigationProperty.ConstructorParameterOnly) + { + UnidirectionalAssociation association = requiredNavigationProperty.AssociationObject as UnidirectionalAssociation; + + Output(association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany + ? $"{requiredNavigationProperty.PropertyName}.{association.TargetPropertyName}.Add(this);" + : $"{requiredNavigationProperty.PropertyName}.{association.TargetPropertyName} = this;"); + } + else + Output($"this.{requiredNavigationProperty.PropertyName} = {parameterName};"); + + NL(); + } + + foreach (NavigationProperty navigationProperty in modelClass.LocalNavigationProperties() + .Where(x => x.AssociationObject.Persistent && (x.IsCollection || x.ClassType.IsDependentType) && !x.ConstructorParameterOnly)) + { + if (!navigationProperty.IsCollection) + Output($"this.{navigationProperty.PropertyName} = new {navigationProperty.ClassType.FullName}();"); + else + { + string collectionType = GetFullContainerName(navigationProperty.AssociationObject.CollectionClass, navigationProperty.ClassType.FullName); + Output($"this.{navigationProperty.PropertyName} = new {collectionType}();"); + } + } + + NL(); + Output("Init();"); + Output("}"); + NL(); + + if (!modelClass.IsAbstract) + { + Output("/// "); + Output("/// Static create function (for use in LINQ queries, etc.)"); + Output("/// "); + WriteConstructorComments(modelClass); + + string newToken = string.Empty; + List requiredParameters = GetRequiredParameters(modelClass, null); + + if (!AllSuperclassesAreNullOrAbstract(modelClass)) + { + List superclassRequiredParameters = GetRequiredParameters(modelClass.Superclass, null); + + if (!requiredParameters.Except(superclassRequiredParameters).Any()) + newToken = "new "; + } + + Output($"public static {newToken}{modelClass.Name} Create({string.Join(", ", GetRequiredParameters(modelClass, null))})"); + Output("{"); + Output($"return new {modelClass.Name}({string.Join(", ", GetRequiredParameterNames(modelClass))});"); + Output("}"); + NL(); + } + } + } + + void WriteConstructorComments(ModelClass modelClass) + { + foreach (ModelAttribute requiredAttribute in modelClass.AllRequiredAttributes.Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) + && !x.IsConcurrencyToken + && x.SetterVisibility == SetterAccessModifier.Public)) + Output($@"/// {SecurityElement.Escape(requiredAttribute.Summary)}"); + + // TODO: Add comment if available + foreach (NavigationProperty requiredNavigationProperty in modelClass.AllRequiredNavigationProperties() + .Where(np => np.AssociationObject.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One + || np.AssociationObject.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One)) + Output($@"/// "); + } + + void WriteDefaultConstructorBody(ModelClass modelClass) + { + int lineCount = 0; + + foreach (ModelAttribute modelAttribute in modelClass.Attributes.Where(x => x.SetterVisibility == SetterAccessModifier.Public + && !string.IsNullOrEmpty(x.InitialValue) + && x.InitialValue.Trim('"') != "null")) + { + string quote = modelAttribute.Type == "String" + ? "\"" + : modelAttribute.Type == "Char" + ? "'" + : string.Empty; + + Output(quote.Length == 1 + ? $"{modelAttribute.Name} = {quote}{FullyQualified(modelClass.ModelRoot, modelAttribute.InitialValue.Trim(quote[0]))}{quote};" + : $"{modelAttribute.Name} = {quote}{FullyQualified(modelClass.ModelRoot, modelAttribute.InitialValue)}{quote};"); + + ++lineCount; + } + + foreach (NavigationProperty navigationProperty in modelClass.LocalNavigationProperties() + .Where(x => x.AssociationObject.Persistent && (x.IsCollection || x.ClassType.IsDependentType) && !x.ConstructorParameterOnly)) + { + if (navigationProperty.ClassType.IsDependentType) + Output($"{navigationProperty.PropertyName} = new {navigationProperty.ClassType.FullName}();"); + else + { + string collectionType = GetFullContainerName(navigationProperty.AssociationObject.CollectionClass, navigationProperty.ClassType.FullName); + Output($"{navigationProperty.PropertyName} = new {collectionType}();"); + } + + ++lineCount; + } + + if (lineCount > 0) + NL(); + + Output("Init();"); + } + + void WriteEnum(ModelEnum modelEnum) + { + Output("using System;"); + NL(); + + BeginNamespace(modelEnum.EffectiveNamespace); + + if (!string.IsNullOrEmpty(modelEnum.Summary)) + { + Output("/// "); + WriteCommentBody(modelEnum.Summary); + Output("/// "); + } + + if (!string.IsNullOrEmpty(modelEnum.Description)) + { + Output("/// "); + WriteCommentBody(modelEnum.Description); + Output("/// "); + } + + if (modelEnum.IsFlags) + Output("[Flags]"); + + if (!string.IsNullOrWhiteSpace(modelEnum.CustomAttributes)) + Output($"[{modelEnum.CustomAttributes.Trim('[', ']')}]"); + + Output($"public enum {modelEnum.Name} : {modelEnum.ValueType}"); + Output("{"); + + ModelEnumValue[] values = modelEnum.Values.ToArray(); + + for (int index = 0; index < values.Length; ++index) + { + if (!string.IsNullOrEmpty(values[index].Summary)) + { + Output("/// "); + WriteCommentBody(values[index].Summary); + Output("/// "); + } + + if (!string.IsNullOrEmpty(values[index].Description)) + { + Output("/// "); + WriteCommentBody(values[index].Description); + Output("/// "); + } + + if (!string.IsNullOrWhiteSpace(values[index].CustomAttributes)) + Output($"[{values[index].CustomAttributes.Trim('[', ']')}]"); + + if (!string.IsNullOrWhiteSpace(values[index].DisplayText)) + Output($"[System.ComponentModel.DataAnnotations.Display(Name=\"{values[index].DisplayText}\")]"); + + Output(string.IsNullOrEmpty(values[index].Value) + ? $"{values[index].Name}{(index < values.Length - 1 ? "," : string.Empty)}" + : $"{values[index].Name} = {values[index].Value}{(index < values.Length - 1 ? "," : string.Empty)}"); + } + + Output("}"); + + EndNamespace(modelEnum.EffectiveNamespace); + } + + void WriteNavigationProperties(ModelClass modelClass) + { + if (!modelClass.LocalNavigationProperties().Any(x => x.AssociationObject.Persistent)) + return; + + Output("/*************************************************************************"); + Output(" * Navigation properties"); + Output(" *************************************************************************/"); + NL(); + + foreach (NavigationProperty navigationProperty in modelClass.LocalNavigationProperties().Where(x => !x.ConstructorParameterOnly)) + { + string type = navigationProperty.IsCollection + ? $"ICollection<{navigationProperty.ClassType.FullName}>" + : navigationProperty.ClassType.FullName; + + if (!navigationProperty.IsCollection && !navigationProperty.IsAutoProperty) + { + Output($"protected {type} _{navigationProperty.PropertyName};"); + Output($"partial void Set{navigationProperty.PropertyName}({type} oldValue, ref {type} newValue);"); + Output($"partial void Get{navigationProperty.PropertyName}(ref {type} result);"); + + NL(); + } + + List comments = new List(); + + if (navigationProperty.Required) + comments.Add("Required"); + + string comment = comments.Count > 0 + ? string.Join(", ", comments) + : string.Empty; + + if (!string.IsNullOrEmpty(navigationProperty.Summary) || !string.IsNullOrEmpty(comment)) + { + Output("/// "); + + if (!string.IsNullOrEmpty(comment) && !string.IsNullOrEmpty(navigationProperty.Summary)) + comment = comment + "
"; + + if (!string.IsNullOrEmpty(comment)) + WriteCommentBody(comment); + + if (!string.IsNullOrEmpty(navigationProperty.Summary)) + WriteCommentBody(navigationProperty.Summary); + + Output("///
"); + } + + if (!string.IsNullOrEmpty(navigationProperty.Description)) + { + Output("/// "); + WriteCommentBody(navigationProperty.Description); + Output("/// "); + } + + if (!string.IsNullOrWhiteSpace(navigationProperty.CustomAttributes)) + Output($"[{navigationProperty.CustomAttributes.Trim('[', ']')}]"); + + if (!string.IsNullOrWhiteSpace(navigationProperty.DisplayText)) + Output($"[Display(Name=\"{navigationProperty.DisplayText}\")]"); + + if (navigationProperty.IsCollection) + Output($"public virtual {type} {navigationProperty.PropertyName} {{ get; protected set; }}"); + else if (navigationProperty.IsAutoProperty) + Output($"public virtual {type} {navigationProperty.PropertyName} {{ get; set; }}"); + else + { + Output($"public virtual {type} {navigationProperty.PropertyName}"); + Output("{"); + Output("get"); + Output("{"); + Output($"{type} value = _{navigationProperty.PropertyName};"); + Output($"Get{navigationProperty.PropertyName}(ref value);"); + Output($"return (_{navigationProperty.PropertyName} = value);"); + Output("}"); + Output("set"); + Output("{"); + Output($"{type} oldValue = _{navigationProperty.PropertyName};"); + Output($"Set{navigationProperty.PropertyName}(oldValue, ref value);"); + Output("if (oldValue != value)"); + Output("{"); + Output($"_{navigationProperty.PropertyName} = value;"); + + if (navigationProperty.ImplementNotify) + Output("OnPropertyChanged();"); + + Output("}"); + Output("}"); + Output("}"); + } + + NL(); + } + } + + void WriteNotifyPropertyChanged(ModelClass modelClass) + { + if (modelClass.ImplementNotify || modelClass.LocalNavigationProperties().Any(x => x.ImplementNotify) || modelClass.Attributes.Any(x => x.ImplementNotify)) + { + string modifier = modelClass.Superclass != null && modelClass.Superclass.ImplementNotify + ? "override" + : "virtual"; + + Output($"public {modifier} event PropertyChangedEventHandler PropertyChanged;"); + NL(); + Output($"protected {modifier} void OnPropertyChanged([CallerMemberName] string propertyName = null)"); + Output("{"); + Output("PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));"); + Output("}"); + NL(); + } + } + + void WriteProperties(ModelClass modelClass) + { + Output("/*************************************************************************"); + Output(" * Properties"); + Output(" *************************************************************************/"); + NL(); + + List segments = new List(); + + foreach (ModelAttribute modelAttribute in modelClass.Attributes) + { + segments.Clear(); + + if (modelAttribute.IsIdentity) + segments.Add("Identity"); + + if (modelAttribute.Indexed) + segments.Add("Indexed"); + + if (modelAttribute.Required || modelAttribute.IsIdentity) + segments.Add("Required"); + + if (modelAttribute.MinLength > 0) + segments.Add($"Min length = {modelAttribute.MinLength}"); + + if (modelAttribute.MaxLength > 0) + segments.Add($"Max length = {modelAttribute.MaxLength}"); + + if (!string.IsNullOrEmpty(modelAttribute.InitialValue)) + { + string quote = modelAttribute.PrimitiveType == "string" + ? "\"" + : modelAttribute.PrimitiveType == "char" + ? "'" + : string.Empty; + + segments.Add($"Default value = {quote}{FullyQualified(modelClass.ModelRoot, modelAttribute.InitialValue.Trim('"'))}{quote}"); + } + + string nullable = IsNullable(modelAttribute) + ? "?" + : string.Empty; + + if (!modelAttribute.IsConcurrencyToken && !modelAttribute.AutoProperty) + { + string visibility = modelAttribute.Indexed ? "internal" : "protected"; + Output("/// "); + Output($"/// Backing field for {modelAttribute.Name}"); + Output("/// "); + Output($"{visibility} {modelAttribute.FQPrimitiveType}{nullable} {modelAttribute.BackingFieldName};"); + Output("/// "); + Output($"/// When provided in a partial class, allows value of {modelAttribute.Name} to be changed before setting."); + Output("/// "); + Output($"partial void Set{modelAttribute.Name}({modelAttribute.FQPrimitiveType}{nullable} oldValue, ref {modelAttribute.FQPrimitiveType}{nullable} newValue);"); + Output("/// "); + Output($"/// When provided in a partial class, allows value of {modelAttribute.Name} to be changed before returning."); + Output("/// "); + Output($"partial void Get{modelAttribute.Name}(ref {modelAttribute.FQPrimitiveType}{nullable} result);"); + + NL(); + } + + if (!string.IsNullOrEmpty(modelAttribute.Summary) || segments.Any()) + { + Output("/// "); + + if (segments.Any()) + WriteCommentBody($"{string.Join(", ", segments)}"); + + if (!string.IsNullOrEmpty(modelAttribute.Summary)) + WriteCommentBody(modelAttribute.Summary); + + Output("/// "); + } + + if (!string.IsNullOrEmpty(modelAttribute.Description)) + { + Output("/// "); + WriteCommentBody(modelAttribute.Description); + Output("/// "); + } + + string setterVisibility = (modelAttribute.IsIdentity && modelAttribute.IdentityType != IdentityType.Manual) || modelAttribute.SetterVisibility == SetterAccessModifier.Protected + ? "protected " + : modelAttribute.SetterVisibility == SetterAccessModifier.Internal + ? "internal " + : string.Empty; + + GeneratePropertyAnnotations(modelAttribute); + + if (!string.IsNullOrWhiteSpace(modelAttribute.CustomAttributes)) + Output($"[{modelAttribute.CustomAttributes.Trim('[', ']')}]"); + + if (modelAttribute.IsConcurrencyToken || modelAttribute.AutoProperty) + Output($"public {modelAttribute.FQPrimitiveType}{nullable} {modelAttribute.Name} {{ get; {setterVisibility}set; }}"); + else + { + Output($"public {modelAttribute.FQPrimitiveType}{nullable} {modelAttribute.Name}"); + Output("{"); + Output("get"); + Output("{"); + Output($"{modelAttribute.FQPrimitiveType}{nullable} value = {modelAttribute.BackingFieldName};"); + Output($"Get{modelAttribute.Name}(ref value);"); + Output($"return ({modelAttribute.BackingFieldName} = value);"); + Output("}"); + Output($"{setterVisibility}set"); + Output("{"); + Output($"{modelAttribute.FQPrimitiveType}{nullable} oldValue = {modelAttribute.BackingFieldName};"); + Output($"Set{modelAttribute.Name}(oldValue, ref value);"); + Output("if (oldValue != value)"); + Output("{"); + Output($"{modelAttribute.BackingFieldName} = value;"); + + if (modelAttribute.ImplementNotify) + Output("OnPropertyChanged();"); + + Output("}"); + Output("}"); + Output("}"); + } + + NL(); + } + + if (!modelClass.AllAttributes.Any(x => x.IsConcurrencyToken) + && (modelClass.Concurrency == ConcurrencyOverride.Optimistic || modelClass.ModelRoot.ConcurrencyDefault == Concurrency.Optimistic)) + { + Output("/// "); + Output("/// Concurrency token"); + Output("/// "); + Output("[Timestamp]"); + Output("public Byte[] Timestamp { get; set; }"); + NL(); + } + } + + private void WriteDbContextComments(ModelRoot modelRoot) + { + if (!string.IsNullOrEmpty(modelRoot.Summary)) + { + Output("/// "); + WriteCommentBody(modelRoot.Summary); + Output("/// "); + + if (!string.IsNullOrEmpty(modelRoot.Description)) + { + Output("/// "); + WriteCommentBody(modelRoot.Description); + Output("/// "); + } + } + else + Output("/// "); + } + } +} + + diff --git a/src/DslPackage/TextTemplates/EditingOnly/MultipleOutputHelper.cs b/src/DslPackage/TextTemplates/EditingOnly/MultipleOutputHelper.cs index b015e775c..f19942863 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/MultipleOutputHelper.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/MultipleOutputHelper.cs @@ -15,7 +15,7 @@ namespace Sawczyn.EFDesigner.EFModel.DslPackage.TextTemplates.EditingOnly [SuppressMessage("ReSharper", "UnusedMember.Global")] partial class EditOnly { - // EFDesigner v2.0.5.6 + // EFDesigner v2.0.5.7 // Copyright (c) 2017-2020 Michael Sawczyn // https://github.com/msawczyn/EFDesigner // @@ -371,8 +371,3 @@ private void ProjectSync(IEnumerable keepFileNames) } } } - - - - - diff --git a/src/DslPackage/TextTemplates/EditingOnly/VSIntegration.cs b/src/DslPackage/TextTemplates/EditingOnly/VSIntegration.cs index a6dcbb453..27d8cc670 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/VSIntegration.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/VSIntegration.cs @@ -12,7 +12,7 @@ namespace Sawczyn.EFDesigner.EFModel.DslPackage.TextTemplates.EditingOnly [SuppressMessage("ReSharper", "UnusedMember.Global")] partial class EditOnly { - // EFDesigner v2.0.5.6 + // EFDesigner v2.0.5.7 // Copyright (c) 2017-2020 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -175,8 +175,3 @@ private IEnumerable RecurseSolutionFolder(Project project) } } } - - - - - diff --git a/src/DslPackage/TextTemplates/MultipleOutputHelper.ttinclude b/src/DslPackage/TextTemplates/MultipleOutputHelper.ttinclude index 91472451e..537d04452 100644 --- a/src/DslPackage/TextTemplates/MultipleOutputHelper.ttinclude +++ b/src/DslPackage/TextTemplates/MultipleOutputHelper.ttinclude @@ -8,364 +8,359 @@ #><#@ import namespace="System.Text" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ -// EFDesigner v2.0.5.6 -// Copyright (c) 2017-2020 Michael Sawczyn -// https://github.com/msawczyn/EFDesigner -// -// based on code from -// https://raw.github.com/damieng/DamienGKit -// http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited - -/************************************************** -* Support for creating multiple output files from a single T4 -*/ - -class Manager -{ - private readonly List files = new List(); - private readonly Block footer = new Block(); - private readonly List generatedFileNames = new List(); - private readonly Block header = new Block(); - private readonly ITextTemplatingEngineHost host; - private readonly StringBuilder template; - - private Block currentBlock; - - private Manager(ITextTemplatingEngineHost host, StringBuilder template) - { - this.host = host; - this.template = template; - this.fileNameMarker = ".generated"; - } - - private string fileNameMarker; - - public string FileNameMarker - { - get + // EFDesigner v2.0.5.7 + // Copyright (c) 2017-2020 Michael Sawczyn + // https://github.com/msawczyn/EFDesigner + // + // based on code from + // https://raw.github.com/damieng/DamienGKit + // http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited + + /************************************************** + * Support for creating multiple output files from a single T4 + */ + + class Manager { - return fileNameMarker; - } - set - { - if (string.IsNullOrEmpty(value)) - fileNameMarker = string.Empty; - else if (!value.StartsWith(".")) - fileNameMarker = $".{value}"; - else - fileNameMarker = value; - } - } - - public virtual string OutputPath - { - get { return Path.GetDirectoryName(host.TemplateFile); } - } - - public virtual string DefaultProjectNamespace - { - get { return null; } - } - - private Block CurrentBlock - { - get { return currentBlock; } - set - { - if (CurrentBlock != null) - EndBlock(); + private readonly List files = new List(); + private readonly Block footer = new Block(); + private readonly List generatedFileNames = new List(); + private readonly Block header = new Block(); + private readonly ITextTemplatingEngineHost host; + private readonly StringBuilder template; - if (value != null) - value.Start = template.Length; + private Block currentBlock; - currentBlock = value; - } - } - - public static Manager Create(ITextTemplatingEngineHost host, StringBuilder template) - { - return host is IServiceProvider - ? new VSManager(host, template) - : new Manager(host, template); - } - - protected virtual void CreateFile(string fileName, string content) - { - if (IsFileContentDifferent(fileName, content)) - File.WriteAllText(fileName, content); - } - - private void EndBlock() - { - if (CurrentBlock == null) - return; - - CurrentBlock.Length = template.Length - CurrentBlock.Start; - - if (CurrentBlock != header && CurrentBlock != footer) - files.Add(CurrentBlock); - - currentBlock = null; - } - - public virtual string GetCustomToolNamespace(string fileName) - { - return null; - } - - private bool IsFileContentDifferent(string fileName, string newContent) - { - return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent); - } - - public virtual void Process(bool split) - { - if (split) - { - EndBlock(); - string headerText = template.ToString(header.Start, header.Length); - string footerText = template.ToString(footer.Start, footer.Length); - files.Reverse(); + private Manager(ITextTemplatingEngineHost host, StringBuilder template) + { + this.host = host; + this.template = template; + this.fileNameMarker = ".generated"; + } - if (!footer.IncludeInDefault) - template.Remove(footer.Start, footer.Length); + private string fileNameMarker; - foreach (Block block in files) + private string FileNameMarker { - string fileName = Path.Combine(OutputPath, block.Name); - string content = headerText + template.ToString(block.Start, block.Length) + footerText; - generatedFileNames.Add(fileName); - CreateFile(fileName, content); - template.Remove(block.Start, block.Length); + get + { + return fileNameMarker; + } + set + { + if (string.IsNullOrEmpty(value)) + fileNameMarker = string.Empty; + else if (!value.StartsWith(".")) + fileNameMarker = $".{value}"; + else + fileNameMarker = value; + } } - if (!header.IncludeInDefault) - template.Remove(header.Start, header.Length); - } - } - - public void StartFooter(bool includeInDefault = true) - { - CurrentBlock = footer; - footer.IncludeInDefault = includeInDefault; - } - - public void StartHeader(bool includeInDefault = true) - { - CurrentBlock = header; - header.IncludeInDefault = includeInDefault; - } - - public void StartNewFile(string name) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - CurrentBlock = new Block {Name = name}; - } - - private class Block - { - public bool IncludeInDefault; - public string Name; - - public int Start - , Length; - } - - private class VSManager : Manager - { - private readonly DTE dte; - private readonly ProjectItem templateProjectItem; - - internal VSManager(ITextTemplatingEngineHost host, StringBuilder template) : base(host, template) - { - IServiceProvider hostServiceProvider = (IServiceProvider)host; + public virtual string OutputPath + { + get { return Path.GetDirectoryName(host.TemplateFile); } + } - if (hostServiceProvider == null) - throw new ArgumentNullException(nameof(host)); + public virtual string DefaultProjectNamespace + { + get { return null; } + } - dte = (DTE)hostServiceProvider.GetCOMService(typeof(DTE)); - templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile); - } + private Block CurrentBlock + { + get { return currentBlock; } + set + { + if (CurrentBlock != null) + EndBlock(); - public override string DefaultProjectNamespace - { - get { return templateProjectItem.ContainingProject.Properties.Item("DefaultNamespace").Value.ToString(); } - } + if (value != null) + value.Start = template.Length; - public override string OutputPath - { - get { return Path.GetDirectoryName(templateProjectItem.ContainingProject.FullName); } - } + currentBlock = value; + } + } - private void CheckoutFileIfRequired(string fileName) - { - SourceControl sc = dte.SourceControl; + public static Manager Create(ITextTemplatingEngineHost host, StringBuilder template) + { + return host is IServiceProvider + ? new VSManager(host, template) + : new Manager(host, template); + } - if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName)) - sc.CheckOutItem(fileName); - } + protected virtual void CreateFile(string fileName, string content) + { + if (IsFileContentDifferent(fileName, content)) + File.WriteAllText(fileName, content); + } - protected override void CreateFile(string fileName, string content) - { - string directory = Path.GetDirectoryName(fileName); + private void EndBlock() + { + if (CurrentBlock == null) + return; - if (!Directory.Exists(directory)) - Directory.CreateDirectory(directory); + CurrentBlock.Length = template.Length - CurrentBlock.Start; - if (IsFileContentDifferent(fileName, content)) - { - CheckoutFileIfRequired(fileName); - File.WriteAllText(fileName, content); + if (CurrentBlock != header && CurrentBlock != footer) + files.Add(CurrentBlock); + + currentBlock = null; } - } - private Dictionary> GetCurrentState() - { - Dictionary> result = new Dictionary>(); - Project currentProject = templateProjectItem.ContainingProject; - string projectDirectory = Path.GetDirectoryName(currentProject.FullName); - string[] existingGeneratedFiles = Directory.GetFiles(projectDirectory, $"*{FileNameMarker}.cs", SearchOption.AllDirectories); + public virtual string GetCustomToolNamespace(string fileName) + { + return null; + } - foreach (string fileName in existingGeneratedFiles) + private bool IsFileContentDifferent(string fileName, string newContent) { - ProjectItem fileItem = dte.Solution.FindProjectItem(fileName); + return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent); + } - if (fileItem != null) + public virtual void Process(bool split) + { + if (split) { - try - { - if (fileItem.Collection.Parent is ProjectItem parentItem) - { - if (!result.ContainsKey(parentItem)) - result.Add(parentItem, new List()); + EndBlock(); + string headerText = template.ToString(header.Start, header.Length); + string footerText = template.ToString(footer.Start, footer.Length); + files.Reverse(); - result[parentItem].Add(fileName); - } + if (!footer.IncludeInDefault) + template.Remove(footer.Start, footer.Length); + + foreach (Block block in files) + { + string fileName = Path.Combine(OutputPath, block.Name); + string content = headerText + template.ToString(block.Start, block.Length) + footerText; + generatedFileNames.Add(fileName); + CreateFile(fileName, content); + template.Remove(block.Start, block.Length); } - catch (InvalidCastException) { } + + if (!header.IncludeInDefault) + template.Remove(header.Start, header.Length); } } - return result; - } + public void StartFooter(bool includeInDefault = true) + { + CurrentBlock = footer; + footer.IncludeInDefault = includeInDefault; + } - private ProjectItem GetOrCreateParentItem(string filePath) - { - if (string.IsNullOrEmpty(filePath)) - return templateProjectItem; + public void StartHeader(bool includeInDefault = true) + { + CurrentBlock = header; + header.IncludeInDefault = includeInDefault; + } - string projectDirectory = Path.GetDirectoryName(templateProjectItem.ContainingProject.FullName); - string fileDirectory = Path.GetDirectoryName(filePath); + public void StartNewFile(string name) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); - if (fileDirectory.ToLower() == projectDirectory.ToLower()) - return templateProjectItem; + CurrentBlock = new Block { Name = name }; + } - ProjectItem result = templateProjectItem; + private class Block + { + public bool IncludeInDefault; + public string Name; - string relativeFilePath = fileDirectory.Substring(projectDirectory.Length + 1); - Queue pathParts = new Queue(relativeFilePath.Split('\\')); - ProjectItems currentItemList = templateProjectItem.ContainingProject.ProjectItems; + public int Start + , Length; + } - while (pathParts.Any()) + private class VSManager : Manager { - bool found = false; - string pathPart = pathParts.Dequeue(); + private readonly DTE dte; + private readonly ProjectItem templateProjectItem; - for (int index = 1; index <= currentItemList.Count; ++index) + internal VSManager(ITextTemplatingEngineHost host, StringBuilder template) : base(host, template) { - ProjectItem item = currentItemList.Item(index); + IServiceProvider hostServiceProvider = (IServiceProvider)host; - if (item.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder && item.Name == pathPart) - { - if (!pathParts.Any()) - result = item; - else - currentItemList = item.ProjectItems; + if (hostServiceProvider == null) + throw new ArgumentNullException(nameof(host)); - found = true; + dte = (DTE)hostServiceProvider.GetCOMService(typeof(DTE)); + templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile); + } - break; + public override string DefaultProjectNamespace + { + get { return templateProjectItem.ContainingProject.Properties.Item("DefaultNamespace").Value.ToString(); } + } + + public override string OutputPath + { + get { return Path.GetDirectoryName(templateProjectItem.ContainingProject.FullName); } + } + + private void CheckoutFileIfRequired(string fileName) + { + SourceControl sc = dte.SourceControl; + + if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName)) + sc.CheckOutItem(fileName); + } + + protected override void CreateFile(string fileName, string content) + { + string directory = Path.GetDirectoryName(fileName); + + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + if (IsFileContentDifferent(fileName, content)) + { + CheckoutFileIfRequired(fileName); + File.WriteAllText(fileName, content); } } - if (!found) + private Dictionary> GetCurrentState() { - ProjectItem newItem = currentItemList.AddFolder(pathPart); + Dictionary> result = new Dictionary>(); + Project currentProject = templateProjectItem.ContainingProject; + string projectDirectory = Path.GetDirectoryName(currentProject.FullName); + string[] existingGeneratedFiles = Directory.GetFiles(projectDirectory, $"*{FileNameMarker}.cs", SearchOption.AllDirectories); - if (!pathParts.Any()) - result = newItem; - else - currentItemList = newItem.ProjectItems; + foreach (string fileName in existingGeneratedFiles) + { + ProjectItem fileItem = dte.Solution.FindProjectItem(fileName); + + if (fileItem != null) + { + try + { + if (fileItem.Collection.Parent is ProjectItem parentItem) + { + if (!result.ContainsKey(parentItem)) + result.Add(parentItem, new List()); + + result[parentItem].Add(fileName); + } + } + catch (InvalidCastException) { } + } + } + + return result; } - } - return result; - } + private ProjectItem GetOrCreateParentItem(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + return templateProjectItem; - private Dictionary> GetTargetState(string[] fileNames) - { - Dictionary> result = new Dictionary> {{templateProjectItem, new List()}}; + string projectDirectory = Path.GetDirectoryName(templateProjectItem.ContainingProject.FullName); + string fileDirectory = Path.GetDirectoryName(filePath); - foreach (string fileName in fileNames) - { - ProjectItem parentItem = GetOrCreateParentItem(fileName); + if (fileDirectory.ToLower() == projectDirectory.ToLower()) + return templateProjectItem; - if (!result.ContainsKey(parentItem)) - result.Add(parentItem, new List()); + ProjectItem result = templateProjectItem; - result[parentItem].Add(fileName); - } + string relativeFilePath = fileDirectory.Substring(projectDirectory.Length + 1); + Queue pathParts = new Queue(relativeFilePath.Split('\\')); + ProjectItems currentItemList = templateProjectItem.ContainingProject.ProjectItems; - return result; - } + while (pathParts.Any()) + { + bool found = false; + string pathPart = pathParts.Dequeue(); - public override void Process(bool split) - { - if (templateProjectItem.ProjectItems == null) - return; + for (int index = 1; index <= currentItemList.Count; ++index) + { + ProjectItem item = currentItemList.Item(index); - base.Process(split); - ProjectSync(generatedFileNames); - } + if (item.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder && item.Name == pathPart) + { + if (!pathParts.Any()) + result = item; + else + currentItemList = item.ProjectItems; - private void ProjectSync(IEnumerable keepFileNames) - { - Dictionary> current = GetCurrentState(); + found = true; - string[] fileNames = keepFileNames as string[] ?? keepFileNames.ToArray(); - Dictionary> target = GetTargetState(fileNames); - List allTargetFiles = target.Keys.SelectMany(k => target[k]).ToList(); + break; + } + } - List existingFiles = new List(); + if (!found) + { + ProjectItem newItem = currentItemList.AddFolder(pathPart); - foreach (ProjectItem parentItem in current.Keys.ToList()) - { - foreach (string filename in current[parentItem]) + if (!pathParts.Any()) + result = newItem; + else + currentItemList = newItem.ProjectItems; + } + } + + return result; + } + + private Dictionary> GetTargetState(string[] fileNames) { - if (!allTargetFiles.Contains(filename) && !fileNames.Contains(filename)) - dte.Solution.FindProjectItem(filename)?.Delete(); - else - existingFiles.Add(filename); + Dictionary> result = new Dictionary> {{templateProjectItem, new List()}}; + + foreach (string fileName in fileNames) + { + ProjectItem parentItem = GetOrCreateParentItem(fileName); + + if (!result.ContainsKey(parentItem)) + result.Add(parentItem, new List()); + + result[parentItem].Add(fileName); + } + + return result; } - } - // just to be safe - existingFiles = existingFiles.Distinct().ToList(); + public override void Process(bool split) + { + if (templateProjectItem.ProjectItems == null) + return; - foreach (ProjectItem parentItem in target.Keys.ToList()) - { - foreach (string filename in target[parentItem].Except(existingFiles).ToList()) - parentItem.ProjectItems.AddFromFile(filename); - } - } - } -} + base.Process(split); + ProjectSync(generatedFileNames); + } -#> + private void ProjectSync(IEnumerable keepFileNames) + { + Dictionary> current = GetCurrentState(); + string[] fileNames = keepFileNames as string[] ?? keepFileNames.ToArray(); + Dictionary> target = GetTargetState(fileNames); + List allTargetFiles = target.Keys.SelectMany(k => target[k]).ToList(); + List existingFiles = new List(); + foreach (ProjectItem parentItem in current.Keys.ToList()) + { + foreach (string filename in current[parentItem]) + { + if (!allTargetFiles.Contains(filename) && !fileNames.Contains(filename)) + dte.Solution.FindProjectItem(filename)?.Delete(); + else + existingFiles.Add(filename); + } + } + + // just to be safe + existingFiles = existingFiles.Distinct().ToList(); + foreach (ProjectItem parentItem in target.Keys.ToList()) + { + foreach (string filename in target[parentItem].Except(existingFiles).ToList()) + parentItem.ProjectItems.AddFromFile(filename); + } + } + } + } +#> \ No newline at end of file diff --git a/src/DslPackage/TextTemplates/VSIntegration.ttinclude b/src/DslPackage/TextTemplates/VSIntegration.ttinclude index dfbc3f3d1..b34b1a748 100644 --- a/src/DslPackage/TextTemplates/VSIntegration.ttinclude +++ b/src/DslPackage/TextTemplates/VSIntegration.ttinclude @@ -9,171 +9,166 @@ #><#@ import namespace="EnvDTE" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ - // EFDesigner v2.0.5.6 - // Copyright (c) 2017-2020 Michael Sawczyn - // https://github.com/msawczyn/EFDesigner - - // this bit is based on EntityFramework Reverse POCO Code First Generator - // Copyright (C) Simon Hughes 2012 - // https://github.com/sjh37/EntityFramework-Reverse-POCO-Code-First-Generator - // - - /************************************************** - * Interactions with Visual Studio - */ - - public IEnumerable GetAllProjects() - { - foreach (Project project in GetSolution().Projects.OfType()) - { - if (project.Kind == EnvDTE.Constants.vsProjectKindSolutionItems) - { - foreach (Project p in RecurseSolutionFolder(project)) - yield return p; - } - else - yield return project; - } - } + // EFDesigner v2.0.5.7 + // Copyright (c) 2017-2020 Michael Sawczyn + // https://github.com/msawczyn/EFDesigner - public Project GetCurrentProject() - { - DTE dte = GetDTE(); + // this bit is based on EntityFramework Reverse POCO Code First Generator + // Copyright (C) Simon Hughes 2012 + // https://github.com/sjh37/EntityFramework-Reverse-POCO-Code-First-Generator + // - ProjectItem projectItem = dte.Solution.FindProjectItem(Host.TemplateFile); + /************************************************** + * Interactions with Visual Studio + */ - if (projectItem?.ContainingProject != null) - return projectItem.ContainingProject; + public IEnumerable GetAllProjects() + { + foreach (Project project in GetSolution().Projects.OfType()) + { + if (project.Kind == EnvDTE.Constants.vsProjectKindSolutionItems) + { + foreach (Project p in RecurseSolutionFolder(project)) + yield return p; + } + else + yield return project; + } + } - // this returns SELECTED (active) project(s) - it may be a different project than the T4 template. - Array activeSolutionProjects = (Array)dte.ActiveSolutionProjects; + public Project GetCurrentProject() + { + DTE dte = GetDTE(); -if (activeSolutionProjects == null) - throw new Exception("DTE.ActiveSolutionProjects returned null"); + ProjectItem projectItem = dte.Solution.FindProjectItem(Host.TemplateFile); - if (activeSolutionProjects.Length > 0) - { - Project dteProject = (Project)activeSolutionProjects.GetValue(0); + if (projectItem?.ContainingProject != null) + return projectItem.ContainingProject; - if (dteProject != null) - return dteProject; - } + // this returns SELECTED (active) project(s) - it may be a different project than the T4 template. + Array activeSolutionProjects = (Array)dte.ActiveSolutionProjects; - throw new InvalidOperationException("Error in GetCurrentProject(). Unable to find project."); - } + if (activeSolutionProjects == null) + throw new Exception("DTE.ActiveSolutionProjects returned null"); - private ProjectItem GetDirectoryItem(string target) - { - DTE dte = GetDTE(); - Array projects = dte?.ActiveSolutionProjects as Array; - Project currentProject = projects?.GetValue(0) as Project; - ProjectItem targetProjectItem = null; + if (activeSolutionProjects.Length > 0) + { + Project dteProject = (Project)activeSolutionProjects.GetValue(0); - if (currentProject != null) - { - string rootDirectory = Path.GetDirectoryName(currentProject.FullName); - Directory.CreateDirectory(Path.Combine(rootDirectory, target)); + if (dteProject != null) + return dteProject; + } - Queue paths = new Queue(target.Split('\\')); - ProjectItems currentItemList = currentProject.ProjectItems; - bool found = false; + throw new InvalidOperationException("Error in GetCurrentProject(). Unable to find project."); + } - while (paths.Any()) + private ProjectItem GetDirectoryItem(string target) { - string path = paths.Dequeue(); + DTE dte = GetDTE(); + Array projects = dte?.ActiveSolutionProjects as Array; + Project currentProject = projects?.GetValue(0) as Project; + ProjectItem targetProjectItem = null; - for (int index = 1; index <= currentItemList.Count; ++index) + if (currentProject != null) { - if (currentItemList.Item(index).Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder) - { - if (!paths.Any()) - targetProjectItem = currentItemList.Item(index); - else - currentItemList = currentItemList.Item(index).ProjectItems; - - found = true; - - break; - } + string rootDirectory = Path.GetDirectoryName(currentProject.FullName); + Directory.CreateDirectory(Path.Combine(rootDirectory, target)); + + Queue paths = new Queue(target.Split('\\')); + ProjectItems currentItemList = currentProject.ProjectItems; + bool found = false; + + while (paths.Any()) + { + string path = paths.Dequeue(); + + for (int index = 1; index <= currentItemList.Count; ++index) + { + if (currentItemList.Item(index).Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder) + { + if (!paths.Any()) + targetProjectItem = currentItemList.Item(index); + else + currentItemList = currentItemList.Item(index).ProjectItems; + + found = true; + + break; + } + } + + if (!found) + { + ProjectItem newItem = currentItemList.AddFolder(path); + + if (!paths.Any()) + targetProjectItem = newItem; + else + currentItemList = newItem.ProjectItems; + } + } } - if (!found) - { - ProjectItem newItem = currentItemList.AddFolder(path); - - if (!paths.Any()) - targetProjectItem = newItem; - else - currentItemList = newItem.ProjectItems; - } + return targetProjectItem; } - } - - return targetProjectItem; - } - - public DTE GetDTE() - { - IServiceProvider serviceProvider = (IServiceProvider)Host; - - if (serviceProvider == null) - throw new Exception("Host property returned unexpected value (null)"); - - DTE dte = (DTE)serviceProvider.GetService(typeof(DTE)); - - if (dte == null) - throw new Exception("Unable to retrieve EnvDTE.DTE"); - - return dte; - } - - private string GetProjectPath(Project project) - { - string fullProjectName = project.FullName; - - if (string.IsNullOrWhiteSpace(fullProjectName)) - return string.Empty; - - try - { - FileInfo info = new FileInfo(fullProjectName); - return info.Directory != null ? info.Directory.FullName : string.Empty; - } - catch - { - WriteLine("// Project " + fullProjectName + " excluded."); - return string.Empty; - } - } - - public Solution GetSolution() - { - return GetDTE().Solution; - } - - private IEnumerable RecurseSolutionFolder(Project project) - { - if (project.ProjectItems == null) - yield break; - - foreach (Project subProject in project.ProjectItems - .Cast() - .Select(projectItem => projectItem.SubProject) - .Where(subProject => subProject != null)) - { - if (subProject.Kind == EnvDTE.Constants.vsProjectKindSolutionItems) + + public EnvDTE.DTE GetDTE() { - foreach (Project p in RecurseSolutionFolder(subProject)) - yield return p; + IServiceProvider serviceProvider = (IServiceProvider)Host; + + if (serviceProvider == null) + throw new Exception("Host property returned unexpected value (null)"); + + DTE dte = (DTE)serviceProvider.GetService(typeof(DTE)); + + if (dte == null) + throw new Exception("Unable to retrieve EnvDTE.DTE"); + + return dte; } - else - yield return subProject; - } - } -#> + private string GetProjectPath(Project project) + { + string fullProjectName = project.FullName; + if (string.IsNullOrWhiteSpace(fullProjectName)) + return string.Empty; + try + { + FileInfo info = new FileInfo(fullProjectName); + return info.Directory != null ? info.Directory.FullName : string.Empty; + } + catch + { + WriteLine("// Project " + fullProjectName + " excluded."); + return string.Empty; + } + } + public Solution GetSolution() + { + return GetDTE().Solution; + } + + private IEnumerable RecurseSolutionFolder(Project project) + { + if (project.ProjectItems == null) + yield break; + foreach (Project subProject in project.ProjectItems + .Cast() + .Select(projectItem => projectItem.SubProject) + .Where(subProject => subProject != null)) + { + if (subProject.Kind == EnvDTE.Constants.vsProjectKindSolutionItems) + { + foreach (Project p in RecurseSolutionFolder(subProject)) + yield return p; + } + else + yield return subProject; + } + } +#> \ No newline at end of file diff --git a/src/DslPackage/source.extension.vsixmanifest b/src/DslPackage/source.extension.vsixmanifest index 20f84cd82..316519a10 100644 --- a/src/DslPackage/source.extension.vsixmanifest +++ b/src/DslPackage/source.extension.vsixmanifest @@ -1,7 +1,7 @@  - + Entity Framework Visual Editor Entity Framework visual editor for EF6, EFCore and beyond. Logo.ico diff --git a/src/Testing/Sandbox/Sandbox_EF6/EFModel1.cs b/src/Testing/Sandbox/Sandbox_EF6/EFModel1.cs index 6153a7d78..2edc99e3e 100644 --- a/src/Testing/Sandbox/Sandbox_EF6/EFModel1.cs +++ b/src/Testing/Sandbox/Sandbox_EF6/EFModel1.cs @@ -1 +1 @@ - +ErrorGeneratingOutput \ No newline at end of file diff --git a/src/Testing/Sandbox/Sandbox_EF6/EFModel1.efmodel b/src/Testing/Sandbox/Sandbox_EF6/EFModel1.efmodel index b99148268..bd2f55f52 100644 --- a/src/Testing/Sandbox/Sandbox_EF6/EFModel1.efmodel +++ b/src/Testing/Sandbox/Sandbox_EF6/EFModel1.efmodel @@ -1,5 +1,5 @@  - + diff --git a/src/Testing/Sandbox/Sandbox_EF6/EFModel1.efmodel.diagramx b/src/Testing/Sandbox/Sandbox_EF6/EFModel1.efmodel.diagramx index 2666d72da..647acc516 100644 --- a/src/Testing/Sandbox/Sandbox_EF6/EFModel1.efmodel.diagramx +++ b/src/Testing/Sandbox/Sandbox_EF6/EFModel1.efmodel.diagramx @@ -3,31 +3,31 @@ - + - - - + + + - + - - - + + + - + - - - + + + - + @@ -35,7 +35,7 @@ - + @@ -43,15 +43,15 @@ - + - - - + + + - + diff --git a/src/UpdateTemplates.ps1 b/src/UpdateTemplates.ps1 new file mode 100644 index 000000000..5f0610ff7 --- /dev/null +++ b/src/UpdateTemplates.ps1 @@ -0,0 +1,24 @@ +$files = + 'DslPackage\TextTemplates\EditingOnly\EF6Designer', + 'DslPackage\TextTemplates\EditingOnly\EFCoreDesigner', + 'DslPackage\TextTemplates\EditingOnly\EFDesigner' + +foreach ($f in $files) { +[regex]::Replace((get-content "$f.cs" -Raw), '(?s)^.+#region Template[\r\n]+(.+)#endregion Template.+$', '<#@ assembly name="System.Core" +#><#@ assembly name="System.Data.Linq" +#><#@ assembly name="EnvDTE" +#><#@ assembly name="System.Xml" +#><#@ assembly name="System.Xml.Linq" +#><#@ import namespace="System.Collections.Generic" +#><#@ import namespace="System.IO" +#><#@ import namespace="System.Linq" +#><#@ import namespace="System.Security" +#><#+ +$1 +#> +') | set-content "$f.ttinclude" + +# Clean up the blank lines at the end of the file that, for some reason, appear there +[regex]::Replace((get-content "$f.ttinclude" -Raw), '\r?\n\r?\n\r?\n[\r\n]*', "") | set-content "$f.ttinclude" +} + diff --git a/src/UpdateVersion.ps1 b/src/UpdateVersion.ps1 index d4b20b44c..4dd251f85 100644 --- a/src/UpdateVersion.ps1 +++ b/src/UpdateVersion.ps1 @@ -27,6 +27,7 @@ $assemblyInfo = foreach ($f in $assemblyInfo) { [regex]::Replace((get-content $f -Raw), '\[assembly:\s*AssemblyVersion\("[\d\.]+"\)\]', '[assembly: AssemblyVersion("'+$version+'")]') | set-content $f [regex]::Replace((get-content $f -Raw), '\[assembly:\s*AssemblyFileVersion\("[\d\.]+"\)\]', '[assembly: AssemblyFileVersion("'+$version+'")]') | set-content $f + [regex]::Replace((get-content $f -Raw), '\r?\n\r?\n\r?\n[\r\n]*', "") | set-content $f # Clean up the blank lines at the end of the file that, for some reason, appear there } $t4 = @@ -43,4 +44,6 @@ $t4 = foreach ($f in $t4) { [regex]::Replace((get-content $f -Raw), '(\s*)// EFDesigner v[\d\.]+', '$1// EFDesigner v'+$version) | set-content $f + [regex]::Replace((get-content $f -Raw), '\r?\n\r?\n\r?\n[\r\n]*', "") | set-content $f } + diff --git a/src/Utilities/ParsingModels/AssemblyInfo.cs b/src/Utilities/ParsingModels/AssemblyInfo.cs index eefc182e2..316d3fc64 100644 --- a/src/Utilities/ParsingModels/AssemblyInfo.cs +++ b/src/Utilities/ParsingModels/AssemblyInfo.cs @@ -33,8 +33,8 @@ // Build Number // Revision // -[assembly: AssemblyVersion("2.0.5.6")] -[assembly: AssemblyFileVersion("2.0.5.6")] +[assembly: AssemblyVersion("2.0.5.7")] +[assembly: AssemblyFileVersion("2.0.5.7")] [assembly: ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)] @@ -49,3 +49,5 @@ + +