From 58c6646c8efb66bd8744589c8bf46469ec8beb76 Mon Sep 17 00:00:00 2001 From: Michael Sawczyn Date: Sun, 16 Jan 2022 16:08:07 -0500 Subject: [PATCH] AssociationClass code complete and tested --- changelog.txt | 7 + .../Gestures/ClassDragMouseAction.cs | 336 ++++++++++++++ .../CustomCode/Gestures/ClassShapeDragData.cs | 28 +- src/Dsl/CustomCode/Partials/Association.cs | 2 +- src/Dsl/CustomCode/Partials/ClassShape.cs | 6 +- src/Dsl/CustomCode/Partials/EFModelDiagram.cs | 33 +- src/Dsl/CustomCode/Partials/ModelClass.cs | 19 +- .../CustomCode/Rules/ClassShapeChangeRules.cs | 122 +++++ .../CustomCode/Rules/ModelClassChangeRules.cs | 8 + .../ModelClassTypeDescriptor.cs | 9 + .../ModelRootTypeDescriptor.cs | 4 + .../Utilities/Import/TextFileProcessor.cs | 2 +- .../CustomCode/Utilities/Nuget/NuGetHelper.cs | 1 + src/Dsl/DslDefinition.dsl | 28 +- src/Dsl/DslDefinition.dsl.diagram | 114 ++--- src/Dsl/GeneratedCode/DomainClasses.cs | 184 +++++++- src/Dsl/GeneratedCode/DomainModel.cs | 2 + src/Dsl/GeneratedCode/DomainModelResx.resx | 24 + src/Dsl/GeneratedCode/EFModelSchema.xsd | 12 + src/Dsl/GeneratedCode/LanguageNameSchema.xsd | 12 + src/Dsl/GeneratedCode/SerializationHelper.cs | 4 +- src/Dsl/GeneratedCode/Serializer.cs | 63 ++- src/Dsl/GeneratedCode/ToolboxHelper.cs | 16 +- src/Dsl/Properties/AssemblyInfo.cs | 6 +- src/Dsl/Resources.Designer.cs | 10 + src/Dsl/Resources.resx | 3 + src/DslPackage/Commands.vsct | 5 +- src/DslPackage/CustomCode/CommandSet.cs | 44 +- .../CustomCode/Partials/EFModelDocData.cs | 186 ++++---- src/DslPackage/DslPackage.csproj | 14 + src/DslPackage/GeneratedCode/Constants.cs | 5 +- src/DslPackage/GeneratedCode/Constants.tt | 3 +- src/DslPackage/GeneratedCode/Package.cs | 2 +- src/DslPackage/GeneratedCode/Package.tt | 2 +- src/DslPackage/Parsers/EF6Parser.exe | Bin 9389765 -> 9389765 bytes src/DslPackage/Parsers/EFCore2Parser.exe | Bin 7382618 -> 7383130 bytes src/DslPackage/Parsers/EFCore3Parser.exe | Bin 9587160 -> 9587160 bytes src/DslPackage/Parsers/EFCore5Parser.exe | Bin 12821089 -> 12821089 bytes src/DslPackage/PreviewImage.png | Bin 0 -> 9604 bytes .../ProjectItemTemplates/EFModel.xsd | 12 + src/DslPackage/Properties/AssemblyInfo.cs | 4 +- .../TextTemplates/EF6Designer.ttinclude | 2 +- .../TextTemplates/EF6ModelGenerator.ttinclude | 2 +- .../EFCore2ModelGenerator.ttinclude | 2 +- .../EFCore3ModelGenerator.ttinclude | 4 +- .../EFCore5ModelGenerator.ttinclude | 402 +++++++++++------ .../TextTemplates/EFCoreDesigner.ttinclude | 2 +- .../EFCoreModelGenerator.ttinclude | 364 +++++++-------- .../TextTemplates/EFDesigner.ttinclude | 2 +- .../EFModelFileManager.ttinclude | 4 +- .../TextTemplates/EFModelGenerator.ttinclude | 110 +++-- .../TextTemplates/EditingOnly/EF6Designer.cs | 4 +- .../EditingOnly/EF6ModelGenerator.cs | 6 +- .../EditingOnly/EFCore2ModelGenerator.cs | 6 +- .../EditingOnly/EFCore3ModelGenerator.cs | 8 +- .../EditingOnly/EFCore5ModelGenerator.cs | 415 ++++++++++++------ .../EditingOnly/EFCoreDesigner.cs | 5 +- .../EditingOnly/EFCoreModelGenerator.cs | 368 ++++++++-------- .../TextTemplates/EditingOnly/EFDesigner.cs | 4 +- .../EditingOnly/EFModelFileManager.cs | 6 +- .../EditingOnly/EFModelGenerator.cs | 113 +++-- .../EditingOnly/MultipleOutputHelper.cs | 6 +- .../EditingOnly/VSIntegration.cs | 16 +- .../MultipleOutputHelper.ttinclude | 2 +- .../TextTemplates/VSIntegration.ttinclude | 12 +- src/DslPackage/source.extension.vsixmanifest | 2 +- .../EFCoreV5/EFCore5Net5/EFModel1.efmodel | 51 ++- .../EFCore5Net5/EFModel1.efmodel.diagramx | 86 +++- .../Generated/AssocClass.generated.cs | 139 ++++++ .../Generated/EFModel1.generated.cs | 67 ++- .../Generated/Entity1.generated.cs | 41 +- .../Generated/Entity2.generated.cs | 70 +++ .../Generated/EntityAbstract.generated.cs | 2 +- .../EntityImplementation.generated.cs | 17 +- .../Generated/EntityRelated.generated.cs | 2 +- .../EFCore5Net5/VSIntegration.ttinclude | 191 ++++++++ .../Sandbox_EFCore/AppDbContext.generated.cs | 40 +- .../Sandbox/Sandbox_EFCore/EFModel1.efmodel | 19 +- .../Sandbox_EFCore/EFModel1.efmodel.diagramx | 39 +- .../Sandbox_EFCore/Entity1.generated.cs | 126 ++++++ .../Sandbox_EFCore/Entity2.generated.cs | 139 ++++++ .../Sandbox_EFCore/Sandbox_EFCore.csproj | 6 + .../Sandbox_EFCore/TestData.generated.cs | 47 +- .../Sandbox_EFCore/TestView.generated.cs | 2 +- src/Utilities/EF6Parser/Parser.cs | 2 +- .../EF6Parser/Properties/AssemblyInfo.cs | 4 +- src/Utilities/EFCore2Parser/Parser.cs | 2 +- .../EFCore2Parser/Properties/AssemblyInfo.cs | 4 +- src/Utilities/EFCore3Parser/Parser.cs | 2 +- .../EFCore3Parser/Properties/AssemblyInfo.cs | 4 +- src/Utilities/EFCore5Parser/Parser.cs | 2 +- .../EFCore5Parser/Properties/AssemblyInfo.cs | 4 +- src/Utilities/ParsingModels/AssemblyInfo.cs | 4 +- src/Utilities/ParsingModels/ParserBase.cs | 5 +- 94 files changed, 3235 insertions(+), 1081 deletions(-) create mode 100644 src/Dsl/CustomCode/Gestures/ClassDragMouseAction.cs create mode 100644 src/Dsl/CustomCode/Rules/ClassShapeChangeRules.cs create mode 100644 src/DslPackage/PreviewImage.png create mode 100644 src/Testing/EFCoreV5/EFCore5Net5/Generated/AssocClass.generated.cs create mode 100644 src/Testing/EFCoreV5/EFCore5Net5/Generated/Entity2.generated.cs create mode 100644 src/Testing/EFCoreV5/EFCore5Net5/VSIntegration.ttinclude create mode 100644 src/Testing/Sandbox/Sandbox_EFCore/Entity1.generated.cs create mode 100644 src/Testing/Sandbox/Sandbox_EFCore/Entity2.generated.cs diff --git a/changelog.txt b/changelog.txt index d38534a49..75792a8d4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +4.1.2 + - Added association classes to model for EFCore >= 5 + - Added table comments for EFCore >= 5 (generation of [Comment] attribute on class) + +4.0.0 + - Added EFCore 6 support + 3.1.0 - Fix generalizations in assembly import - Add DbContextFactory generation on request diff --git a/src/Dsl/CustomCode/Gestures/ClassDragMouseAction.cs b/src/Dsl/CustomCode/Gestures/ClassDragMouseAction.cs new file mode 100644 index 000000000..a384828ed --- /dev/null +++ b/src/Dsl/CustomCode/Gestures/ClassDragMouseAction.cs @@ -0,0 +1,336 @@ +//using System.Collections.Generic; +//using System.Diagnostics; +//using System.Linq; +//using System.Windows.Forms; + +//using Microsoft.VisualStudio.Modeling.Diagrams; + +//namespace Sawczyn.EFDesigner.EFModel +//{ +// public class ClassDragMouseAction : MouseAction +// { +// private readonly ClassShape _classShape; +// private readonly ModelClass _modelClass; + +// public ClassDragMouseAction(ClassShape classShape) : base(classShape.Diagram) +// { +// _classShape = classShape; +// _modelClass = (ModelClass)classShape.ModelElement; +// } + +// /// +// /// Called when a MouseMove event has been dispatched to this MouseAction. +// /// +// /// A DiagramMouseEventArgs that contains event data. +// /// To modify the cursor, override GetCursor. +// /// +// /// To draw feedback for this MouseAction, override DoPaintFeedback. +// /// +// protected override void OnMouseMove(DiagramMouseEventArgs e) +// { +// Debug.WriteLine("OnMouseMove 0"); +// base.OnMouseMove(e); +// Debug.WriteLine("OnMouseMove 1"); +// } + +// /// +// /// Called when a MouseDown event has been dispatched to this MouseAction. +// /// +// /// A DiagramMouseEventArgs that contains event data. +// protected override void OnMouseDown(DiagramMouseEventArgs e) +// { +// Debug.WriteLine("OnMouseDown 0"); +// base.OnMouseDown(e); +// Debug.WriteLine("OnMouseDown 1"); +// } + +// /// +// /// Called when this MouseAction's drag operation has been canceled. +// /// +// /// A MouseActionEventArgs that contains event data. +// /// +// /// Set e.ActionRequest to ActionRequest.CompleteAction to complete the +// /// MouseAction and deactivate it. +// /// +// /// +// /// Set e.ActionRequest to ActionRequest.CancelAction to cancel the +// /// MouseAction and deactivate it. +// /// +// /// +// /// Set e.ActionRequest to ActionRequest.ContinueAction to keep the +// /// MouseAction active. This will place the MouseAction in the +// /// hovering state. +// /// +// protected override void OnDragCanceled(MouseActionEventArgs e) +// { +// Debug.WriteLine("OnDragCanceled 0"); +// base.OnDragCanceled(e); +// Debug.WriteLine("OnDragCanceled 1"); +// } + +// /// +// /// Called when this MouseAction has entered the drag/click-pending state. +// /// +// /// A MouseActionEventArgs that contains event data. +// /// +// /// The drag/click-pending state begins when a MouseDown event occurs while +// /// the MouseAction is in a hovering state or while it is inactive. +// /// +// protected override void OnDragPendingBegun(MouseActionEventArgs e) +// { +// Debug.WriteLine("OnDragPendingBegun 0"); +// base.OnDragPendingBegun(e); +// Debug.WriteLine("OnDragPendingBegun 1"); +// } + +// /// +// /// Called when this MouseAction has exited the drag/click-pending state. +// /// +// /// A MouseActionEventArgs that contains event data. +// /// +// /// The drag/click-pending state ends when the criteria for dragging has been +// /// satisfied, or when a MouseUp has been received before dragging could begin +// /// (in which case the user has clicked), or when a Complete or Cancel event +// /// has been received. +// /// +// protected override void OnDragPendingEnded(MouseActionEventArgs e) +// { +// Debug.WriteLine("OnDragPendingEnded 0"); +// base.OnDragPendingEnded(e); +// Debug.WriteLine("OnDragPendingEnded 1"); +// } + +// /// Called when this MouseAction has been activated. +// /// A DiagramEventArgs that contains event data. +// protected override void OnMouseActionActivated(DiagramEventArgs e) +// { +// Debug.WriteLine("OnMouseActionActivated 0"); +// base.OnMouseActionActivated(e); +// Debug.WriteLine("OnMouseActionActivated 1"); +// } + +// /// +// /// Called when the MouseAction has been canceled and is ready to be deactivated. +// /// +// /// A DiagramEventArgs that contains event data. +// protected override void OnMouseActionCanceled(DiagramEventArgs e) +// { +// Debug.WriteLine("OnMouseActionCanceled 0"); +// base.OnMouseActionCanceled(e); +// Debug.WriteLine("OnMouseActionCanceled 1"); +// } + +// /// +// /// Called when the MouseAction has completed and is ready to be deactivated. +// /// +// /// A DiagramEventArgs that contains event data. +// protected override void OnMouseActionCompleted(DiagramEventArgs e) +// { +// Debug.WriteLine("OnMouseActionCompleted 0"); +// base.OnMouseActionCompleted(e); +// Debug.WriteLine("OnMouseActionCompleted 1"); +// } + +// /// Called when this MouseAction has been deactivated. +// /// A DiagramEventArgs that contains event data. +// protected override void OnMouseActionDeactivated(DiagramEventArgs e) +// { +// Debug.WriteLine("OnMouseActionDeactivated 0"); +// base.OnMouseActionDeactivated(e); +// Debug.WriteLine("OnMouseActionDeactivated 1"); +// } + +// /// +// /// Called when a MouseUp event has been dispatched to this MouseAction. +// /// +// /// A DiagramMouseEventArgs that contains event data. +// /// +// /// If your intent is to respond to a MouseDown + MouseUp +// /// combination that does not include dragging, override the +// /// OnClicked method instead. +// /// (A MouseMove may occur between MouseDown and MouseUp that does +// /// not exceed a drag delta and therefore does not start dragging.) +// /// +// /// +// /// If your intent is to respond to a MouseDown + Drag + MouseUp +// /// combination, override the OnDragCompleted method instead. +// /// (Drag occurs when the MouseMove exceeds a drag delta.) +// /// +// /// +// /// If your intent is to respond to a double-click event, override +// /// the OnDoubleClick method instead. +// /// +// /// +// /// If your intent is to respond to the right-click event (perhaps +// /// to prevent the context menu from appearing), override the +// /// OnContextMenuRequested method instead. +// /// +// protected override void OnMouseUp(DiagramMouseEventArgs e) +// { +// Debug.WriteLine("OnMouseUp 0"); +// base.OnMouseUp(e); +// Debug.WriteLine("OnMouseUp 1"); +// List candidates = _classShape.GetBidirectionalConnectorsUnderShape(); + +// if (candidates.Any()) +// { +// Debug.WriteLine("OnMouseUp 2"); + +// foreach (BidirectionalConnector candidate in candidates) +// { +// Debug.WriteLine("OnMouseUp 3"); +// BidirectionalAssociation association = ((BidirectionalAssociation)candidate.ModelElement); + +// if (BooleanQuestionDisplay.Show(_classShape.Store, $"Make {_modelClass.Name} an association class for {association.GetDisplayText()}?") == true) +// { +// Debug.WriteLine("OnMouseUp 4"); +// _classShape.AddAssociationClass(candidate); +// Complete(e.DiagramClientView); + +// Debug.WriteLine("OnMouseUp 5"); + +// return; +// } +// } +// } + +// Debug.WriteLine("OnMouseUp 6"); +// Cancel(e.DiagramClientView); +// } + +// /// +// /// Called when this MouseAction has entered the dragging state. +// /// +// /// A MouseActionEventArgs that contains event data. +// /// +// /// The dragging state begins when the criteria for dragging has been satisfied. +// /// Typically, the mouse cursor must move beyond a drag delta. +// /// +// protected override void OnDraggingBegun(MouseActionEventArgs e) +// { +// Debug.WriteLine("OnDraggingBegun 0"); +// base.OnDraggingBegun(e); +// Debug.WriteLine("OnDraggingBegun 1"); +// } + +// /// +// /// Called when this MouseAction has exited the dragging state. +// /// +// /// A MouseActionEventArgs that contains event data. +// /// +// /// The dragging state ends when a MouseUp event has been received or when a +// /// Complete or Cancel event has been received. +// /// +// protected override void OnDraggingEnded(MouseActionEventArgs e) +// { +// Debug.WriteLine("OnDraggingEnded 0"); +// base.OnDraggingEnded(e); +// Debug.WriteLine("OnDraggingEnded 1"); +// } + +// /// +// /// Called when this MouseAction's drag operation has completed. +// /// +// /// A MouseActionEventArgs that contains event data. +// protected override void OnDragCompleted(MouseActionEventArgs e) +// { +// Debug.WriteLine("OnDragCompleted 0"); +// base.OnDragCompleted(e); +// Debug.WriteLine("OnDragCompleted 1"); + +// List candidates = _classShape.GetBidirectionalConnectorsUnderShape(); + +// if (candidates.Any()) +// { +// Debug.WriteLine("OnDragCompleted 2"); + +// foreach (BidirectionalConnector candidate in candidates) +// { +// Debug.WriteLine("OnDragCompleted 3"); +// BidirectionalAssociation association = ((BidirectionalAssociation)candidate.ModelElement); + +// if (BooleanQuestionDisplay.Show(_classShape.Store, $"Make {_modelClass.Name} an association class for {association.GetDisplayText()}?") == true) +// { +// Debug.WriteLine("OnDragCompleted 4"); +// _classShape.AddAssociationClass(candidate); +// e.ActionRequest = ActionRequest.CompleteAction; + +// Debug.WriteLine("OnDragCompleted 5"); + +// return; +// } +// } +// } + +// Debug.WriteLine("OnDragCompleted 6"); +// e.ActionRequest = ActionRequest.CancelAction; +// } + +// /// +// /// Gets the cursor to display at the specified mouse position. +// /// +// /// The existing cursor. +// /// The DiagramClientView requesting the cursor. +// /// The cursor position in world units relative to the top-left of the diagram. +// /// The cursor to display at the specified mouse position. +// /// +// /// This method is called by the DiagramClientView if this MouseAction is active or +// /// if it is the potential MouseAction. +// /// +// /// By default, this method returns the currentCursor. +// public override Cursor GetCursor(Cursor currentCursor, DiagramClientView diagramClientView, PointD mousePosition) +// { +// return Cursors.SizeAll; +// } + +// /// +// /// Called by the DiagramClientView to paint the feedback for the MouseAction. +// /// +// /// A DiagramPaintEventArgs that contains event data. +// public override void DoPaintFeedback(DiagramPaintEventArgs e) +// { +// Debug.WriteLine("DoPaintFeedback 0"); +// base.DoPaintFeedback(e); +// Debug.WriteLine("DoPaintFeedback 1"); + +// if (_modelClass != null && _modelClass.CanBecomeAssociationClass()) +// { +// Debug.WriteLine("DoPaintFeedback 2"); +// List connectors = _classShape.GetBidirectionalConnectorsUnderShape(); +// HighlightedShapesCollection highlightedShapes = e.View.HighlightedShapes; + +// if (connectors.Any()) +// { +// Debug.WriteLine("DoPaintFeedback 3"); + +// if (!highlightedShapes.Contains(new DiagramItem(_classShape))) +// { +// Debug.WriteLine("DoPaintFeedback 4"); +// highlightedShapes.Add(new DiagramItem(_classShape)); +// _classShape.Invalidate(); +// } + +// foreach (BidirectionalConnector connector in connectors.Where(c => !highlightedShapes.Contains(new DiagramItem(c)))) +// { +// Debug.WriteLine("DoPaintFeedback 5"); +// highlightedShapes.Add(new DiagramItem(connector)); +// connector.Invalidate(); +// } +// } +// else +// { +// Debug.WriteLine("DoPaintFeedback 6"); +// highlightedShapes.Remove(new DiagramItem(_classShape)); +// _classShape.Invalidate(); + +// foreach (BidirectionalConnector connector in connectors) +// { +// Debug.WriteLine("DoPaintFeedback 7"); +// highlightedShapes.Remove(new DiagramItem(connector)); +// connector.Invalidate(); +// } +// } +// } +// } +// } +//} diff --git a/src/Dsl/CustomCode/Gestures/ClassShapeDragData.cs b/src/Dsl/CustomCode/Gestures/ClassShapeDragData.cs index 64e0498d1..ba7da6148 100644 --- a/src/Dsl/CustomCode/Gestures/ClassShapeDragData.cs +++ b/src/Dsl/CustomCode/Gestures/ClassShapeDragData.cs @@ -66,37 +66,25 @@ public List GetBidirectionalConnectorsUnderShape(PointD internal void HighlightActionableClassShapes(PointD mousePosition) { - List connectors = GetBidirectionalConnectorsUnderShape(mousePosition); - HighlightedShapesCollection highlightedShapes = ClassShape.Diagram.ActiveDiagramView.DiagramClientView.HighlightedShapes; - - DiagramItem classShapeItem = new DiagramItem(ClassShape); - - if (highlightedShapes.Contains(classShapeItem)) - { - highlightedShapes.Remove(classShapeItem); - ClassShape.Invalidate(); - } + EFModelDiagram diagram = ((EFModelDiagram)ClassShape.Diagram); + diagram.Unhighlight(ClassShape); foreach (BidirectionalConnector connector in priorHighlightedConnectors) - { - highlightedShapes.Remove(new DiagramItem(connector)); - connector.Invalidate(); - } + diagram.Unhighlight(connector); priorHighlightedConnectors.Clear(); + List connectors = GetBidirectionalConnectorsUnderShape(mousePosition); + HighlightedShapesCollection highlightedShapes = ClassShape.Diagram.ActiveDiagramView.DiagramClientView.HighlightedShapes; + if (connectors.Any()) { priorHighlightedConnectors.AddRange(connectors); - highlightedShapes.Add(classShapeItem); - ClassShape.Invalidate(); + diagram.Highlight(ClassShape); foreach (BidirectionalConnector connector in connectors.Where(c => !highlightedShapes.Contains(new DiagramItem(c)))) - { - highlightedShapes.Add(new DiagramItem(connector)); - connector.Invalidate(); - } + diagram.Highlight(connector); } } } diff --git a/src/Dsl/CustomCode/Partials/Association.cs b/src/Dsl/CustomCode/Partials/Association.cs index 7634ed388..329849703 100644 --- a/src/Dsl/CustomCode/Partials/Association.cs +++ b/src/Dsl/CustomCode/Partials/Association.cs @@ -109,7 +109,7 @@ public ModelClass Dependent /// public string[] GetForeignKeyPropertyNames() { - return FKPropertyName?.Split(',').Select(n => n.Trim()).ToArray() ?? new string[0]; + return FKPropertyName?.Split(',').Select(n => n.Trim()).ToArray() ?? Array.Empty(); } /// diff --git a/src/Dsl/CustomCode/Partials/ClassShape.cs b/src/Dsl/CustomCode/Partials/ClassShape.cs index 573898c5e..02da614c1 100644 --- a/src/Dsl/CustomCode/Partials/ClassShape.cs +++ b/src/Dsl/CustomCode/Partials/ClassShape.cs @@ -14,7 +14,7 @@ namespace Sawczyn.EFDesigner.EFModel { public partial class ClassShape : IHighlightFromModelExplorer, ICompartmentShapeMouseTarget { - internal static ClassShapeDragData ShapeDragData; + internal static ClassShapeDragData ClassShapeDragData; /// /// Initializes style set resources for this shape type @@ -475,7 +475,7 @@ public override void OnMouseDown(DiagramMouseEventArgs e) base.OnMouseDown(e); if (((ModelClass)ModelElement).CanBecomeAssociationClass()) - ShapeDragData = new ClassShapeDragData(this, e.MousePosition); + ClassShapeDragData = new ClassShapeDragData(this, e.MousePosition); } /// @@ -487,7 +487,7 @@ public override void OnMouseDown(DiagramMouseEventArgs e) /// public override Cursor GetCursor(Cursor currentCursor, DiagramClientView diagramClientView, PointD mousePosition) { - return ShapeDragData?.GetBidirectionalConnectorsUnderShape(mousePosition).Any() == true + return ClassShapeDragData?.GetBidirectionalConnectorsUnderShape(mousePosition).Any() == true ? Cursors.Hand : base.GetCursor(currentCursor, diagramClientView, mousePosition); } diff --git a/src/Dsl/CustomCode/Partials/EFModelDiagram.cs b/src/Dsl/CustomCode/Partials/EFModelDiagram.cs index 3559cf6b5..e4280f759 100644 --- a/src/Dsl/CustomCode/Partials/EFModelDiagram.cs +++ b/src/Dsl/CustomCode/Partials/EFModelDiagram.cs @@ -67,6 +67,20 @@ protected override bool ShouldAddShapeForElement(ModelElement element) private bool ForceAddShape { get; set; } + public void Highlight(ShapeElement shape) + { + ShapeElement highlightShape = DetermineHighlightShape(shape); + if (highlightShape != null) + ActiveDiagramView.DiagramClientView.HighlightedShapes.Add(new DiagramItem(highlightShape)); + } + + public void Unhighlight(ShapeElement shape) + { + ShapeElement highlightShape = DetermineHighlightShape(shape); + if (highlightShape != null) + ActiveDiagramView.DiagramClientView.HighlightedShapes.Remove(new DiagramItem(highlightShape)); + } + public override void OnDragOver(DiagramDragEventArgs diagramDragEventArgs) { base.OnDragOver(diagramDragEventArgs); @@ -74,7 +88,12 @@ public override void OnDragOver(DiagramDragEventArgs diagramDragEventArgs) if (diagramDragEventArgs.Handled) return; - bool isDroppingAssociationClass = ClassShape.ShapeDragData?.GetBidirectionalConnectorsUnderShape(diagramDragEventArgs.MousePosition).Any() == true; + List bidirectionalConnectorsUnderShape = ClassShape.ClassShapeDragData?.GetBidirectionalConnectorsUnderShape(diagramDragEventArgs.MousePosition); + + foreach (BidirectionalConnector connector in bidirectionalConnectorsUnderShape) + Highlight(connector); + + bool isDroppingAssociationClass = bidirectionalConnectorsUnderShape?.Any() == true; if (isDroppingAssociationClass) diagramDragEventArgs.Effect = DragDropEffects.Link; @@ -100,7 +119,7 @@ private bool IsAcceptableDropItem(DiagramDragEventArgs diagramDragEventArgs) && filenames2.All(File.Exists)) || (diagramDragEventArgs.Data.GetData("FileDrop") is string[] filenames3 && filenames3.All(File.Exists)); - bool isDroppingAssociationClass = ClassShape.ShapeDragData?.GetBidirectionalConnectorsUnderShape(diagramDragEventArgs.MousePosition).Any() == true; + bool isDroppingAssociationClass = ClassShape.ClassShapeDragData?.GetBidirectionalConnectorsUnderShape(diagramDragEventArgs.MousePosition).Any() == true; return IsDroppingExternal || isDroppingAssociationClass; } @@ -139,7 +158,7 @@ public override void OnDragDrop(DiagramDragEventArgs diagramDragEventArgs) ModelElement element = (diagramDragEventArgs.Data.GetData("Sawczyn.EFDesigner.EFModel.ModelClass") as ModelElement) ?? (diagramDragEventArgs.Data.GetData("Sawczyn.EFDesigner.EFModel.ModelEnum") as ModelElement); - List candidates = ClassShape.ShapeDragData?.GetBidirectionalConnectorsUnderShape(diagramDragEventArgs.MousePosition); + List candidates = ClassShape.ClassShapeDragData?.GetBidirectionalConnectorsUnderShape(diagramDragEventArgs.MousePosition); // are we creating an association class? if (candidates?.Any() == true) @@ -198,7 +217,7 @@ string BuildMessage(List newElements) void MakeAssociationClass(List possibleConnectors) { - ModelClass modelClass = (ModelClass)ClassShape.ShapeDragData.ClassShape.ModelElement; + ModelClass modelClass = (ModelClass)ClassShape.ClassShapeDragData.ClassShape.ModelElement; using (Transaction t = modelClass.Store.TransactionManager.BeginTransaction("Creating association class")) { @@ -209,7 +228,7 @@ void MakeAssociationClass(List possibleConnectors) if (BooleanQuestionDisplay.Show(Store, $"Make {modelClass.Name} an association class for {association.GetDisplayText()}?") == true) { modelClass.ConvertToAssociationClass(association); - ClassShape.ShapeDragData = null; + ClassShape.ClassShapeDragData = null; break; } @@ -218,7 +237,7 @@ void MakeAssociationClass(List possibleConnectors) t.Commit(); } - ClassShape.ShapeDragData = null; + ClassShape.ClassShapeDragData = null; } void AddToDiagram(ModelElement elementToAdd, PointD atPosition) @@ -348,7 +367,7 @@ public void DisableDiagramRules() public override void OnMouseUp(DiagramMouseEventArgs e) { IsDroppingExternal = false; - ClassShape.ShapeDragData = null; + ClassShape.ClassShapeDragData = null; base.OnMouseUp(e); } diff --git a/src/Dsl/CustomCode/Partials/ModelClass.cs b/src/Dsl/CustomCode/Partials/ModelClass.cs index 03cf8fb60..f1c10646c 100644 --- a/src/Dsl/CustomCode/Partials/ModelClass.cs +++ b/src/Dsl/CustomCode/Partials/ModelClass.cs @@ -522,7 +522,8 @@ internal void MoveAttribute(ModelAttribute attribute, ModelClass destination) internal bool CanBecomeAssociationClass() { - return !AllNavigationProperties().Any() + return ModelRoot.IsEFCore5Plus + && !AllNavigationProperties().Any() && string.IsNullOrEmpty(BaseClass) && !IsAssociationClass && !IsAbstract @@ -554,15 +555,15 @@ internal void ConvertToAssociationClass(BidirectionalAssociation bidirectionalAs } , new[] { - new PropertyAssignment(Association.TargetPropertyNameDomainPropertyId, $"{bidirectionalAssociation.TargetPropertyName}_{Name}") - , new PropertyAssignment(BidirectionalAssociation.SourcePropertyNameDomainPropertyId, $"{bidirectionalAssociation.SourcePropertyName}") - , new PropertyAssignment(Association.TargetDisplayTextDomainPropertyId, $"Association object for {bidirectionalAssociation.TargetPropertyName}") + // new PropertyAssignment(Association.TargetPropertyNameDomainPropertyId, $"{bidirectionalAssociation.TargetPropertyName}_{Name}") + //, new PropertyAssignment(BidirectionalAssociation.SourcePropertyNameDomainPropertyId, $"{bidirectionalAssociation.SourcePropertyName}") + new PropertyAssignment(Association.TargetDisplayTextDomainPropertyId, $"Association object for {bidirectionalAssociation.TargetPropertyName}") , new PropertyAssignment(BidirectionalAssociation.SourceDisplayTextDomainPropertyId, $"Association object for {bidirectionalAssociation.SourcePropertyName}") , new PropertyAssignment(Association.TargetSummaryDomainPropertyId, $"Association class for {bidirectionalAssociation.TargetPropertyName}") , new PropertyAssignment(BidirectionalAssociation.SourceSummaryDomainPropertyId, $"Association class for {bidirectionalAssociation.SourcePropertyName}") , new PropertyAssignment(Association.SourceMultiplicityDomainPropertyId, Multiplicity.One) , new PropertyAssignment(Association.TargetMultiplicityDomainPropertyId, Multiplicity.ZeroMany) - , new PropertyAssignment(Association.FKPropertyNameDomainPropertyId, $"{bidirectionalAssociation.TargetPropertyName}Id") + , new PropertyAssignment(Association.FKPropertyNameDomainPropertyId, $"{bidirectionalAssociation.SourcePropertyName}Id") }); // ReSharper disable once UnusedVariable @@ -575,15 +576,15 @@ internal void ConvertToAssociationClass(BidirectionalAssociation bidirectionalAs } , new[] { - new PropertyAssignment(Association.TargetPropertyNameDomainPropertyId, $"{bidirectionalAssociation.SourcePropertyName}_{Name}") - , new PropertyAssignment(BidirectionalAssociation.SourcePropertyNameDomainPropertyId, $"{bidirectionalAssociation.TargetPropertyName}") - , new PropertyAssignment(Association.TargetDisplayTextDomainPropertyId, $"Association object for {bidirectionalAssociation.SourcePropertyName}") + // new PropertyAssignment(Association.TargetPropertyNameDomainPropertyId, $"{bidirectionalAssociation.SourcePropertyName}_{Name}") + //, new PropertyAssignment(BidirectionalAssociation.SourcePropertyNameDomainPropertyId, $"{bidirectionalAssociation.TargetPropertyName}") + new PropertyAssignment(Association.TargetDisplayTextDomainPropertyId, $"Association object for {bidirectionalAssociation.SourcePropertyName}") , new PropertyAssignment(BidirectionalAssociation.SourceDisplayTextDomainPropertyId, $"Association object for {bidirectionalAssociation.TargetPropertyName}") , new PropertyAssignment(Association.TargetSummaryDomainPropertyId, $"Association class for {bidirectionalAssociation.SourcePropertyName}") , new PropertyAssignment(BidirectionalAssociation.SourceSummaryDomainPropertyId, $"Association class for {bidirectionalAssociation.TargetPropertyName}") , new PropertyAssignment(Association.SourceMultiplicityDomainPropertyId, Multiplicity.One) , new PropertyAssignment(Association.TargetMultiplicityDomainPropertyId, Multiplicity.ZeroMany) - , new PropertyAssignment(Association.FKPropertyNameDomainPropertyId, $"{bidirectionalAssociation.SourcePropertyName}Id") + , new PropertyAssignment(Association.FKPropertyNameDomainPropertyId, $"{bidirectionalAssociation.TargetPropertyName}Id") }); // set some properties in the association class diff --git a/src/Dsl/CustomCode/Rules/ClassShapeChangeRules.cs b/src/Dsl/CustomCode/Rules/ClassShapeChangeRules.cs new file mode 100644 index 000000000..83e24bd83 --- /dev/null +++ b/src/Dsl/CustomCode/Rules/ClassShapeChangeRules.cs @@ -0,0 +1,122 @@ +//using System; +//using System.Collections.Generic; +//using System.IO; +//using System.Linq; +//using System.Windows.Forms; + +//using Microsoft.VisualStudio.Modeling; +//using Microsoft.VisualStudio.Modeling.Diagrams; + +//namespace Sawczyn.EFDesigner.EFModel +//{ +// [RuleOn(typeof(ClassShape), FireTime = TimeToFire.TopLevelCommit)] +// internal class ClassShapeChangeRules : ChangeRule +// { +// /// +// public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) +// { +// base.ElementPropertyChanged(e); + +// ClassShape element = (ClassShape)e.ModelElement; + +// if (element.IsDeleted) +// return; + +// ModelClass modelClass = (ModelClass)element.ModelElement; +// Store store = element.Store; +// Transaction current = store.TransactionManager.CurrentTransaction; + +// if (current.IsSerializing || ModelRoot.BatchUpdating) +// return; + +// if (Equals(e.NewValue, e.OldValue)) +// return; + +// List errorMessages = new List(); + +// switch (e.DomainProperty.Name) +// { +// case "AbsoluteBounds": +// { +// if (modelClass.CanBecomeAssociationClass()) +// { +// RectangleD oldBounds = (RectangleD)e.OldValue; +// RectangleD newBounds = element.AbsoluteBoundingBox; +// double dw = newBounds.Width - oldBounds.Width; +// double dh = newBounds.Height - oldBounds.Height; +// double dx = newBounds.X - oldBounds.X; +// double dy = newBounds.Y - oldBounds.Y; + +// // Moved or resized? If moving, height and width don't change. +// if (dw != 0.0 && dh != 0.0) +// break; + +// List candidates = GetBidirectionalConnectorsUnderShape(modelClass, store, element); + +// if (candidates.Any()) +// { +// foreach (BidirectionalConnector candidate in candidates) +// { +// BidirectionalAssociation association = ((BidirectionalAssociation)candidate.ModelElement); + +// if (BooleanQuestionDisplay.Show(store, $"Make {modelClass.Name} an association class for {association.GetDisplayText()}?") == true) +// { +// ClassShape.AddAssociationClass(candidate, element); +// break; +// } +// } +// } +// } +// } + +// break; +// } + +// errorMessages = errorMessages.Where(m => m != null).ToList(); + +// if (errorMessages.Any()) +// { +// current.Rollback(); +// ErrorDisplay.Show(store, string.Join("\n", errorMessages)); +// } +// } + +// private static List GetBidirectionalConnectorsUnderShape(ModelClass modelClass, Store store, ClassShape element) +// { +// List linkedConnectionObjectIds = modelClass.Store.ElementDirectory.AllElements +// .OfType() +// .Where(x => x.IsAssociationClass) +// .Select(x => x.DescribedAssociationElementId) +// .ToList(); + +// List candidates = store.ElementDirectory.AllElements +// .OfType() +// .Where(c => ((BidirectionalAssociation)c.ModelElement).SourceMultiplicity == Multiplicity.ZeroMany +// && ((BidirectionalAssociation)c.ModelElement).TargetMultiplicity == Multiplicity.ZeroMany +// && !linkedConnectionObjectIds.Contains(((BidirectionalAssociation)c.ModelElement).Id) +// && c.Diagram.Id == element.Diagram.Id +// && c.AbsoluteBoundingBox.IntersectsWith(element.AbsoluteBoundingBox)) +// .ToList(); + +// return candidates; +// } + +// private static Cursor _moveCursor; + +// private static Cursor MoveCursor +// { +// get +// { +// if (_moveCursor == null) +// { +// using (MemoryStream stream = new MemoryStream(Resources.MoveCursor)) +// { +// _moveCursor = new Cursor(stream); +// } +// } + +// return _moveCursor; +// } +// } +// } +//} diff --git a/src/Dsl/CustomCode/Rules/ModelClassChangeRules.cs b/src/Dsl/CustomCode/Rules/ModelClassChangeRules.cs index 2bd682338..9501b9f46 100644 --- a/src/Dsl/CustomCode/Rules/ModelClassChangeRules.cs +++ b/src/Dsl/CustomCode/Rules/ModelClassChangeRules.cs @@ -415,6 +415,14 @@ public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) break; } + case "Summary": + { + if (string.IsNullOrEmpty(element.TableComment) || element.TableComment == (string)e.OldValue) + element.TableComment = element.Summary; + + break; + } + case "TableName": { if (!element.IsDatabaseView) diff --git a/src/Dsl/CustomCode/Type Descriptors/ModelClassTypeDescriptor.cs b/src/Dsl/CustomCode/Type Descriptors/ModelClassTypeDescriptor.cs index 01f3cf7ca..96777a238 100644 --- a/src/Dsl/CustomCode/Type Descriptors/ModelClassTypeDescriptor.cs +++ b/src/Dsl/CustomCode/Type Descriptors/ModelClassTypeDescriptor.cs @@ -34,6 +34,7 @@ private PropertyDescriptorCollection GetCustomProperties(Attribute[] attributes) // things unavailable if pre-EFCore5 if (!modelRoot.IsEFCore5Plus) { + propertyDescriptors.Remove("TableComment"); propertyDescriptors.Remove("IsPropertyBag"); propertyDescriptors.Remove("IsQueryType"); propertyDescriptors.Remove("ExcludeFromMigrations"); @@ -45,15 +46,22 @@ private PropertyDescriptorCollection GetCustomProperties(Attribute[] attributes) } else { + if (!modelRoot.GenerateTableComments) + propertyDescriptors.Remove("TableComment"); + if (modelClass.IsQueryType) { propertyDescriptors.Remove("TableName"); + propertyDescriptors.Remove("TableComment"); propertyDescriptors.Remove("DatabaseSchema"); propertyDescriptors.Remove("Concurrency"); } if (modelClass.IsDatabaseView) + { propertyDescriptors.Remove("TableName"); + propertyDescriptors.Remove("TableComment"); + } else propertyDescriptors.Remove("ViewName"); @@ -75,6 +83,7 @@ private PropertyDescriptorCollection GetCustomProperties(Attribute[] attributes) propertyDescriptors.Remove("IsQueryType"); propertyDescriptors.Remove("IsDatabaseView"); propertyDescriptors.Remove("ViewName"); + propertyDescriptors.Remove("ExcludeFromMigrations"); } //Add the descriptors for the tracking properties diff --git a/src/Dsl/CustomCode/Type Descriptors/ModelRootTypeDescriptor.cs b/src/Dsl/CustomCode/Type Descriptors/ModelRootTypeDescriptor.cs index f0b35888d..e7148836e 100644 --- a/src/Dsl/CustomCode/Type Descriptors/ModelRootTypeDescriptor.cs +++ b/src/Dsl/CustomCode/Type Descriptors/ModelRootTypeDescriptor.cs @@ -28,9 +28,13 @@ private PropertyDescriptorCollection GetCustomProperties(Attribute[] attributes) if (modelRoot.GetEntityFrameworkPackageVersionNum() < 2.1) propertyDescriptors.Remove("LazyLoadingEnabled"); + + if (!modelRoot.IsEFCore5Plus) + propertyDescriptors.Remove("GenerateTableComments"); } else { + propertyDescriptors.Remove("GenerateTableComments"); propertyDescriptors.Remove("GenerateDbContextFactory"); propertyDescriptors.Remove("PropertyAccessModeDefault"); propertyDescriptors.Remove("DatabaseCollationDefault"); diff --git a/src/Dsl/CustomCode/Utilities/Import/TextFileProcessor.cs b/src/Dsl/CustomCode/Utilities/Import/TextFileProcessor.cs index 47a609734..26897c6b8 100644 --- a/src/Dsl/CustomCode/Utilities/Import/TextFileProcessor.cs +++ b/src/Dsl/CustomCode/Utilities/Import/TextFileProcessor.cs @@ -567,7 +567,7 @@ private ModelClass ProcessClass([NotNull] ClassDeclarationSyntax classDecl, List result = new ModelClass(Store , new PropertyAssignment(ModelClass.NameDomainPropertyId, className) , new PropertyAssignment(ModelClass.NamespaceDomainPropertyId, namespaceDecl?.Name.ToString() ?? modelRoot.Namespace) - , new PropertyAssignment(ModelClass.IsAbstractDomainPropertyId, classDecl.DescendantNodes().Any(n => n.Kind() == SyntaxKind.AbstractKeyword))); + , new PropertyAssignment(ModelClass.IsAbstractDomainPropertyId, classDecl.DescendantNodes().Any(n => n.IsKind(SyntaxKind.AbstractKeyword)))); newElements.Add(result); modelRoot.Classes.Add(result); diff --git a/src/Dsl/CustomCode/Utilities/Nuget/NuGetHelper.cs b/src/Dsl/CustomCode/Utilities/Nuget/NuGetHelper.cs index e9c0acb1f..de9c7baa5 100644 --- a/src/Dsl/CustomCode/Utilities/Nuget/NuGetHelper.cs +++ b/src/Dsl/CustomCode/Utilities/Nuget/NuGetHelper.cs @@ -37,6 +37,7 @@ static NuGetHelper() public static Dictionary> EFPackageVersions { get; } public static List NuGetPackageDisplay { get; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "Caller requires synchronous method")] private static void LoadNuGetVersions(EFVersion efVersion, string packageId) { // get NuGet packages with that package id diff --git a/src/Dsl/DslDefinition.dsl b/src/Dsl/DslDefinition.dsl index c9b873ad1..7fa50ca45 100644 --- a/src/Dsl/DslDefinition.dsl +++ b/src/Dsl/DslDefinition.dsl @@ -1,5 +1,5 @@  - + @@ -365,6 +365,11 @@ + + + + + @@ -608,6 +613,19 @@ + + + + + + + + + + + + + @@ -827,7 +845,7 @@ - + @@ -2344,6 +2362,9 @@ + + + @@ -2463,6 +2484,9 @@ + + + diff --git a/src/Dsl/DslDefinition.dsl.diagram b/src/Dsl/DslDefinition.dsl.diagram index 55db46fde..113f27f23 100644 --- a/src/Dsl/DslDefinition.dsl.diagram +++ b/src/Dsl/DslDefinition.dsl.diagram @@ -4,7 +4,7 @@ - + @@ -22,7 +22,7 @@ - + @@ -32,39 +32,39 @@ - + - + - + - + - + - + @@ -104,13 +104,13 @@ - + - + @@ -128,13 +128,13 @@ - + - + @@ -143,7 +143,7 @@ - + @@ -152,7 +152,7 @@ - + @@ -167,31 +167,31 @@ - + - + - + - + - + @@ -209,7 +209,7 @@ - + @@ -230,7 +230,7 @@ - + @@ -248,7 +248,7 @@ - + @@ -257,7 +257,7 @@ - + @@ -333,61 +333,61 @@ - + - + - + - + - + - + - + - + - + @@ -400,13 +400,13 @@ - + - + @@ -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 b971dd4fe..e93221b94 100644 --- a/src/Dsl/GeneratedCode/DomainClasses.cs +++ b/src/Dsl/GeneratedCode/DomainClasses.cs @@ -4692,6 +4692,95 @@ public override sealed void SetValue(ModelRoot element, global::System.Boolean n } } + #endregion + #region GenerateTableComments domain property code + + /// + /// GenerateTableComments domain property Id. + /// + public static readonly global::System.Guid GenerateTableCommentsDomainPropertyId = new global::System.Guid(0x65d03977, 0x253f, 0x4e9c, 0xa9, 0x46, 0x14, 0xd0, 0xe4, 0x16, 0x44, 0x6d); + + /// + /// Storage for GenerateTableComments + /// + private global::System.Boolean generateTableCommentsPropertyStorage = true; + + /// + /// Gets or sets the value of GenerateTableComments domain property. + /// If true, will allow generating [Comment] attributes on C# class + /// + [DslDesign::DisplayNameResource("Sawczyn.EFDesigner.EFModel.ModelRoot/GenerateTableComments.DisplayName", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] + [DslDesign::CategoryResource("Sawczyn.EFDesigner.EFModel.ModelRoot/GenerateTableComments.Category", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] + [DslDesign::DescriptionResource("Sawczyn.EFDesigner.EFModel.ModelRoot/GenerateTableComments.Description", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] + [global::System.ComponentModel.DefaultValue(true)] + [DslModeling::DomainObjectId("65d03977-253f-4e9c-a946-14d0e416446d")] + public global::System.Boolean GenerateTableComments + { + [global::System.Diagnostics.DebuggerStepThrough] + get + { + return generateTableCommentsPropertyStorage; + } + [global::System.Diagnostics.DebuggerStepThrough] + set + { + GenerateTableCommentsPropertyHandler.Instance.SetValue(this, value); + } + } + /// + /// Value handler for the ModelRoot.GenerateTableComments domain property. + /// + internal sealed partial class GenerateTableCommentsPropertyHandler : DslModeling::DomainPropertyValueHandler + { + private GenerateTableCommentsPropertyHandler() { } + + /// + /// Gets the singleton instance of the ModelRoot.GenerateTableComments domain property value handler. + /// + public static readonly GenerateTableCommentsPropertyHandler Instance = new GenerateTableCommentsPropertyHandler(); + + /// + /// Gets the Id of the ModelRoot.GenerateTableComments domain property. + /// + public sealed override global::System.Guid DomainPropertyId + { + [global::System.Diagnostics.DebuggerStepThrough] + get + { + return GenerateTableCommentsDomainPropertyId; + } + } + + /// + /// Gets a strongly-typed value of the property on specified element. + /// + /// Element which owns the property. + /// Property value. + public override sealed global::System.Boolean GetValue(ModelRoot element) + { + if (element == null) throw new global::System.ArgumentNullException("element"); + return element.generateTableCommentsPropertyStorage; + } + + /// + /// Sets property value on an element. + /// + /// Element which owns the property. + /// New property value. + public override sealed void SetValue(ModelRoot element, global::System.Boolean newValue) + { + if (element == null) throw new global::System.ArgumentNullException("element"); + + global::System.Boolean oldValue = GetValue(element); + if (newValue != oldValue) + { + ValueChanging(element, oldValue, newValue); + element.generateTableCommentsPropertyStorage = newValue; + ValueChanged(element, oldValue, newValue); + } + } + } + #endregion #region Comments opposite domain role accessor @@ -7882,6 +7971,95 @@ public override sealed void SetValue(ModelClass element, global::System.Guid new } } + #endregion + #region TableComment domain property code + + /// + /// TableComment domain property Id. + /// + public static readonly global::System.Guid TableCommentDomainPropertyId = new global::System.Guid(0x221e8cda, 0xf6f7, 0x49e6, 0xa0, 0x3c, 0x9e, 0xe9, 0xce, 0x27, 0x55, 0xff); + + /// + /// Storage for TableComment + /// + private global::System.String tableCommentPropertyStorage = string.Empty; + + /// + /// Gets or sets the value of TableComment domain property. + /// Table comment that will be applied to the database, if possible + /// + [System.ComponentModel.Editor(typeof(System.ComponentModel.Design.MultilineStringEditor), typeof(System.Drawing.Design.UITypeEditor))] + [DslDesign::DisplayNameResource("Sawczyn.EFDesigner.EFModel.ModelClass/TableComment.DisplayName", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] + [DslDesign::CategoryResource("Sawczyn.EFDesigner.EFModel.ModelClass/TableComment.Category", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] + [DslDesign::DescriptionResource("Sawczyn.EFDesigner.EFModel.ModelClass/TableComment.Description", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] + [DslModeling::DomainObjectId("221e8cda-f6f7-49e6-a03c-9ee9ce2755ff")] + public global::System.String TableComment + { + [global::System.Diagnostics.DebuggerStepThrough] + get + { + return tableCommentPropertyStorage; + } + [global::System.Diagnostics.DebuggerStepThrough] + set + { + TableCommentPropertyHandler.Instance.SetValue(this, value); + } + } + /// + /// Value handler for the ModelClass.TableComment domain property. + /// + internal sealed partial class TableCommentPropertyHandler : DslModeling::DomainPropertyValueHandler + { + private TableCommentPropertyHandler() { } + + /// + /// Gets the singleton instance of the ModelClass.TableComment domain property value handler. + /// + public static readonly TableCommentPropertyHandler Instance = new TableCommentPropertyHandler(); + + /// + /// Gets the Id of the ModelClass.TableComment domain property. + /// + public sealed override global::System.Guid DomainPropertyId + { + [global::System.Diagnostics.DebuggerStepThrough] + get + { + return TableCommentDomainPropertyId; + } + } + + /// + /// Gets a strongly-typed value of the property on specified element. + /// + /// Element which owns the property. + /// Property value. + public override sealed global::System.String GetValue(ModelClass element) + { + if (element == null) throw new global::System.ArgumentNullException("element"); + return element.tableCommentPropertyStorage; + } + + /// + /// Sets property value on an element. + /// + /// Element which owns the property. + /// New property value. + public override sealed void SetValue(ModelClass element, global::System.String newValue) + { + if (element == null) throw new global::System.ArgumentNullException("element"); + + global::System.String oldValue = GetValue(element); + if (newValue != oldValue) + { + ValueChanging(element, oldValue, newValue); + element.tableCommentPropertyStorage = newValue; + ValueChanged(element, oldValue, newValue); + } + } + } + #endregion #region Targets opposite domain role accessor @@ -10830,7 +11008,7 @@ public override sealed void SetValue(ModelAttribute element, global::System.Bool [global::System.ComponentModel.Browsable(false)] [global::System.ComponentModel.ReadOnly(true)] [DslModeling::DomainObjectId("8282d835-2c0e-4d59-a638-6d3c6e494260")] - internal global::System.Guid IsForeignKeyFor + public global::System.Guid IsForeignKeyFor { [global::System.Diagnostics.DebuggerStepThrough] get @@ -10838,7 +11016,7 @@ public override sealed void SetValue(ModelAttribute element, global::System.Bool return isForeignKeyForPropertyStorage; } [global::System.Diagnostics.DebuggerStepThrough] - set + internal set { IsForeignKeyForPropertyHandler.Instance.SetValue(this, value); } @@ -13794,6 +13972,6 @@ namespace Sawczyn.EFDesigner.EFModel /// partial class ModelRoot { - public const string DSLVersion = "4.0.1.1"; + public const string DSLVersion = "4.1.2.0"; } } diff --git a/src/Dsl/GeneratedCode/DomainModel.cs b/src/Dsl/GeneratedCode/DomainModel.cs index 851fcc7ca..adebf0f3d 100644 --- a/src/Dsl/GeneratedCode/DomainModel.cs +++ b/src/Dsl/GeneratedCode/DomainModel.cs @@ -168,6 +168,7 @@ protected sealed override DomainMemberInfo[] GetGeneratedDomainProperties() new DomainMemberInfo(typeof(ModelRoot), "ShadowKeyNamePattern", ModelRoot.ShadowKeyNamePatternDomainPropertyId, typeof(ModelRoot.ShadowKeyNamePatternPropertyHandler)), new DomainMemberInfo(typeof(ModelRoot), "AutoPropertyDefault", ModelRoot.AutoPropertyDefaultDomainPropertyId, typeof(ModelRoot.AutoPropertyDefaultPropertyHandler)), new DomainMemberInfo(typeof(ModelRoot), "ShowInterfaceIndicators", ModelRoot.ShowInterfaceIndicatorsDomainPropertyId, typeof(ModelRoot.ShowInterfaceIndicatorsPropertyHandler)), + new DomainMemberInfo(typeof(ModelRoot), "GenerateTableComments", ModelRoot.GenerateTableCommentsDomainPropertyId, typeof(ModelRoot.GenerateTableCommentsPropertyHandler)), new DomainMemberInfo(typeof(ModelClass), "IsAbstract", ModelClass.IsAbstractDomainPropertyId, typeof(ModelClass.IsAbstractPropertyHandler)), new DomainMemberInfo(typeof(ModelClass), "TableName", ModelClass.TableNameDomainPropertyId, typeof(ModelClass.TableNamePropertyHandler)), new DomainMemberInfo(typeof(ModelClass), "DatabaseSchema", ModelClass.DatabaseSchemaDomainPropertyId, typeof(ModelClass.DatabaseSchemaPropertyHandler)), @@ -201,6 +202,7 @@ protected sealed override DomainMemberInfo[] GetGeneratedDomainProperties() new DomainMemberInfo(typeof(ModelClass), "UseTemporalTables", ModelClass.UseTemporalTablesDomainPropertyId, typeof(ModelClass.UseTemporalTablesPropertyHandler)), new DomainMemberInfo(typeof(ModelClass), "IsAssociationClass", ModelClass.IsAssociationClassDomainPropertyId, typeof(ModelClass.IsAssociationClassPropertyHandler)), new DomainMemberInfo(typeof(ModelClass), "DescribedAssociationElementId", ModelClass.DescribedAssociationElementIdDomainPropertyId, typeof(ModelClass.DescribedAssociationElementIdPropertyHandler)), + new DomainMemberInfo(typeof(ModelClass), "TableComment", ModelClass.TableCommentDomainPropertyId, typeof(ModelClass.TableCommentPropertyHandler)), new DomainMemberInfo(typeof(ModelAttribute), "Type", ModelAttribute.TypeDomainPropertyId, typeof(ModelAttribute.TypePropertyHandler)), new DomainMemberInfo(typeof(ModelAttribute), "InitialValue", ModelAttribute.InitialValueDomainPropertyId, typeof(ModelAttribute.InitialValuePropertyHandler)), new DomainMemberInfo(typeof(ModelAttribute), "IsIdentity", ModelAttribute.IsIdentityDomainPropertyId, typeof(ModelAttribute.IsIdentityPropertyHandler)), diff --git a/src/Dsl/GeneratedCode/DomainModelResx.resx b/src/Dsl/GeneratedCode/DomainModelResx.resx index 2a31e5676..5d123fdb7 100644 --- a/src/Dsl/GeneratedCode/DomainModelResx.resx +++ b/src/Dsl/GeneratedCode/DomainModelResx.resx @@ -753,6 +753,18 @@ Designer Category for DomainProperty 'ShowInterfaceIndicators' on DomainClass 'ModelRoot' + + If true, will allow generating [Comment] attributes on C# class + Description for DomainProperty 'GenerateTableComments' on DomainClass 'ModelRoot' + + + Generate Table Comments + DisplayName for DomainProperty 'GenerateTableComments' on DomainClass 'ModelRoot' + + + Database + Category for DomainProperty 'GenerateTableComments' on DomainClass 'ModelRoot' + Description for DomainClass 'ModelClass' @@ -1125,6 +1137,18 @@ Described Association Element Id DisplayName for DomainProperty 'DescribedAssociationElementId' on DomainClass 'ModelClass' + + Table comment that will be applied to the database, if possible + Description for DomainProperty 'TableComment' on DomainClass 'ModelClass' + + + Table Comment + DisplayName for DomainProperty 'TableComment' on DomainClass 'ModelClass' + + + Database + Category for DomainProperty 'TableComment' on DomainClass 'ModelClass' + An attribute of a class. Description for DomainClass 'ModelAttribute' diff --git a/src/Dsl/GeneratedCode/EFModelSchema.xsd b/src/Dsl/GeneratedCode/EFModelSchema.xsd index 1630caa4f..663111d2c 100644 --- a/src/Dsl/GeneratedCode/EFModelSchema.xsd +++ b/src/Dsl/GeneratedCode/EFModelSchema.xsd @@ -387,6 +387,12 @@ If true, will display a UML interface glyph on classes that have custom interfaces defined + + + + If true, will allow generating [Comment] attributes on C# class + + @@ -664,6 +670,12 @@ When IsAssociationClass is true, the element id of the association this entity extends + + + + Table comment that will be applied to the database, if possible + + diff --git a/src/Dsl/GeneratedCode/LanguageNameSchema.xsd b/src/Dsl/GeneratedCode/LanguageNameSchema.xsd index 1630caa4f..663111d2c 100644 --- a/src/Dsl/GeneratedCode/LanguageNameSchema.xsd +++ b/src/Dsl/GeneratedCode/LanguageNameSchema.xsd @@ -387,6 +387,12 @@ If true, will display a UML interface glyph on classes that have custom interfaces defined + + + + If true, will allow generating [Comment] attributes on C# class + + @@ -664,6 +670,12 @@ When IsAssociationClass is true, the element id of the association this entity extends + + + + Table comment that will be applied to the database, if possible + + diff --git a/src/Dsl/GeneratedCode/SerializationHelper.cs b/src/Dsl/GeneratedCode/SerializationHelper.cs index db218641b..13a75c0b4 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("4.0.1.1"); + rootElementSettings.Version = new global::System.Version("4.1.2.0"); // 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("4.0.1.1"); + global::System.Version expectedVersion = new global::System.Version("4.1.2.0"); string dslVersionStr = reader.GetAttribute("dslVersion"); if (dslVersionStr != null) { diff --git a/src/Dsl/GeneratedCode/Serializer.cs b/src/Dsl/GeneratedCode/Serializer.cs index 967542813..9a8b4bf95 100644 --- a/src/Dsl/GeneratedCode/Serializer.cs +++ b/src/Dsl/GeneratedCode/Serializer.cs @@ -1004,6 +1004,23 @@ protected override void ReadPropertiesFromAttributes(DslModeling::SerializationC } } } + // GenerateTableComments + if (!serializationContext.Result.Failed) + { + string attribGenerateTableComments = EFModelSerializationHelper.Instance.ReadAttribute(serializationContext, element, reader, "generateTableComments"); + if (attribGenerateTableComments != null) + { + global::System.Boolean valueOfGenerateTableComments; + if (DslModeling::SerializationUtilities.TryGetValue(serializationContext, attribGenerateTableComments, out valueOfGenerateTableComments)) + { + instanceOfModelRoot.GenerateTableComments = valueOfGenerateTableComments; + } + else + { // Invalid property value, ignored. + EFModelSerializationBehaviorSerializationMessages.IgnoredPropertyValue(serializationContext, reader, "generateTableComments", typeof(global::System.Boolean), attribGenerateTableComments); + } + } + } } /// @@ -2296,6 +2313,19 @@ protected override void WritePropertiesAsAttributes(DslModeling::SerializationCo EFModelSerializationHelper.Instance.WriteAttributeString(serializationContext, element, writer, "showInterfaceIndicators", serializedPropValue); } } + // GenerateTableComments + if (!serializationContext.Result.Failed) + { + global::System.Boolean propValue = instanceOfModelRoot.GenerateTableComments; + string serializedPropValue = DslModeling::SerializationUtilities.GetString(serializationContext, propValue); + if (!serializationContext.Result.Failed) + { + if (serializationContext.WriteOptionalPropertiesWithDefaultValue || string.CompareOrdinal(serializedPropValue, "true") != 0) + { // No need to write the value out if it's the same as default value. + EFModelSerializationHelper.Instance.WriteAttributeString(serializationContext, element, writer, "generateTableComments", serializedPropValue); + } + } + } } /// @@ -3119,6 +3149,23 @@ protected override void ReadPropertiesFromAttributes(DslModeling::SerializationC } } } + // TableComment + if (!serializationContext.Result.Failed) + { + string attribTableComment = EFModelSerializationHelper.Instance.ReadAttribute(serializationContext, element, reader, "tableComment"); + if (attribTableComment != null) + { + global::System.String valueOfTableComment; + if (DslModeling::SerializationUtilities.TryGetValue(serializationContext, attribTableComment, out valueOfTableComment)) + { + instanceOfModelClass.TableComment = valueOfTableComment; + } + else + { // Invalid property value, ignored. + EFModelSerializationBehaviorSerializationMessages.IgnoredPropertyValue(serializationContext, reader, "tableComment", typeof(global::System.String), attribTableComment); + } + } + } } /// @@ -4166,6 +4213,17 @@ protected override void WritePropertiesAsAttributes(DslModeling::SerializationCo EFModelSerializationHelper.Instance.WriteAttributeString(serializationContext, element, writer, "describedAssociationElementId", serializedPropValue); } } + // TableComment + if (!serializationContext.Result.Failed) + { + global::System.String propValue = instanceOfModelClass.TableComment; + if (!serializationContext.Result.Failed) + { + if (!string.IsNullOrEmpty(propValue)) + EFModelSerializationHelper.Instance.WriteAttributeString(serializationContext, element, writer, "tableComment", propValue); + + } + } } /// @@ -5867,10 +5925,7 @@ protected override void WritePropertiesAsAttributes(DslModeling::SerializationCo // IsForeignKeyFor if (!serializationContext.Result.Failed) { - // Non-public getter, use DomainPropertyInfo method. - DslModeling::DomainPropertyInfo propInfo = instanceOfModelAttribute.Partition.DomainDataDirectory.GetDomainProperty (ModelAttribute.IsForeignKeyForDomainPropertyId); - global::System.Diagnostics.Debug.Assert (propInfo != null, "Cannot get DomainPropertyInfo for ModelAttribute.IsForeignKeyFor!"); - global::System.Guid propValue = ((global::System.Guid)propInfo.GetValue(instanceOfModelAttribute)); + global::System.Guid propValue = instanceOfModelAttribute.IsForeignKeyFor; string serializedPropValue = DslModeling::SerializationUtilities.GetString(serializationContext, propValue); if (!serializationContext.Result.Failed) { diff --git a/src/Dsl/GeneratedCode/ToolboxHelper.cs b/src/Dsl/GeneratedCode/ToolboxHelper.cs index 26841970b..389198baa 100644 --- a/src/Dsl/GeneratedCode/ToolboxHelper.cs +++ b/src/Dsl/GeneratedCode/ToolboxHelper.cs @@ -46,35 +46,35 @@ public abstract class EFModelToolboxHelperBase /// See the MSDN documentation for the ToolboxItemFilterAttribute class for more information on toolbox /// item filters. /// - public const string ToolboxFilterString = "EFModel.4.0"; + public const string ToolboxFilterString = "EFModel.4.1"; /// /// Toolbox item filter string used to identify ModelClass element tool. /// - public const string ModelClassFilterString = "ModelClass.4.0"; + public const string ModelClassFilterString = "ModelClass.4.1"; /// /// Toolbox item filter string used to identify UnidirectionalAssociation connector tool. /// - public const string UnidirectionalAssociationFilterString = "UnidirectionalAssociation.4.0"; + public const string UnidirectionalAssociationFilterString = "UnidirectionalAssociation.4.1"; /// /// Toolbox item filter string used to identify BidirectionalAssociation connector tool. /// - public const string BidirectionalAssociationFilterString = "BidirectionalAssociation.4.0"; + public const string BidirectionalAssociationFilterString = "BidirectionalAssociation.4.1"; /// /// Toolbox item filter string used to identify Generalization connector tool. /// - public const string GeneralizationFilterString = "Generalization.4.0"; + public const string GeneralizationFilterString = "Generalization.4.1"; /// /// Toolbox item filter string used to identify Comment element tool. /// - public const string CommentFilterString = "Comment.4.0"; + public const string CommentFilterString = "Comment.4.1"; /// /// Toolbox item filter string used to identify CommentLink connector tool. /// - public const string CommentLinkFilterString = "CommentLink.4.0"; + public const string CommentLinkFilterString = "CommentLink.4.1"; /// /// Toolbox item filter string used to identify Enumeration element tool. /// - public const string EnumerationFilterString = "Enumeration.4.0"; + public const string EnumerationFilterString = "Enumeration.4.1"; private global::System.Collections.Generic.Dictionary toolboxItemCache = new global::System.Collections.Generic.Dictionary(); diff --git a/src/Dsl/Properties/AssemblyInfo.cs b/src/Dsl/Properties/AssemblyInfo.cs index fd3e258b2..72e292239 100644 --- a/src/Dsl/Properties/AssemblyInfo.cs +++ b/src/Dsl/Properties/AssemblyInfo.cs @@ -5,7 +5,7 @@ using System.Runtime.ConstrainedExecution; [assembly: AssemblyTitle("Entity Framework Visual Designer")] -[assembly: AssemblyDescription("Entity Framework visual design surface and code-first code generation for EF6, EFCore and beyond")] +[assembly: AssemblyDescription("Entity Framework visual design surface and code-first code generation for EF6, EFCore and beyond (VS2019 version)")] #if DEBUG [assembly: AssemblyConfiguration("Debug")] #else @@ -17,8 +17,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("3.1.0.0")] -[assembly: AssemblyFileVersion("3.1.0.0")] +[assembly: AssemblyVersion("4.1.2.0")] +[assembly: AssemblyFileVersion("4.1.2.0")] [assembly: ComVisible(false)] [assembly: CLSCompliant(true)] [assembly: ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)] diff --git a/src/Dsl/Resources.Designer.cs b/src/Dsl/Resources.Designer.cs index 7cae4ae01..dc6d75a60 100644 --- a/src/Dsl/Resources.Designer.cs +++ b/src/Dsl/Resources.Designer.cs @@ -230,6 +230,16 @@ internal class Resources { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Dictionary_16x { + get { + object obj = ResourceManager.GetObject("Dictionary_16x", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/src/Dsl/Resources.resx b/src/Dsl/Resources.resx index b3c517649..9edf7edfb 100644 --- a/src/Dsl/Resources.resx +++ b/src/Dsl/Resources.resx @@ -169,6 +169,9 @@ resources\cardinality-many-many.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + resources\dictionary_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + resources\dictionary_16xvisible.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/src/DslPackage/Commands.vsct b/src/DslPackage/Commands.vsct index 54d3eaad7..732edc2c9 100644 --- a/src/DslPackage/Commands.vsct +++ b/src/DslPackage/Commands.vsct @@ -4,7 +4,7 @@ - + @@ -410,7 +410,6 @@ - @@ -436,7 +435,6 @@ - @@ -494,7 +492,6 @@ - diff --git a/src/DslPackage/CustomCode/CommandSet.cs b/src/DslPackage/CustomCode/CommandSet.cs index b29a85a82..c3714d76a 100644 --- a/src/DslPackage/CustomCode/CommandSet.cs +++ b/src/DslPackage/CustomCode/CommandSet.cs @@ -38,7 +38,6 @@ internal partial class EFModelCommandSet private const int cmdidGenerateCode = 0x0015; private const int cmdidAddCodeProperties = 0x0016; private const int cmdidSaveAsImage = 0x0017; - private const int cmdidLoadNuGet = 0x0018; private const int cmdidAddCodeValues = 0x0019; private const int cmdidExpandSelected = 0x001A; private const int cmdidCollapseSelected = 0x001B; @@ -197,17 +196,6 @@ protected override IList GetMenuCommands() #endregion - #region loadNuGetCommand - -#pragma warning disable 612 - DynamicStatusMenuCommand loadNuGetCommand = - new DynamicStatusMenuCommand(OnStatusLoadNuGet, OnMenuLoadNuGet, new CommandID(guidEFDiagramMenuCmdSet, cmdidLoadNuGet)); -#pragma warning restore 612 - - commands.Add(loadNuGetCommand); - - #endregion - #region selectClassesCommand DynamicStatusMenuCommand selectClassesCommand = @@ -415,6 +403,13 @@ protected override void ProcessOnMenuDeleteCommand() } } + if (CurrentSelection.OfType().Any(a => a.ModelClass.IsAssociationClass && a.IsIdentity)) + { + MessageDisplay.Show("Identity attributes of association classes can't be deleted"); + + return; + } + base.ProcessOnMenuDeleteCommand(); } @@ -1080,31 +1075,6 @@ private void OnMenuImageToClipboard(object sender, EventArgs e) #endregion - #region Load NuGet - - [Obsolete] - private void OnStatusLoadNuGet(object sender, EventArgs e) - { - if (sender is MenuCommand command) - { - //Store store = CurrentDocData.Store; - //ModelRoot modelRoot = store.ModelRoot(); - command.Visible = false; // modelRoot != null && CurrentDocData is EFModelDocData && IsDiagramSelected(); - command.Enabled = false; // IsDiagramSelected() && ModelRoot.CanLoadNugetPackages; - } - } - - [Obsolete] - private void OnMenuLoadNuGet(object sender, EventArgs e) - { - //Store store = CurrentDocData.Store; - //ModelRoot modelRoot = store.ModelRoot(); - - //((EFModelDocData)CurrentDocData).EnsureCorrectNuGetPackages(modelRoot); - } - - #endregion Load NuGet - #region Merge Unidirectional Associations private void OnStatusMergeAssociations(object sender, EventArgs e) diff --git a/src/DslPackage/CustomCode/Partials/EFModelDocData.cs b/src/DslPackage/CustomCode/Partials/EFModelDocData.cs index 66f90788f..ec2a7d76a 100644 --- a/src/DslPackage/CustomCode/Partials/EFModelDocData.cs +++ b/src/DslPackage/CustomCode/Partials/EFModelDocData.cs @@ -7,11 +7,8 @@ using System.Linq; using System.Runtime.InteropServices; using System.Windows.Forms; - using EnvDTE; - using EnvDTE80; - using Microsoft.VisualStudio; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Modeling; @@ -24,6 +21,7 @@ using Sawczyn.EFDesigner.EFModel.Extensions; using VSLangProj; +// ReSharper disable MemberCanBePrivate.Global namespace Sawczyn.EFDesigner.EFModel { @@ -32,22 +30,18 @@ internal partial class EFModelDocData { private static DTE _dte; private static DTE2 _dte2; - private IComponentModel _componentModel; - //private IVsOutputWindowPane _outputWindow; internal static DTE Dte => _dte ?? (_dte = Package.GetGlobalService(typeof(DTE)) as DTE); internal static DTE2 Dte2 => _dte2 ?? (_dte2 = Package.GetGlobalService(typeof(SDTE)) as DTE2); - internal IComponentModel ComponentModel => _componentModel ?? (_componentModel = (IComponentModel)GetService(typeof(SComponentModel))); - //internal IVsOutputWindowPane OutputWindow => _outputWindow ?? (_outputWindow = (IVsOutputWindowPane)GetService(typeof(SVsGeneralOutputWindowPane))); internal static Project ActiveProject => - Dte.ActiveSolutionProjects is Array activeSolutionProjects && activeSolutionProjects.Length > 0 - ? activeSolutionProjects.GetValue(0) as Project - : null; + Dte.ActiveSolutionProjects is Array activeSolutionProjects && activeSolutionProjects.Length > 0 + ? activeSolutionProjects.GetValue(0) as Project + : null; internal static void GenerateCode() { @@ -125,7 +119,6 @@ internal static bool OpenFileFor(ModelClass modelClass) catch (Exception e) { Debug.WriteLine("Error opening file. " + e.Message); - return false; } } @@ -159,7 +152,6 @@ internal static bool OpenFileFor(ModelEnum modelEnum) catch (Exception e) { Debug.WriteLine("Error opening file. " + e.Message); - return false; } } @@ -378,14 +370,18 @@ private void ValidateModelElement(ModelElement modelElement) ValidationCategories allCategories = ValidationCategories.Menu | ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Custom | ValidationCategories.Load; ValidationController.Validate(modelElement, allCategories); - + displaysWarningElement.RedrawItem(); } } internal static DialogResult ShowQuestionBox(IServiceProvider serviceProvider, string question) { - return PackageUtility.ShowMessageBox(serviceProvider, question, OLEMSGBUTTON.OLEMSGBUTTON_YESNO, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_SECOND, OLEMSGICON.OLEMSGICON_QUERY); + return PackageUtility.ShowMessageBox(serviceProvider, + question, + OLEMSGBUTTON.OLEMSGBUTTON_YESNO, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_SECOND, + OLEMSGICON.OLEMSGICON_QUERY); } internal static bool ShowBooleanQuestionBox(IServiceProvider serviceProvider, string question) @@ -434,7 +430,6 @@ protected override bool CanSave(bool allowUserInterface) { if (allowUserInterface) ValidationController?.ClearMessages(); - return base.CanSave(allowUserInterface); } @@ -455,8 +450,7 @@ protected override void OnDocumentSaved(EventArgs e) public void Merge(UnidirectionalAssociation[] selected) { - if (!(RootElement is ModelRoot modelRoot)) - return; + if (!(RootElement is ModelRoot modelRoot)) return; using (Transaction tx = modelRoot.Store.TransactionManager.BeginTransaction("Merge associations")) { @@ -464,89 +458,105 @@ public void Merge(UnidirectionalAssociation[] selected) selected[1].Delete(); // ReSharper disable once UnusedVariable - BidirectionalAssociation element = new BidirectionalAssociation(Store - , new[] - { - new RoleAssignment(BidirectionalAssociation.BidirectionalSourceDomainRoleId, selected[0].Source) - , new RoleAssignment(BidirectionalAssociation.BidirectionalTargetDomainRoleId, selected[1].Source) - } - , new[] - { - new PropertyAssignment(BidirectionalAssociation.SourcePropertyNameDomainPropertyId, selected[1].TargetPropertyName) - , new PropertyAssignment(Association.TargetPropertyNameDomainPropertyId, selected[0].TargetPropertyName) - , new PropertyAssignment(BidirectionalAssociation.SourceCustomAttributesDomainPropertyId - , selected[1].TargetCustomAttributes) - , new PropertyAssignment(Association.TargetCustomAttributesDomainPropertyId, selected[0].TargetCustomAttributes) - , new PropertyAssignment(BidirectionalAssociation.SourceDisplayTextDomainPropertyId, selected[1].TargetDisplayText) - , new PropertyAssignment(Association.TargetDisplayTextDomainPropertyId, selected[0].TargetDisplayText) - , new PropertyAssignment(BidirectionalAssociation.SourceSummaryDomainPropertyId, selected[1].TargetSummary) - , new PropertyAssignment(BidirectionalAssociation.SourceDescriptionDomainPropertyId, selected[1].TargetDescription) - , new PropertyAssignment(Association.TargetSummaryDomainPropertyId, selected[0].TargetSummary) - , new PropertyAssignment(Association.TargetDescriptionDomainPropertyId, selected[0].TargetDescription) - , new PropertyAssignment(Association.SourceDeleteActionDomainPropertyId, selected[1].TargetDeleteAction) - , new PropertyAssignment(Association.TargetDeleteActionDomainPropertyId, selected[0].TargetDeleteAction) - , new PropertyAssignment(Association.SourceRoleDomainPropertyId, selected[1].TargetRole) - , new PropertyAssignment(Association.TargetRoleDomainPropertyId, selected[0].TargetRole) - , new PropertyAssignment(Association.SourceMultiplicityDomainPropertyId, selected[1].TargetMultiplicity) - , new PropertyAssignment(Association.TargetMultiplicityDomainPropertyId, selected[0].TargetMultiplicity) - }); - + BidirectionalAssociation element = new BidirectionalAssociation(Store, + new[] + { + new RoleAssignment(BidirectionalAssociation.BidirectionalSourceDomainRoleId, selected[0].Source), + new RoleAssignment(BidirectionalAssociation.BidirectionalTargetDomainRoleId, selected[1].Source) + }, + new[] + { + new PropertyAssignment(BidirectionalAssociation.SourcePropertyNameDomainPropertyId, selected[1].TargetPropertyName), + new PropertyAssignment(Association.TargetPropertyNameDomainPropertyId, selected[0].TargetPropertyName), + + new PropertyAssignment(BidirectionalAssociation.SourceCustomAttributesDomainPropertyId, selected[1].TargetCustomAttributes), + new PropertyAssignment(Association.TargetCustomAttributesDomainPropertyId, selected[0].TargetCustomAttributes), + + new PropertyAssignment(BidirectionalAssociation.SourceDisplayTextDomainPropertyId, selected[1].TargetDisplayText), + new PropertyAssignment(Association.TargetDisplayTextDomainPropertyId, selected[0].TargetDisplayText), + + new PropertyAssignment(BidirectionalAssociation.SourceSummaryDomainPropertyId, selected[1].TargetSummary), + new PropertyAssignment(BidirectionalAssociation.SourceDescriptionDomainPropertyId, selected[1].TargetDescription), + + new PropertyAssignment(Association.TargetSummaryDomainPropertyId, selected[0].TargetSummary), + new PropertyAssignment(Association.TargetDescriptionDomainPropertyId, selected[0].TargetDescription), + + new PropertyAssignment(Association.SourceDeleteActionDomainPropertyId, selected[1].TargetDeleteAction), + new PropertyAssignment(Association.TargetDeleteActionDomainPropertyId, selected[0].TargetDeleteAction), + + new PropertyAssignment(Association.SourceRoleDomainPropertyId, selected[1].TargetRole), + new PropertyAssignment(Association.TargetRoleDomainPropertyId, selected[0].TargetRole), + + new PropertyAssignment(Association.SourceMultiplicityDomainPropertyId, selected[1].TargetMultiplicity), + new PropertyAssignment(Association.TargetMultiplicityDomainPropertyId, selected[0].TargetMultiplicity), + }); tx.Commit(); } } public void Split(BidirectionalAssociation selected) { - if (!(RootElement is ModelRoot modelRoot)) - return; + if (!(RootElement is ModelRoot modelRoot)) return; using (Transaction tx = modelRoot.Store.TransactionManager.BeginTransaction("Split associations")) { selected.Delete(); // ReSharper disable once UnusedVariable - UnidirectionalAssociation element1 = new UnidirectionalAssociation(Store - , new[] - { - new RoleAssignment(Association.SourceDomainRoleId, selected.Source) - , new RoleAssignment(Association.TargetDomainRoleId, selected.Target) - } - , new[] - { - new PropertyAssignment(Association.TargetPropertyNameDomainPropertyId, selected.TargetPropertyName) - , new PropertyAssignment(Association.TargetCustomAttributesDomainPropertyId, selected.TargetCustomAttributes) - , new PropertyAssignment(Association.TargetDisplayTextDomainPropertyId, selected.TargetDisplayText) - , new PropertyAssignment(Association.TargetSummaryDomainPropertyId, selected.TargetSummary) - , new PropertyAssignment(Association.TargetDescriptionDomainPropertyId, selected.TargetDescription) - , new PropertyAssignment(Association.SourceDeleteActionDomainPropertyId, selected.SourceDeleteAction) - , new PropertyAssignment(Association.TargetDeleteActionDomainPropertyId, selected.TargetDeleteAction) - , new PropertyAssignment(Association.SourceRoleDomainPropertyId, selected.SourceRole) - , new PropertyAssignment(Association.TargetRoleDomainPropertyId, selected.TargetRole) - , new PropertyAssignment(Association.SourceMultiplicityDomainPropertyId, selected.SourceMultiplicity) - , new PropertyAssignment(Association.TargetMultiplicityDomainPropertyId, selected.TargetMultiplicity) - }); + UnidirectionalAssociation element1 = new UnidirectionalAssociation(Store, + new[] + { + new RoleAssignment(Association.SourceDomainRoleId, selected.Source), + new RoleAssignment(Association.TargetDomainRoleId, selected.Target) + }, + new[] + { + new PropertyAssignment(Association.TargetPropertyNameDomainPropertyId, selected.TargetPropertyName), + + new PropertyAssignment(Association.TargetCustomAttributesDomainPropertyId, selected.TargetCustomAttributes), + + new PropertyAssignment(Association.TargetDisplayTextDomainPropertyId, selected.TargetDisplayText), + + new PropertyAssignment(Association.TargetSummaryDomainPropertyId, selected.TargetSummary), + new PropertyAssignment(Association.TargetDescriptionDomainPropertyId, selected.TargetDescription), + + new PropertyAssignment(Association.SourceDeleteActionDomainPropertyId, selected.SourceDeleteAction), + new PropertyAssignment(Association.TargetDeleteActionDomainPropertyId, selected.TargetDeleteAction), + + new PropertyAssignment(Association.SourceRoleDomainPropertyId, selected.SourceRole), + new PropertyAssignment(Association.TargetRoleDomainPropertyId, selected.TargetRole), + + new PropertyAssignment(Association.SourceMultiplicityDomainPropertyId, selected.SourceMultiplicity), + new PropertyAssignment(Association.TargetMultiplicityDomainPropertyId, selected.TargetMultiplicity), + }); // ReSharper disable once UnusedVariable - UnidirectionalAssociation element2 = new UnidirectionalAssociation(Store - , new[] - { - new RoleAssignment(Association.SourceDomainRoleId, selected.Target) - , new RoleAssignment(Association.TargetDomainRoleId, selected.Source) - } - , new[] - { - new PropertyAssignment(Association.TargetPropertyNameDomainPropertyId, selected.SourcePropertyName) - , new PropertyAssignment(Association.TargetCustomAttributesDomainPropertyId, selected.SourceCustomAttributes) - , new PropertyAssignment(Association.TargetDisplayTextDomainPropertyId, selected.SourceDisplayText) - , new PropertyAssignment(Association.TargetSummaryDomainPropertyId, selected.SourceSummary) - , new PropertyAssignment(Association.TargetDescriptionDomainPropertyId, selected.SourceDescription) - , new PropertyAssignment(Association.SourceDeleteActionDomainPropertyId, selected.TargetDeleteAction) - , new PropertyAssignment(Association.TargetDeleteActionDomainPropertyId, selected.SourceDeleteAction) - , new PropertyAssignment(Association.SourceRoleDomainPropertyId, selected.TargetRole) - , new PropertyAssignment(Association.TargetRoleDomainPropertyId, selected.SourceRole) - , new PropertyAssignment(Association.SourceMultiplicityDomainPropertyId, selected.TargetMultiplicity) - , new PropertyAssignment(Association.TargetMultiplicityDomainPropertyId, selected.SourceMultiplicity) - }); + UnidirectionalAssociation element2 = new UnidirectionalAssociation(Store, + new[] + { + new RoleAssignment(Association.SourceDomainRoleId, selected.Target), + new RoleAssignment(Association.TargetDomainRoleId, selected.Source) + }, + new[] + { + new PropertyAssignment(Association.TargetPropertyNameDomainPropertyId, selected.SourcePropertyName), + + new PropertyAssignment(Association.TargetCustomAttributesDomainPropertyId, selected.SourceCustomAttributes), + + new PropertyAssignment(Association.TargetDisplayTextDomainPropertyId, selected.SourceDisplayText), + + new PropertyAssignment(Association.TargetSummaryDomainPropertyId, selected.SourceSummary), + new PropertyAssignment(Association.TargetDescriptionDomainPropertyId, selected.SourceDescription), + + new PropertyAssignment(Association.SourceDeleteActionDomainPropertyId, selected.TargetDeleteAction), + new PropertyAssignment(Association.TargetDeleteActionDomainPropertyId, selected.SourceDeleteAction), + + new PropertyAssignment(Association.SourceRoleDomainPropertyId, selected.TargetRole), + new PropertyAssignment(Association.TargetRoleDomainPropertyId, selected.SourceRole), + + new PropertyAssignment(Association.SourceMultiplicityDomainPropertyId, selected.TargetMultiplicity), + new PropertyAssignment(Association.TargetMultiplicityDomainPropertyId, selected.SourceMultiplicity), + }); tx.Commit(); } @@ -555,7 +565,6 @@ public void Split(BidirectionalAssociation selected) protected override void CleanupOldDiagramFiles() { string diagramsFileName = FileName + this.DiagramExtension; - if (diagramsFileName.EndsWith("x")) { string oldDiagramFileName = diagramsFileName.TrimEnd('x'); @@ -565,5 +574,4 @@ protected override void CleanupOldDiagramFiles() } } } - -} \ No newline at end of file +} diff --git a/src/DslPackage/DslPackage.csproj b/src/DslPackage/DslPackage.csproj index bf7d04fc0..0b6e1414c 100644 --- a/src/DslPackage/DslPackage.csproj +++ b/src/DslPackage/DslPackage.csproj @@ -241,6 +241,7 @@ true + @@ -273,6 +274,18 @@ true + + + + + + + + + + + + Always @@ -401,6 +414,7 @@ true + diff --git a/src/DslPackage/GeneratedCode/Constants.cs b/src/DslPackage/GeneratedCode/Constants.cs index 00d14bad0..a4bd6219b 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 = "4.0.1.1"; + public const string ProductVersion = "4.1.2.0"; // Menu definitions public static readonly global::System.ComponentModel.Design.CommandID EFModelDiagramMenu = new global::System.ComponentModel.Design.CommandID(new global::System.Guid(EFModelCommandSetId), 0x10000); @@ -40,7 +40,8 @@ internal static partial class Constants // Model explorer tool window identifier public const string EFModelModelExplorerToolWindowId = "860f0cbe-0c84-4abe-8062-fa681f8038db"; } -}// +} +// // Constants not generated from values in DesignerDefinition.dsl are defined below // namespace Sawczyn.EFDesigner.EFModel diff --git a/src/DslPackage/GeneratedCode/Constants.tt b/src/DslPackage/GeneratedCode/Constants.tt index 969abc906..6ade8b1bd 100644 --- a/src/DslPackage/GeneratedCode/Constants.tt +++ b/src/DslPackage/GeneratedCode/Constants.tt @@ -1,5 +1,6 @@ <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #><#@ -include file="..\Templates\Constants.tt" #>// +include file="..\Templates\Constants.tt" #> +// // Constants not generated from values in DesignerDefinition.dsl are defined below // namespace <#= CodeGenerationUtilities.GetPackageNamespace(this.Dsl) #> diff --git a/src/DslPackage/GeneratedCode/Package.cs b/src/DslPackage/GeneratedCode/Package.cs index ee4c3a699..8f8779a22 100644 --- a/src/DslPackage/GeneratedCode/Package.cs +++ b/src/DslPackage/GeneratedCode/Package.cs @@ -182,7 +182,7 @@ namespace Sawczyn.EFDesigner.EFModel /// /// Double-derived class to allow easier code customization. /// - [VSShell::ProvideMenuResource("1000.ctmenu", version: 43)] + [VSShell::ProvideMenuResource("1000.ctmenu", version: 44)] [VSShell::ProvideToolboxItems(1)] [global::Microsoft.VisualStudio.TextTemplating.VSHost.ProvideDirectiveProcessor(typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDirectiveProcessor), global::Sawczyn.EFDesigner.EFModel.EFModelDirectiveProcessor.EFModelDirectiveProcessorName, "A directive processor that provides access to EFModel files")] [global::System.Runtime.InteropServices.Guid(Constants.EFModelPackageId)] diff --git a/src/DslPackage/GeneratedCode/Package.tt b/src/DslPackage/GeneratedCode/Package.tt index c6c29bcb7..8169e7d20 100644 --- a/src/DslPackage/GeneratedCode/Package.tt +++ b/src/DslPackage/GeneratedCode/Package.tt @@ -22,7 +22,7 @@ namespace <#= CodeGenerationUtilities.GetPackageNamespace(this.Dsl) #> /// /// Double-derived class to allow easier code customization. /// - [VSShell::ProvideMenuResource("1000.ctmenu", version: 43)] + [VSShell::ProvideMenuResource("1000.ctmenu", version: 44)] [VSShell::ProvideToolboxItems(1)] [global::Microsoft.VisualStudio.TextTemplating.VSHost.ProvideDirectiveProcessor(typeof(global::<#= this.Dsl.Namespace #>.<#= directiveName #>DirectiveProcessor), global::<#= this.Dsl.Namespace #>.<#= directiveName #>DirectiveProcessor.<#= directiveName #>DirectiveProcessorName, "A directive processor that provides access to <#= directiveName #> files")] [global::System.Runtime.InteropServices.Guid(Constants.<#= dslName #>PackageId)] diff --git a/src/DslPackage/Parsers/EF6Parser.exe b/src/DslPackage/Parsers/EF6Parser.exe index 87c4b1bc0d154bd056a449eb2e9d78e24690ade9..d3b17f6f01c5bcbe2210995c20dc522a0b50d25b 100644 GIT binary patch delta 13172 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 HE|LBRSmqpB delta 13116 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 diff --git a/src/DslPackage/Parsers/EFCore2Parser.exe b/src/DslPackage/Parsers/EFCore2Parser.exe index f4a5cea8c69d0b10ac9e9e822b494c4847f46b41..7fe532a820afe9d0e5cc4c43db0c61cd4e5b0fdb 100644 GIT binary patch delta 12243 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 diff --git a/src/DslPackage/Parsers/EFCore3Parser.exe b/src/DslPackage/Parsers/EFCore3Parser.exe index 97a723975f3b8e4b83ff0c1f5a55aa9488366700..e379e3367135bab5490c39e5d9c05a03a4ce4db8 100644 GIT binary patch delta 11926 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 LYNiy#?r{7!8CJQE delta 11630 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 diff --git a/src/DslPackage/Parsers/EFCore5Parser.exe b/src/DslPackage/Parsers/EFCore5Parser.exe index dcc21151a5cbff5bcfc5dc0fcd51cd6597714320..297fd0c2c498fa046397fcb08e6f3237afd15ce6 100644 GIT binary patch delta 11972 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 Q%b#9QSkr!etflk+0D6bwU;qFB delta 11898 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 diff --git a/src/DslPackage/PreviewImage.png b/src/DslPackage/PreviewImage.png new file mode 100644 index 0000000000000000000000000000000000000000..abd79712ba8e29e1c1762b814460c45a153111fb GIT binary patch literal 9604 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 QkyB{u%GyeG&tAX(U+r!zuK)l5 literal 0 HcmV?d00001 diff --git a/src/DslPackage/ProjectItemTemplates/EFModel.xsd b/src/DslPackage/ProjectItemTemplates/EFModel.xsd index 1630caa4f..663111d2c 100644 --- a/src/DslPackage/ProjectItemTemplates/EFModel.xsd +++ b/src/DslPackage/ProjectItemTemplates/EFModel.xsd @@ -387,6 +387,12 @@ If true, will display a UML interface glyph on classes that have custom interfaces defined + + + + If true, will allow generating [Comment] attributes on C# class + + @@ -664,6 +670,12 @@ When IsAssociationClass is true, the element id of the association this entity extends + + + + Table comment that will be applied to the database, if possible + + diff --git a/src/DslPackage/Properties/AssemblyInfo.cs b/src/DslPackage/Properties/AssemblyInfo.cs index 05262c3d2..6bdc9a73a 100644 --- a/src/DslPackage/Properties/AssemblyInfo.cs +++ b/src/DslPackage/Properties/AssemblyInfo.cs @@ -16,8 +16,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("3.1.0.0")] -[assembly: AssemblyFileVersion("3.1.0.0")] +[assembly: AssemblyVersion("4.1.2.0")] +[assembly: AssemblyFileVersion("4.1.2.0")] [assembly: ComVisible(false)] [assembly: CLSCompliant(false)] [assembly: ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)] diff --git a/src/DslPackage/TextTemplates/EF6Designer.ttinclude b/src/DslPackage/TextTemplates/EF6Designer.ttinclude index 9a50423f1..7a297cbdc 100644 --- a/src/DslPackage/TextTemplates/EF6Designer.ttinclude +++ b/src/DslPackage/TextTemplates/EF6Designer.ttinclude @@ -23,7 +23,7 @@ #><#@ import namespace="System.Data.Entity.Design.PluralizationServices" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ -// EFDesigner v3.0.8 +// EFDesigner v4.1.2.0 // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner diff --git a/src/DslPackage/TextTemplates/EF6ModelGenerator.ttinclude b/src/DslPackage/TextTemplates/EF6ModelGenerator.ttinclude index 9574eef2d..9ecabb9d9 100644 --- a/src/DslPackage/TextTemplates/EF6ModelGenerator.ttinclude +++ b/src/DslPackage/TextTemplates/EF6ModelGenerator.ttinclude @@ -23,7 +23,7 @@ #><#@ import namespace="System.Data.Entity.Design.PluralizationServices" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ - // EFDesigner v3.1.0.0 + // EFDesigner v4.1.2.0 // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner diff --git a/src/DslPackage/TextTemplates/EFCore2ModelGenerator.ttinclude b/src/DslPackage/TextTemplates/EFCore2ModelGenerator.ttinclude index 5275b055d..54311b578 100644 --- a/src/DslPackage/TextTemplates/EFCore2ModelGenerator.ttinclude +++ b/src/DslPackage/TextTemplates/EFCore2ModelGenerator.ttinclude @@ -23,7 +23,7 @@ #><#@ import namespace="System.Data.Entity.Design.PluralizationServices" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ - // EFDesigner v3.1.0.0 + // EFDesigner v4.1.2.0 // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner diff --git a/src/DslPackage/TextTemplates/EFCore3ModelGenerator.ttinclude b/src/DslPackage/TextTemplates/EFCore3ModelGenerator.ttinclude index 8e7ff1540..0c0f2f884 100644 --- a/src/DslPackage/TextTemplates/EFCore3ModelGenerator.ttinclude +++ b/src/DslPackage/TextTemplates/EFCore3ModelGenerator.ttinclude @@ -23,7 +23,7 @@ #><#@ import namespace="System.Data.Entity.Design.PluralizationServices" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ - // EFDesigner v3.1.0.0 + // EFDesigner v4.1.2.0 // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -31,7 +31,7 @@ { public EFCore3ModelGenerator(GeneratedTextTransformation host) : base(host) { } - protected override void WriteTargetDeleteBehavior(UnidirectionalAssociation association, List segments) + protected override void WriteTargetDeleteBehavior(Association association, List segments) { if (!association.Source.IsDependentType && !association.Target.IsDependentType diff --git a/src/DslPackage/TextTemplates/EFCore5ModelGenerator.ttinclude b/src/DslPackage/TextTemplates/EFCore5ModelGenerator.ttinclude index 9f86066f9..f08c000a3 100644 --- a/src/DslPackage/TextTemplates/EFCore5ModelGenerator.ttinclude +++ b/src/DslPackage/TextTemplates/EFCore5ModelGenerator.ttinclude @@ -23,7 +23,7 @@ #><#@ import namespace="System.Data.Entity.Design.PluralizationServices" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ - // EFDesigner v3.1.0.0 + // EFDesigner v4.1.2.0 // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -31,17 +31,114 @@ { public EFCore5ModelGenerator(GeneratedTextTransformation host) : base(host) { } + protected override void ConfigureModelClasses(List segments, ModelClass[] classesWithTables, List foreignKeyColumns, List visited) + { + foreach (ModelClass modelClass in modelRoot.Classes.Where(x => !x.IsAssociationClass).OrderBy(x => x.Name)) + ConfigureModelClass(segments, classesWithTables, foreignKeyColumns, visited, modelClass); + } + + protected override void ConfigureModelClass(List segments, ModelClass[] classesWithTables, List foreignKeyColumns, List visited, ModelClass modelClass) + { + segments.Clear(); + foreignKeyColumns.Clear(); + NL(); + + if (modelClass.IsDependentType) + { + segments.Add($"modelBuilder.Owned<{modelClass.FullName}>()"); + Output(segments); + + return; + } + + segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); + + ConfigureTransientProperties(segments, modelClass); + + //if (modelRoot.InheritanceStrategy == CodeStrategy.TablePerConcreteType && modelClass.Superclass != null) + // segments.Add("Map(x => x.MapInheritedProperties())"); + + if (classesWithTables.Contains(modelClass)) + { + if (modelClass.IsQueryType) + { + Output($"// There is no storage defined for {modelClass.Name} because its IsQueryType value is"); + Output($"// set to 'true'. Please provide the {modelRoot.FullName}.Get{modelClass.Name}SqlQuery() method in the partial class."); + Output("// "); + Output($"// private string Get{modelClass.Name}SqlQuery()"); + Output("// {"); + Output($"// return the defining SQL query that pulls all the properties for {modelClass.FullName}"); + Output("// }"); + + segments.Add($"ToSqlQuery(Get{modelClass.Name}SqlQuery())"); + } + else + ConfigureTable(segments, modelClass); + } + + if (segments.Count > 1 || modelClass.IsDependentType) + Output(segments); + + // attribute level + ConfigureModelAttributes(segments, modelClass); + + 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. So how do these map to each other? Short answer: they don't - they're orthogonal concepts. + // Source and Target are accidents of where the user started drawing the association, and help define where the + // properties are in unidirectional associations. Principal and Dependent define where the foreign keys go in + // the persistence mechanism. + + // What matters to code generation is the Principal and Dependent classifications, so we focus on those. + // In the case of 1-1 or 0/1-0/1, it's situational, so the user has to tell us. + // In all other cases, we can tell by the cardinalities of the associations. + + // navigation properties + List declaredShadowProperties = new List(); + + if (!modelClass.IsDependentType) + { + ConfigureUnidirectionalAssociations(modelClass, visited, foreignKeyColumns, declaredShadowProperties); + ConfigureBidirectionalAssociations(modelClass, visited, foreignKeyColumns, declaredShadowProperties); + } + } + protected override void ConfigureTable(List segments, ModelClass modelClass) { - string tableName = string.IsNullOrEmpty(modelClass.TableName) ? modelClass.Name : modelClass.TableName; - string viewName = string.IsNullOrEmpty(modelClass.ViewName) ? modelClass.Name : modelClass.ViewName; - string schema = string.IsNullOrEmpty(modelClass.DatabaseSchema) || modelClass.DatabaseSchema == modelClass.ModelRoot.DatabaseSchema ? string.Empty : $", \"{modelClass.DatabaseSchema}\""; - string buildAction = modelClass.ExcludeFromMigrations ? ", t => t.ExcludeFromMigrations()" : string.Empty; + string tableName = string.IsNullOrEmpty(modelClass.TableName) + ? modelClass.Name + : modelClass.TableName; + + string viewName = string.IsNullOrEmpty(modelClass.ViewName) + ? modelClass.Name + : modelClass.ViewName; + + string schema = string.IsNullOrEmpty(modelClass.DatabaseSchema) || modelClass.DatabaseSchema == modelClass.ModelRoot.DatabaseSchema + ? string.Empty + : $", \"{modelClass.DatabaseSchema}\""; + + List modifiers = new List(); - if (modelClass.IsDatabaseView) - segments.Add($"ToView(\"{viewName}\"{schema}{buildAction})"); - else - segments.Add($"ToTable(\"{tableName}\"{schema}{buildAction})"); + if (modelClass.ExcludeFromMigrations) + modifiers.Add("t.ExcludeFromMigrations();"); + + if (modelClass.UseTemporalTables + && !modelClass.IsDatabaseView + && (!modelClass.Subclasses.Any() || modelClass.ModelRoot.InheritanceStrategy == CodeStrategy.TablePerHierarchy) + && modelClass.Superclass == null) + modifiers.Add("t.IsTemporal();"); + + string buildActions = modifiers.Any() + ? $", t => {{ {string.Join(" ", modifiers)} }}" + : string.Empty; + + segments.Add(modelClass.IsDatabaseView + ? $"ToView(\"{viewName}\"{schema}{buildActions})" + : $"ToTable(\"{tableName}\"{schema}{buildActions})"); if (modelClass.Superclass != null) segments.Add($"HasBaseType<{modelClass.Superclass.FullName}>()"); @@ -101,6 +198,14 @@ && modelAttribute.Type == "String") segments.Add($"UseCollation(\"{modelAttribute.DatabaseCollation.Trim('"')}\")"); + int index = segments.IndexOf("IsRequired()"); + + if (index >= 0) + { + segments.RemoveAt(index); + segments.Add("IsRequired()"); + } + return segments; } @@ -170,7 +275,10 @@ visited.Add(association); List segments = new List(); - string separator = sourceInstance.ModelRoot.ShadowKeyNamePattern == ShadowKeyPattern.TableColumn ? string.Empty : "_"; + + string separator = sourceInstance.ModelRoot.ShadowKeyNamePattern == ShadowKeyPattern.TableColumn + ? string.Empty + : "_"; switch (association.TargetMultiplicity) // realized by property on source { @@ -297,8 +405,7 @@ visited.Add(association); List segments = new List(); - bool sourceRequired = false; - bool targetRequired = false; + bool required = false; segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); @@ -307,18 +414,12 @@ case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: { segments.Add($"HasMany<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); + required = association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One; break; } case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - { - segments.Add($"HasOne<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); - targetRequired = true; - - break; - } - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: { segments.Add($"HasOne<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); @@ -330,67 +431,31 @@ switch (association.SourceMultiplicity) // realized by property on target, but no property on target { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: + segments.Add($"WithMany(p => p.{association.SourcePropertyName})"); + + if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { - segments.Add($"WithMany(p => p.{association.SourcePropertyName})"); + ModelClass associationClass = modelClass.Store.ElementDirectory.AllElements.OfType().FirstOrDefault(m => m.DescribedAssociationElementId == association.Id); - if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) + if (associationClass == null) + segments.AddRange(WriteStandardBidirectionalAssociation(association, foreignKeyColumns, required)); + else { - string tableMap = string.IsNullOrEmpty(association.JoinTableName) - ? $"{association.Target.Name}_{association.SourcePropertyName}_x_{association.Source.Name}_{association.TargetPropertyName}" - : association.JoinTableName; - - segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap.Trim('"')}\"))"); + OutputNoTerminator(segments); + WriteBidirectionalAssociationWithAssociationClass(modelClass, associationClass, association); } - - break; } - case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - { - segments.Add($"WithOne(p => p.{association.SourcePropertyName})"); - sourceRequired = true; - - break; - } + break; + case Sawczyn.EFDesigner.EFModel.Multiplicity.One: case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"WithOne(p => p.{association.SourcePropertyName})"); break; } - string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); - - if (!string.IsNullOrEmpty(foreignKeySegment)) - segments.Add(foreignKeySegment); - - if (association.Dependent == association.Target) - { - if (association.SourceDeleteAction == DeleteAction.None) - segments.Add("OnDelete(DeleteBehavior.NoAction)"); - else if (association.SourceDeleteAction == DeleteAction.Cascade) - segments.Add("OnDelete(DeleteBehavior.Cascade)"); - - if (targetRequired) - segments.Add("IsRequired()"); - } - else if (association.Dependent == association.Source) - { - if (association.TargetDeleteAction == DeleteAction.None) - segments.Add("OnDelete(DeleteBehavior.NoAction)"); - else if (association.TargetDeleteAction == DeleteAction.Cascade) - segments.Add("OnDelete(DeleteBehavior.Cascade)"); - - if (sourceRequired) - segments.Add("IsRequired()"); - } - - Output(segments); - - if (association.Principal == association.Target && targetRequired) - Output($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName}).IsRequired();"); - else if (association.Principal == association.Source && sourceRequired) - Output($"modelBuilder.Entity<{association.Target.FullName}>().Navigation(e => e.{association.SourcePropertyName}).IsRequired();"); + if (segments.Any()) Output(segments); if (association.TargetAutoInclude) Output($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName}).AutoInclude();"); @@ -400,19 +465,11 @@ if (!association.TargetAutoProperty) { segments.Add($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName})"); + segments.Add($"HasField(\"{association.TargetBackingFieldName}\")"); - if (association.Source == association.Principal) - { - segments.Add($"HasField(\"{association.TargetBackingFieldName}\")"); - segments.Add($"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode});"); - } - else if (association.Target == association.Principal) - { - segments.Add($"HasField(\"{association.TargetBackingFieldName}\")"); - segments.Add($"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode});"); - } - else - segments.Add($"HasField(\"{association.TargetBackingFieldName}\");"); + segments.Add(modelClass.ModelRoot.IsEFCore6Plus + ? $"UsePropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})" + : $"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})"); Output(segments); } @@ -420,26 +477,140 @@ if (!association.SourceAutoProperty) { segments.Add($"modelBuilder.Entity<{association.Target.FullName}>().Navigation(e => e.{association.SourcePropertyName})"); + segments.Add($"HasField(\"{association.SourceBackingFieldName}\")"); - if (association.Target == association.Principal) - { - segments.Add($"HasField(\"{association.SourceBackingFieldName}\")"); - segments.Add($"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.SourcePropertyAccessMode});"); - } - else if (association.Source == association.Principal) - { - segments.Add($"HasField(\"{association.SourceBackingFieldName}\")"); - segments.Add($"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.SourcePropertyAccessMode});"); - } - else - segments.Add($"HasField(\"{association.SourceBackingFieldName}\");"); + segments.Add(modelClass.ModelRoot.IsEFCore6Plus + ? $"UsePropertyAccessMode(PropertyAccessMode.{association.SourcePropertyAccessMode})" + : $"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.SourcePropertyAccessMode})"); Output(segments); } - } } + private IEnumerable WriteStandardBidirectionalAssociation(BidirectionalAssociation association, List foreignKeyColumns, bool required) + { + List segments = new List(); + + string tableMap = string.IsNullOrEmpty(association.JoinTableName) + ? $"{association.Target.Name}_{association.SourcePropertyName}_x_{association.Source.Name}_{association.TargetPropertyName}" + : association.JoinTableName; + + segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); + + string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); + + if (!string.IsNullOrEmpty(foreignKeySegment)) + segments.Add(foreignKeySegment); + + WriteSourceDeleteBehavior(association, segments); + WriteTargetDeleteBehavior(association, segments); + + if (required + && (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One + || association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One)) + segments.Add("IsRequired()"); + + return segments; + } + + private void WriteBidirectionalAssociationWithAssociationClass(ModelClass modelClass, ModelClass associationClass, BidirectionalAssociation association) + { + string indent = associationClass.ModelRoot.UseTabs + ? "\t" + : " "; + BidirectionalAssociation associationToSource = (BidirectionalAssociation)associationClass.AllNavigationProperties().First(n => n.AssociationObject.Source == association.Source).AssociationObject; + BidirectionalAssociation associationToTarget = (BidirectionalAssociation)associationClass.AllNavigationProperties().First(n => n.AssociationObject.Source == association.Target).AssociationObject; + + if (modelClass.ModelRoot.ChopMethodChains) + PushIndent(" "); + else + PushIndent(indent); + + Output($".UsingEntity<{associationClass.FullName}>("); + PushIndent(indent); + Output("j => j"); + PushIndent(indent); + Output($".HasOne(x => x.{associationToTarget.SourcePropertyName})"); + Output($".WithMany(x => x.{associationToTarget.TargetPropertyName})"); + Output($".HasForeignKey(x => x.{associationClass.Attributes.First(a => a.IsForeignKeyFor == associationToTarget.Id).Name}),"); + PopIndent(); + Output("j => j"); + PushIndent(indent); + Output($".HasOne(x => x.{associationToSource.SourcePropertyName})"); + Output($".WithMany(x => x.{associationToSource.TargetPropertyName})"); + Output($".HasForeignKey(x => x.{associationClass.Attributes.First(a => a.IsForeignKeyFor == associationToSource.Id).Name}),"); + PopIndent(); + Output("j =>"); + Output("{"); + + #region transient properties + + foreach (ModelAttribute transient in associationClass.Attributes.Where(x => !x.Persistent)) + Output($"j.Ignore(t => t.{transient.Name});"); + + #endregion + + #region table definition + + string tableName = string.IsNullOrEmpty(associationClass.TableName) + ? associationClass.Name + : associationClass.TableName; + + string schema = string.IsNullOrEmpty(associationClass.DatabaseSchema) || associationClass.DatabaseSchema == associationClass.ModelRoot.DatabaseSchema + ? string.Empty + : $", \"{associationClass.DatabaseSchema}\""; + + List modifiers = new List(); + + if (associationClass.UseTemporalTables) + modifiers.Add(" t.IsTemporal();"); + + string buildActions = modifiers.Any() + ? $", t => {{ {string.Join(" ", modifiers)} }}" + : string.Empty; + + Output($"j.ToTable(\"{tableName}\"{schema}{buildActions});"); + + List identityAttributes = associationClass.IdentityAttributes.ToList(); + + if (identityAttributes.Count == 1) + Output($"j.HasKey(t => t.{identityAttributes[0].Name});"); + else if (identityAttributes.Count > 1) + Output($"j.HasKey(t => new {{ t.{string.Join(", t.", identityAttributes.Select(ia => ia.Name))} }});"); + + #endregion + +#region model attributes + + foreach (ModelAttribute modelAttribute in associationClass.Attributes.Where(x => x.Persistent && !x.IsIdentity)) + { + List buffer = new List(); + buffer.AddRange(GatherModelAttributeSegments(modelAttribute)); + + if (buffer.Any()) + Output($"j.Property(t => t.{modelAttribute.Name}).{string.Join(".", buffer)};"); + + if (modelAttribute.Indexed) + { + buffer.Clear(); + buffer.Add($"HasIndex(t => t.{modelAttribute.Name})"); + + if (modelAttribute.IndexedUnique) + buffer.Add("IsUnique()"); + + Output($"j.{string.Join(".", buffer)};"); + } + } + +#endregion + + PopIndent(); + Output("});"); + PopIndent(); + PopIndent(); + } + [SuppressMessage("ReSharper", "RedundantNameQualifier")] protected override void ConfigureUnidirectionalAssociations(ModelClass modelClass , List visited @@ -463,7 +634,10 @@ visited.Add(association); List segments = new List(); - string separator = sourceInstance.ModelRoot.ShadowKeyNamePattern == ShadowKeyPattern.TableColumn ? string.Empty : "_"; + + string separator = sourceInstance.ModelRoot.ShadowKeyNamePattern == ShadowKeyPattern.TableColumn + ? string.Empty + : "_"; switch (association.TargetMultiplicity) // realized by property on source { @@ -553,8 +727,7 @@ visited.Add(association); List segments = new List(); - bool sourceRequired = false; - bool targetRequired = false; + bool required = false; segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); @@ -562,15 +735,11 @@ { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: segments.Add($"HasMany<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); + required = (association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One); break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - segments.Add($"HasOne<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); - targetRequired = true; - - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"HasOne<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); @@ -581,6 +750,7 @@ { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: segments.Add("WithMany()"); + required = (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One); if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { @@ -594,11 +764,6 @@ break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - segments.Add("WithOne()"); - sourceRequired = true; - - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add("WithOne()"); @@ -616,9 +781,6 @@ segments.Add("OnDelete(DeleteBehavior.NoAction)"); else if (association.SourceDeleteAction == DeleteAction.Cascade) segments.Add("OnDelete(DeleteBehavior.Cascade)"); - - if (targetRequired) - segments.Add("IsRequired()"); } else if (association.Dependent == association.Source) { @@ -626,15 +788,12 @@ segments.Add("OnDelete(DeleteBehavior.NoAction)"); else if (association.TargetDeleteAction == DeleteAction.Cascade) segments.Add("OnDelete(DeleteBehavior.Cascade)"); - - if (sourceRequired) - segments.Add("IsRequired()"); } - Output(segments); + if (required) + segments.Add("IsRequired()"); - if (association.Principal == association.Target && targetRequired) - Output($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName}).IsRequired();"); + Output(segments); if (association.TargetAutoInclude) Output($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName}).AutoInclude();"); @@ -643,18 +802,9 @@ { segments.Add($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName})"); - if (association.Source == association.Principal) - { - segments.Add($"HasField(\"{association.TargetBackingFieldName}\")"); - segments.Add($"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode});"); - } - else if (association.Target == association.Principal) - { - segments.Add($"HasField(\"{association.TargetBackingFieldName}\")"); - segments.Add($"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode});"); - } - else - segments.Add($"HasField(\"{association.TargetBackingFieldName}\");"); + segments.Add(modelClass.ModelRoot.IsEFCore6Plus + ? $"UsePropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})" + : $"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})"); Output(segments); } diff --git a/src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude b/src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude index 3a3a235e0..7a297cbdc 100644 --- a/src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude +++ b/src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude @@ -23,7 +23,7 @@ #><#@ import namespace="System.Data.Entity.Design.PluralizationServices" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ -// EFDesigner v3.1.0.0 +// EFDesigner v4.1.2.0 // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner diff --git a/src/DslPackage/TextTemplates/EFCoreModelGenerator.ttinclude b/src/DslPackage/TextTemplates/EFCoreModelGenerator.ttinclude index 8dba2a64a..0f44709f4 100644 --- a/src/DslPackage/TextTemplates/EFCoreModelGenerator.ttinclude +++ b/src/DslPackage/TextTemplates/EFCoreModelGenerator.ttinclude @@ -23,7 +23,7 @@ #><#@ import namespace="System.Data.Entity.Design.PluralizationServices" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ - // EFDesigner v3.1.0.0 + // EFDesigner v4.1.2.0 // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -136,7 +136,7 @@ Output("}"); Output("}"); NL(); - + Output("/// "); Output("/// Defines a factory for creating derived DbContext instances."); Output("/// "); @@ -193,79 +193,87 @@ return result; } - protected virtual void ConfigureModelClasses(List segments, ModelClass[] classesWithTables, List foreignKeyColumns, List visited) + protected virtual void ConfigureModelClass(List segments, ModelClass[] classesWithTables, List foreignKeyColumns, List visited, ModelClass modelClass) { - foreach (ModelClass modelClass in modelRoot.Classes.OrderBy(x => x.Name)) - { - segments.Clear(); - foreignKeyColumns.Clear(); - NL(); + segments.Clear(); + foreignKeyColumns.Clear(); + NL(); - if (modelClass.IsDependentType) - { - segments.Add($"modelBuilder.Owned<{modelClass.FullName}>()"); - Output(segments); - continue; - } + if (modelClass.IsDependentType) + { + segments.Add($"modelBuilder.Owned<{modelClass.FullName}>()"); + Output(segments); + return; + } - segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); + segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); - foreach (ModelAttribute transient in modelClass.Attributes.Where(x => !x.Persistent)) - segments.Add($"Ignore(t => t.{transient.Name})"); + ConfigureTransientProperties(segments, modelClass); - //if (modelRoot.InheritanceStrategy == CodeStrategy.TablePerConcreteType && modelClass.Superclass != null) - // segments.Add("Map(x => x.MapInheritedProperties())"); + //if (modelRoot.InheritanceStrategy == CodeStrategy.TablePerConcreteType && modelClass.Superclass != null) + // segments.Add("Map(x => x.MapInheritedProperties())"); - if (classesWithTables.Contains(modelClass)) + if (classesWithTables.Contains(modelClass)) + { + if (modelClass.IsQueryType) { - if (modelClass.IsQueryType) - { - Output($"// There is no storage defined for {modelClass.Name} because its IsQueryType value is"); - Output($"// set to 'true'. Please provide the {modelRoot.FullName}.Get{modelClass.Name}SqlQuery() method in the partial class."); - Output("// "); - Output($"// private string Get{modelClass.Name}SqlQuery()"); - Output("// {"); - Output($"// return the defining SQL query that pulls all the properties for {modelClass.FullName}"); - Output("// }"); - - segments.Add($"ToSqlQuery(Get{modelClass.Name}SqlQuery())"); - } - else - ConfigureTable(segments, modelClass); + Output($"// There is no storage defined for {modelClass.Name} because its IsQueryType value is"); + Output($"// set to 'true'. Please provide the {modelRoot.FullName}.Get{modelClass.Name}SqlQuery() method in the partial class."); + Output("// "); + Output($"// private string Get{modelClass.Name}SqlQuery()"); + Output("// {"); + Output($"// return the defining SQL query that pulls all the properties for {modelClass.FullName}"); + Output("// }"); + + segments.Add($"ToSqlQuery(Get{modelClass.Name}SqlQuery())"); } + else + ConfigureTable(segments, modelClass); + } - if (segments.Count > 1 || modelClass.IsDependentType) - Output(segments); + if (segments.Count > 1 || modelClass.IsDependentType) + Output(segments); - // attribute level - ConfigureModelAttributes(segments, modelClass); + // attribute level + ConfigureModelAttributes(segments, modelClass); - bool hasDefinedConcurrencyToken = modelClass.AllAttributes.Any(x => x.IsConcurrencyToken); + bool hasDefinedConcurrencyToken = modelClass.AllAttributes.Any(x => x.IsConcurrencyToken); - if (!hasDefinedConcurrencyToken && modelClass.EffectiveConcurrency == ConcurrencyOverride.Optimistic) - Output($@"modelBuilder.Entity<{modelClass.FullName}>().Property(""Timestamp"").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. So how do these map to each other? Short answer: they don't - they're orthogonal concepts. - // Source and Target are accidents of where the user started drawing the association, and help define where the - // properties are in unidirectional associations. Principal and Dependent define where the foreign keys go in - // the persistence mechanism. + // Navigation endpoints are distingished as Source and Target. They are also distinguished as Principal + // and Dependent. So how do these map to each other? Short answer: they don't - they're orthogonal concepts. + // Source and Target are accidents of where the user started drawing the association, and help define where the + // properties are in unidirectional associations. Principal and Dependent define where the foreign keys go in + // the persistence mechanism. - // What matters to code generation is the Principal and Dependent classifications, so we focus on those. - // In the case of 1-1 or 0/1-0/1, it's situational, so the user has to tell us. - // In all other cases, we can tell by the cardinalities of the associations. + // What matters to code generation is the Principal and Dependent classifications, so we focus on those. + // In the case of 1-1 or 0/1-0/1, it's situational, so the user has to tell us. + // In all other cases, we can tell by the cardinalities of the associations. - // navigation properties - List declaredShadowProperties = new List(); + // navigation properties + List declaredShadowProperties = new List(); - if (!modelClass.IsDependentType) - { - ConfigureUnidirectionalAssociations(modelClass, visited, foreignKeyColumns, declaredShadowProperties); - ConfigureBidirectionalAssociations(modelClass, visited, foreignKeyColumns, declaredShadowProperties); - } + if (!modelClass.IsDependentType) + { + ConfigureUnidirectionalAssociations(modelClass, visited, foreignKeyColumns, declaredShadowProperties); + ConfigureBidirectionalAssociations(modelClass, visited, foreignKeyColumns, declaredShadowProperties); } } + protected virtual void ConfigureModelClasses(List segments, ModelClass[] classesWithTables, List foreignKeyColumns, List visited) + { + foreach (ModelClass modelClass in modelRoot.Classes.OrderBy(x => x.Name)) + ConfigureModelClass(segments, classesWithTables, foreignKeyColumns, visited, modelClass); + } + + protected static void ConfigureTransientProperties(List segments, ModelClass modelClass) + { + foreach (ModelAttribute transient in modelClass.Attributes.Where(x => !x.Persistent)) + segments.Add($"Ignore(t => t.{transient.Name})"); + } + protected virtual void ConfigureTable(List segments, ModelClass modelClass) { string tableName = string.IsNullOrEmpty(modelClass.TableName) ? modelClass.Name : modelClass.TableName; @@ -574,106 +582,106 @@ switch (association.TargetMultiplicity) // realized by property on source { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: - { - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add($"ToTable(\"{(string.IsNullOrEmpty(association.Target.TableName) ? association.Target.Name : association.Target.TableName)}\")"); - Output(segments); + { + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add($"ToTable(\"{(string.IsNullOrEmpty(association.Target.TableName) ? association.Target.Name : association.Target.TableName)}\")"); + Output(segments); - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add($"WithOwner(\"{association.SourcePropertyName}\")"); - segments.Add($"HasForeignKey(\"{association.SourcePropertyName}{separator}Id\")"); - Output(segments); + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add($"WithOwner(\"{association.SourcePropertyName}\")"); + segments.Add($"HasForeignKey(\"{association.SourcePropertyName}{separator}Id\")"); + Output(segments); - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add($"Property<{modelRoot.DefaultIdentityType}>(\"Id\")"); + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add($"Property<{modelRoot.DefaultIdentityType}>(\"Id\")"); - Output(segments); + Output(segments); - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add("HasKey(\"Id\")"); + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add("HasKey(\"Id\")"); - Output(segments); + Output(segments); - WriteBidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsMany(p => p.{association.TargetPropertyName})", visited); + WriteBidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsMany(p => p.{association.TargetPropertyName})", visited); - break; - } + break; + } case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - { - segments.Add(baseSegment); - segments.Add($"OwnsOne(p => p.{association.TargetPropertyName})"); - segments.Add($"WithOwner(p => p.{association.SourcePropertyName})"); - Output(segments); - - if (!string.IsNullOrEmpty(association.Target.TableName)) { segments.Add(baseSegment); segments.Add($"OwnsOne(p => p.{association.TargetPropertyName})"); - segments.Add($"ToTable(\"{association.Target.TableName}\")"); + segments.Add($"WithOwner(p => p.{association.SourcePropertyName})"); Output(segments); - } - foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) - { - segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); + if (!string.IsNullOrEmpty(association.Target.TableName)) + { + segments.Add(baseSegment); + segments.Add($"OwnsOne(p => p.{association.TargetPropertyName})"); + segments.Add($"ToTable(\"{association.Target.TableName}\")"); + Output(segments); + } - if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) - segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); + foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) + { + segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); - if (modelAttribute.Required) - segments.Add("IsRequired()"); + if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) + segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); - if (segments.Count > 1) - Output(segments); + if (modelAttribute.Required) + segments.Add("IsRequired()"); - segments.Clear(); - } + if (segments.Count > 1) + Output(segments); - WriteBidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); + segments.Clear(); + } - break; - } + WriteBidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: - { - segments.Add(baseSegment); - segments.Add($"OwnsOne(p => p.{association.TargetPropertyName})"); - segments.Add($"WithOwner(p => p.{association.SourcePropertyName})"); - Output(segments); + break; + } - if (!string.IsNullOrEmpty(association.Target.TableName)) + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: { segments.Add(baseSegment); segments.Add($"OwnsOne(p => p.{association.TargetPropertyName})"); - segments.Add($"ToTable(\"{association.Target.TableName}\")"); + segments.Add($"WithOwner(p => p.{association.SourcePropertyName})"); Output(segments); - } - foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) - { - segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); + if (!string.IsNullOrEmpty(association.Target.TableName)) + { + segments.Add(baseSegment); + segments.Add($"OwnsOne(p => p.{association.TargetPropertyName})"); + segments.Add($"ToTable(\"{association.Target.TableName}\")"); + Output(segments); + } - if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) - segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); + foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) + { + segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); - if (modelAttribute.Required) - segments.Add("IsRequired()"); + if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) + segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); - if (segments.Count > 1) - Output(segments); + if (modelAttribute.Required) + segments.Add("IsRequired()"); - segments.Clear(); - } + if (segments.Count > 1) + Output(segments); - WriteBidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); + segments.Clear(); + } - break; - } + WriteBidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); + + break; + } } } } @@ -721,12 +729,12 @@ if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { - string tableMap = string.IsNullOrEmpty(association.JoinTableName) - ? $"{association.Target.Name}_{association.SourcePropertyName}_x_{association.Source.Name}_{association.TargetPropertyName}" - : association.JoinTableName; + string tableMap = string.IsNullOrEmpty(association.JoinTableName) + ? $"{association.Target.Name}_{association.SourcePropertyName}_x_{association.Source.Name}_{association.TargetPropertyName}" + : association.JoinTableName; - segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); - } + segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); + } break; @@ -742,23 +750,23 @@ break; } - string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); + string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); - if (!string.IsNullOrEmpty(foreignKeySegment)) - segments.Add(foreignKeySegment); + if (!string.IsNullOrEmpty(foreignKeySegment)) + segments.Add(foreignKeySegment); - WriteSourceDeleteBehavior(association, segments); + WriteSourceDeleteBehavior(association, segments); - if (required - && (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One - || association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One)) - segments.Add("IsRequired()"); + if (required + && (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One + || association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One)) + segments.Add("IsRequired()"); Output(segments); } } - protected virtual void WriteTargetDeleteBehavior(UnidirectionalAssociation association, List segments) + protected virtual void WriteTargetDeleteBehavior(Association association, List segments) { if (!association.Source.IsDependentType && !association.Target.IsDependentType @@ -836,69 +844,69 @@ switch (association.TargetMultiplicity) // realized by property on source { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: - { - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add($"WithOwner(\"{association.Source.Name}_{association.TargetPropertyName}\")"); - segments.Add($"HasForeignKey(\"{association.Source.Name}_{association.TargetPropertyName}{separator}Id\")"); - Output(segments); + { + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add($"WithOwner(\"{association.Source.Name}_{association.TargetPropertyName}\")"); + segments.Add($"HasForeignKey(\"{association.Source.Name}_{association.TargetPropertyName}{separator}Id\")"); + Output(segments); - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add($"Property<{modelRoot.DefaultIdentityType}>(\"Id\")"); + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add($"Property<{modelRoot.DefaultIdentityType}>(\"Id\")"); - Output(segments); + Output(segments); - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add("HasKey(\"Id\")"); + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add("HasKey(\"Id\")"); - Output(segments); + Output(segments); - WriteUnidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsMany(p => p.{association.TargetPropertyName})", visited); + WriteUnidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsMany(p => p.{association.TargetPropertyName})", visited); - break; - } + break; + } case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - { - foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) { - segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); + foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) + { + segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); - if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) - segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); + if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) + segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); - if (modelAttribute.Required) - segments.Add("IsRequired()"); + if (modelAttribute.Required) + segments.Add("IsRequired()"); - Output(segments); - } + Output(segments); + } - WriteUnidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); + WriteUnidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); - break; - } + break; + } case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: - { - foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) { - segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); + foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) + { + segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); - if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) - segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); + if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) + segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); - if (modelAttribute.Required) - segments.Add("IsRequired()"); + if (modelAttribute.Required) + segments.Add("IsRequired()"); - Output(segments); - } + Output(segments); + } - WriteUnidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); + WriteUnidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); - break; - } + break; + } } } } diff --git a/src/DslPackage/TextTemplates/EFDesigner.ttinclude b/src/DslPackage/TextTemplates/EFDesigner.ttinclude index 87d6d5f23..75a6068cf 100644 --- a/src/DslPackage/TextTemplates/EFDesigner.ttinclude +++ b/src/DslPackage/TextTemplates/EFDesigner.ttinclude @@ -23,7 +23,7 @@ #><#@ import namespace="System.Data.Entity.Design.PluralizationServices" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ - // EFDesigner v3.1.0.0 + // EFDesigner v4.1.2.0 // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner diff --git a/src/DslPackage/TextTemplates/EFModelFileManager.ttinclude b/src/DslPackage/TextTemplates/EFModelFileManager.ttinclude index fc3b8f06f..a31c4b390 100644 --- a/src/DslPackage/TextTemplates/EFModelFileManager.ttinclude +++ b/src/DslPackage/TextTemplates/EFModelFileManager.ttinclude @@ -23,7 +23,7 @@ #><#@ import namespace="System.Data.Entity.Design.PluralizationServices" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ - // EFDesigner v3.1.0.0 + // EFDesigner v4.1.2.0 // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -208,7 +208,7 @@ private void CheckoutFileIfRequired(string fileName) { - SourceControl sc = dte.SourceControl; + EnvDTE.SourceControl sc = dte.SourceControl; if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName)) sc.CheckOutItem(fileName); diff --git a/src/DslPackage/TextTemplates/EFModelGenerator.ttinclude b/src/DslPackage/TextTemplates/EFModelGenerator.ttinclude index 668f9f119..0ccd6b244 100644 --- a/src/DslPackage/TextTemplates/EFModelGenerator.ttinclude +++ b/src/DslPackage/TextTemplates/EFModelGenerator.ttinclude @@ -23,7 +23,7 @@ #><#@ import namespace="System.Data.Entity.Design.PluralizationServices" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ - // EFDesigner v3.1.0.0 + // EFDesigner v4.1.2.0 // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -42,6 +42,16 @@ segments.Clear(); } + protected void OutputNoTerminator(List segments) + { + if (ModelRoot.ChopMethodChains) + OutputChoppedNoTerminator(segments); + else + Output(string.Join(".", segments)); + + segments.Clear(); + } + protected void Output(string text) { if (text == "}") @@ -90,6 +100,34 @@ segments.Clear(); } + protected void OutputChoppedNoTerminator(List 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]}"; + + foreach (string segment in segmentArray) + Output(segment); + + segments.Clear(); + } + public abstract class EFModelGenerator { protected static string[] xmlDocTags = @@ -150,8 +188,11 @@ // implementations delegated to the surrounding GeneratedTextTransformation for backward compatability protected void NL() { host.NL(); } protected void Output(List segments) { host.Output(segments); } + protected void OutputNoTerminator(List segments) { host.OutputNoTerminator(segments); } protected void Output(string text) { host.Output(text); } protected void Output(string template, params object[] items) { host.Output(template, items); } + protected void PushIndent(string indent) { host.PushIndent(indent); } + protected void PopIndent() { host.PopIndent(); } protected void ClearIndent() { host.ClearIndent(); } public static string[] NonNullableTypes @@ -289,10 +330,10 @@ } if (!string.IsNullOrWhiteSpace(modelAttribute.DisplayText)) - Output($"[Display(Name=\"{modelAttribute.DisplayText.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.DataAnnotations.Display(Name=\"{modelAttribute.DisplayText.Replace("\"", "\\\"")}\")]"); if (!string.IsNullOrWhiteSpace(modelAttribute.Summary)) - Output($"[System.ComponentModel.Description(\"{modelAttribute.Summary.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.Description(\"{modelAttribute.Summary.Trim('\r', '\n').Replace("\"", "\\\"")}\")]"); } protected abstract List GetAdditionalUsingStatements(); @@ -373,7 +414,8 @@ nsParts.Add("Migrations"); return string.Join(".", nsParts); - } protected List GetRequiredParameterNames(ModelClass modelClass, bool publicOnly = false) + } + protected List GetRequiredParameterNames(ModelClass modelClass, bool publicOnly = false) { return GetRequiredParameters(modelClass, null, publicOnly).Select(p => p.Split(' ')[1]).ToList(); } @@ -490,7 +532,7 @@ Output($"[{modelClass.CustomAttributes.Trim('[', ']')}]"); if (!string.IsNullOrWhiteSpace(modelClass.Summary)) - Output($"[System.ComponentModel.Description(\"{modelClass.Summary.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.Description(\"{modelClass.Summary.Trim('\r', '\n').Replace("\"", "\\\"")}\")]"); Output(baseClass.Length > 0 ? $"public {isAbstract}partial class {modelClass.Name}: {baseClass}" @@ -515,7 +557,7 @@ if (!string.IsNullOrEmpty(comment)) { int chunkSize = 80; - string[] parts = comment.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.RemoveEmptyEntries); + string[] parts = comment.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); foreach (string value in parts) { @@ -547,7 +589,7 @@ protected void WriteCommentBody(string comment) { foreach (string s in GenerateCommentBody(comment)) - Output($"/// {s}"); + Output($"/// {s}"); } protected void WriteConstructor(ModelClass modelClass) @@ -685,7 +727,11 @@ 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()};"); + string lhs = requiredAttribute.AutoProperty || string.IsNullOrEmpty(requiredAttribute.BackingFieldName) + ? requiredAttribute.Name + : requiredAttribute.BackingFieldName; + + Output($"this.{lhs} = {requiredAttribute.Name.ToLower()};"); NL(); } @@ -703,9 +749,13 @@ if (modelAttribute.Type == "decimal") initialValue += "m"; + string lhs = modelAttribute.AutoProperty || string.IsNullOrEmpty(modelAttribute.BackingFieldName) + ? modelAttribute.Name + : modelAttribute.BackingFieldName; + Output(quote.Length > 0 - ? $"this.{modelAttribute.Name} = {quote}{FullyQualified(initialValue.Trim(quote[0]))}{quote};" - : $"this.{modelAttribute.Name} = {quote}{FullyQualified(initialValue)}{quote};"); + ? $"this.{lhs} = {quote}{FullyQualified(initialValue.Trim(quote[0]))}{quote};" + : $"this.{lhs} = {quote}{FullyQualified(initialValue)}{quote};"); } // all required navigation properties that aren't a 1..1 relationship @@ -717,17 +767,21 @@ string parameterName = requiredNavigationProperty.PropertyName.ToLower(); Output($"if ({parameterName} == null) throw new ArgumentNullException(nameof({parameterName}));"); + string targetObjectName = requiredNavigationProperty.IsAutoProperty + ? requiredNavigationProperty.PropertyName + : requiredNavigationProperty.BackingFieldName; + if (!requiredNavigationProperty.ConstructorParameterOnly) { Output(requiredNavigationProperty.IsCollection - ? $"{requiredNavigationProperty.PropertyName}.Add({parameterName});" - : $"this.{requiredNavigationProperty.PropertyName} = {parameterName};"); + ? $"this.{targetObjectName}.Add({parameterName});" + : $"this.{targetObjectName} = {parameterName};"); } if (!string.IsNullOrEmpty(otherSide.PropertyName)) { - Output(otherSide.IsCollection - ? $"{parameterName}.{otherSide.PropertyName}.Add(this);" + Output(otherSide.IsCollection + ? $"{parameterName}.{otherSide.PropertyName}.Add(this);" : $"{parameterName}.{otherSide.PropertyName} = this;"); } @@ -852,9 +906,13 @@ if (modelAttribute.Type == "decimal") initialValue += "m"; + string lhs = modelAttribute.AutoProperty || string.IsNullOrEmpty(modelAttribute.BackingFieldName) + ? modelAttribute.Name + : modelAttribute.BackingFieldName; + Output(quote.Length == 1 - ? $"{modelAttribute.Name} = {quote}{FullyQualified(initialValue.Trim(quote[0]))}{quote};" - : $"{modelAttribute.Name} = {quote}{FullyQualified(initialValue)}{quote};"); + ? $"{lhs} = {quote}{FullyQualified(initialValue.Trim(quote[0]))}{quote};" + : $"{lhs} = {quote}{FullyQualified(initialValue)}{quote};"); ++lineCount; } @@ -895,7 +953,7 @@ Output($"[{modelEnum.CustomAttributes.Trim('[', ']')}]"); if (!string.IsNullOrWhiteSpace(modelEnum.Summary)) - Output($"[System.ComponentModel.Description(\"{modelEnum.Summary.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.Description(\"{modelEnum.Summary.Trim('\r', '\n').Replace("\"", "\\\"")}\")]"); Output($"public enum {modelEnum.Name} : {modelEnum.ValueType}"); Output("{"); @@ -922,10 +980,10 @@ Output($"[{values[index].CustomAttributes.Trim('[', ']')}]"); if (!string.IsNullOrWhiteSpace(values[index].Summary)) - Output($"[System.ComponentModel.Description(\"{values[index].Summary.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.Description(\"{values[index].Summary.Trim('\r', '\n').Replace("\"", "\\\"")}\")]"); if (!string.IsNullOrWhiteSpace(values[index].DisplayText)) - Output($"[System.ComponentModel.DataAnnotations.Display(Name=\"{values[index].DisplayText.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.DataAnnotations.Display(Name=\"{values[index].DisplayText.Trim('\r', '\n').Replace("\"", "\\\"")}\")]"); Output(string.IsNullOrEmpty(values[index].Value) ? $"{values[index].Name}{(index < values.Length - 1 ? "," : string.Empty)}" @@ -948,7 +1006,7 @@ Output(" *************************************************************************/"); NL(); - foreach (NavigationProperty navigationProperty in modelClass.LocalNavigationProperties().Where(x => !x.ConstructorParameterOnly)) + foreach (NavigationProperty navigationProperty in modelClass.LocalNavigationProperties().Where(x => !x.ConstructorParameterOnly).OrderBy(x => x.PropertyName)) { string type = navigationProperty.IsCollection ? $"ICollection<{navigationProperty.ClassType.FullName}>" @@ -1015,10 +1073,10 @@ Output($"[{navigationProperty.CustomAttributes.Trim('[', ']')}]"); if (!string.IsNullOrWhiteSpace(navigationProperty.Summary)) - Output($"[Description(\"{navigationProperty.Summary.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.Description(\"{navigationProperty.Summary.Replace("\"", "\\\"")}\")]"); if (!string.IsNullOrWhiteSpace(navigationProperty.DisplayText)) - Output($"[Display(Name=\"{navigationProperty.DisplayText.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.DataAnnotations.Display(Name=\"{navigationProperty.DisplayText.Replace("\"", "\\\"")}\")]"); if (navigationProperty.IsAutoProperty) { @@ -1053,7 +1111,7 @@ Output("}"); Output("set"); Output("{"); - Output($"{type} oldValue = {navigationProperty.BackingFieldName};"); + Output($"{type} oldValue = {navigationProperty.PropertyName};"); Output($"Set{navigationProperty.PropertyName}(oldValue, ref value);"); Output("if (oldValue != value)"); Output("{"); @@ -1080,7 +1138,7 @@ List segments = new List(); - foreach (ModelAttribute modelAttribute in modelClass.Attributes) + foreach (ModelAttribute modelAttribute in modelClass.Attributes.OrderBy(x => x.Name)) { segments.Clear(); @@ -1185,7 +1243,7 @@ Output("}"); Output($"{setterVisibility}set"); Output("{"); - Output($"{modelAttribute.FQPrimitiveType}{nullable} oldValue = {modelAttribute.BackingFieldName};"); + Output($"{modelAttribute.FQPrimitiveType}{nullable} oldValue = {modelAttribute.Name};"); Output($"Set{modelAttribute.Name}(oldValue, ref value);"); Output("if (oldValue != value)"); Output("{"); @@ -1208,7 +1266,7 @@ Output("/// "); Output("/// Concurrency token"); Output("/// "); - Output("[Timestamp]"); + Output("[System.ComponentModel.DataAnnotations.Timestamp]"); Output("public Byte[] Timestamp { get; set; }"); NL(); } diff --git a/src/DslPackage/TextTemplates/EditingOnly/EF6Designer.cs b/src/DslPackage/TextTemplates/EditingOnly/EF6Designer.cs index c0b85eff8..913940b0b 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EF6Designer.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EF6Designer.cs @@ -1,6 +1,6 @@ #region Template -// EFDesigner v3.0.8 -// Copyright (c) 2017-2021 Michael Sawczyn +// EFDesigner v4.1.2.0 +// Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner // This file is only present to support transition between v2.x EFModel templates and v3+ EFModel templates diff --git a/src/DslPackage/TextTemplates/EditingOnly/EF6ModelGenerator.cs b/src/DslPackage/TextTemplates/EditingOnly/EF6ModelGenerator.cs index a99f344d6..1262d13ff 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EF6ModelGenerator.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EF6ModelGenerator.cs @@ -10,8 +10,8 @@ namespace Sawczyn.EFDesigner.EFModel.EditingOnly public partial class GeneratedTextTransformation { #region Template - // EFDesigner v3.0.8.0 - // Copyright (c) 2017-2021 Michael Sawczyn + // EFDesigner v4.1.2.0 + // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner public class EF6ModelGenerator : EFModelGenerator @@ -796,3 +796,5 @@ private void WriteOnModelCreate(ModelClass[] classesWithTables) #endregion Template } } + + diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFCore2ModelGenerator.cs b/src/DslPackage/TextTemplates/EditingOnly/EFCore2ModelGenerator.cs index e4d33bc24..7f2dcfe9c 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EFCore2ModelGenerator.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EFCore2ModelGenerator.cs @@ -3,8 +3,8 @@ namespace Sawczyn.EFDesigner.EFModel.EditingOnly public partial class GeneratedTextTransformation { #region Template - // EFDesigner v3.0.8.0 - // Copyright (c) 2017-2021 Michael Sawczyn + // EFDesigner v4.1.2.0 + // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner public class EFCore2ModelGenerator : EFCoreModelGenerator @@ -15,3 +15,5 @@ public class EFCore2ModelGenerator : EFCoreModelGenerator #endregion Template } } + + diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFCore3ModelGenerator.cs b/src/DslPackage/TextTemplates/EditingOnly/EFCore3ModelGenerator.cs index 40cd3ea9a..a32ace3c1 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EFCore3ModelGenerator.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EFCore3ModelGenerator.cs @@ -6,15 +6,15 @@ public partial class GeneratedTextTransformation { #region Template - // EFDesigner v3.0.8.0 - // Copyright (c) 2017-2021 Michael Sawczyn + // EFDesigner v4.1.2.0 + // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner public class EFCore3ModelGenerator : EFCore2ModelGenerator { public EFCore3ModelGenerator(GeneratedTextTransformation host) : base(host) { } - protected override void WriteTargetDeleteBehavior(UnidirectionalAssociation association, List segments) + protected override void WriteTargetDeleteBehavior(Association association, List segments) { if (!association.Source.IsDependentType && !association.Target.IsDependentType @@ -68,3 +68,5 @@ protected override void WriteSourceDeleteBehavior(BidirectionalAssociation assoc #endregion Template } } + + diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFCore5ModelGenerator.cs b/src/DslPackage/TextTemplates/EditingOnly/EFCore5ModelGenerator.cs index 4da7c86d4..71ba42a5d 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EFCore5ModelGenerator.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EFCore5ModelGenerator.cs @@ -1,32 +1,137 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Text; + +using Newtonsoft.Json; + +using Sawczyn.EFDesigner.EFModel.Annotations; + // ReSharper disable RedundantNameQualifier namespace Sawczyn.EFDesigner.EFModel.EditingOnly { + [UsedImplicitly] public partial class GeneratedTextTransformation { #region Template - // EFDesigner v3.0.8.0 - // Copyright (c) 2017-2021 Michael Sawczyn + + // EFDesigner v4.1.2.0 + // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner public class EFCore5ModelGenerator : EFCore3ModelGenerator { public EFCore5ModelGenerator(GeneratedTextTransformation host) : base(host) { } + protected override void ConfigureModelClasses(List segments, ModelClass[] classesWithTables, List foreignKeyColumns, List visited) + { + foreach (ModelClass modelClass in modelRoot.Classes.Where(x => !x.IsAssociationClass).OrderBy(x => x.Name)) + ConfigureModelClass(segments, classesWithTables, foreignKeyColumns, visited, modelClass); + } + + protected override void ConfigureModelClass(List segments, ModelClass[] classesWithTables, List foreignKeyColumns, List visited, ModelClass modelClass) + { + segments.Clear(); + foreignKeyColumns.Clear(); + NL(); + + if (modelClass.IsDependentType) + { + segments.Add($"modelBuilder.Owned<{modelClass.FullName}>()"); + Output(segments); + + return; + } + + segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); + + ConfigureTransientProperties(segments, modelClass); + + //if (modelRoot.InheritanceStrategy == CodeStrategy.TablePerConcreteType && modelClass.Superclass != null) + // segments.Add("Map(x => x.MapInheritedProperties())"); + + if (classesWithTables.Contains(modelClass)) + { + if (modelClass.IsQueryType) + { + Output($"// There is no storage defined for {modelClass.Name} because its IsQueryType value is"); + Output($"// set to 'true'. Please provide the {modelRoot.FullName}.Get{modelClass.Name}SqlQuery() method in the partial class."); + Output("// "); + Output($"// private string Get{modelClass.Name}SqlQuery()"); + Output("// {"); + Output($"// return the defining SQL query that pulls all the properties for {modelClass.FullName}"); + Output("// }"); + + segments.Add($"ToSqlQuery(Get{modelClass.Name}SqlQuery())"); + } + else + ConfigureTable(segments, modelClass); + } + + if (segments.Count > 1 || modelClass.IsDependentType) + Output(segments); + + // attribute level + ConfigureModelAttributes(segments, modelClass); + + 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. So how do these map to each other? Short answer: they don't - they're orthogonal concepts. + // Source and Target are accidents of where the user started drawing the association, and help define where the + // properties are in unidirectional associations. Principal and Dependent define where the foreign keys go in + // the persistence mechanism. + + // What matters to code generation is the Principal and Dependent classifications, so we focus on those. + // In the case of 1-1 or 0/1-0/1, it's situational, so the user has to tell us. + // In all other cases, we can tell by the cardinalities of the associations. + + // navigation properties + List declaredShadowProperties = new List(); + + if (!modelClass.IsDependentType) + { + ConfigureUnidirectionalAssociations(modelClass, visited, foreignKeyColumns, declaredShadowProperties); + ConfigureBidirectionalAssociations(modelClass, visited, foreignKeyColumns, declaredShadowProperties); + } + } + protected override void ConfigureTable(List segments, ModelClass modelClass) { - string tableName = string.IsNullOrEmpty(modelClass.TableName) ? modelClass.Name : modelClass.TableName; - string viewName = string.IsNullOrEmpty(modelClass.ViewName) ? modelClass.Name : modelClass.ViewName; - string schema = string.IsNullOrEmpty(modelClass.DatabaseSchema) || modelClass.DatabaseSchema == modelClass.ModelRoot.DatabaseSchema ? string.Empty : $", \"{modelClass.DatabaseSchema}\""; - string buildAction = modelClass.ExcludeFromMigrations ? ", t => t.ExcludeFromMigrations()" : string.Empty; + string tableName = string.IsNullOrEmpty(modelClass.TableName) + ? modelClass.Name + : modelClass.TableName; + + string viewName = string.IsNullOrEmpty(modelClass.ViewName) + ? modelClass.Name + : modelClass.ViewName; + + string schema = string.IsNullOrEmpty(modelClass.DatabaseSchema) || modelClass.DatabaseSchema == modelClass.ModelRoot.DatabaseSchema + ? string.Empty + : $", \"{modelClass.DatabaseSchema}\""; + + List modifiers = new List(); + + if (modelClass.ExcludeFromMigrations) + modifiers.Add("t.ExcludeFromMigrations();"); - if (modelClass.IsDatabaseView) - segments.Add($"ToView(\"{viewName}\"{schema}{buildAction})"); - else - segments.Add($"ToTable(\"{tableName}\"{schema}{buildAction})"); + if (modelClass.UseTemporalTables + && !modelClass.IsDatabaseView + && (!modelClass.Subclasses.Any() || modelClass.ModelRoot.InheritanceStrategy == CodeStrategy.TablePerHierarchy) + && modelClass.Superclass == null) + modifiers.Add("t.IsTemporal();"); + + string buildActions = modifiers.Any() + ? $", t => {{ {string.Join(" ", modifiers)} }}" + : string.Empty; + + segments.Add(modelClass.IsDatabaseView + ? $"ToView(\"{viewName}\"{schema}{buildActions})" + : $"ToTable(\"{tableName}\"{schema}{buildActions})"); if (modelClass.Superclass != null) segments.Add($"HasBaseType<{modelClass.Superclass.FullName}>()"); @@ -86,6 +191,14 @@ protected override List GatherModelAttributeSegments(ModelAttribute mode && modelAttribute.Type == "String") segments.Add($"UseCollation(\"{modelAttribute.DatabaseCollation.Trim('"')}\")"); + int index = segments.IndexOf("IsRequired()"); + + if (index >= 0) + { + segments.RemoveAt(index); + segments.Add("IsRequired()"); + } + return segments; } @@ -155,7 +268,10 @@ protected override void WriteBidirectionalDependentAssociations(ModelClass sourc visited.Add(association); List segments = new List(); - string separator = sourceInstance.ModelRoot.ShadowKeyNamePattern == ShadowKeyPattern.TableColumn ? string.Empty : "_"; + + string separator = sourceInstance.ModelRoot.ShadowKeyNamePattern == ShadowKeyPattern.TableColumn + ? string.Empty + : "_"; switch (association.TargetMultiplicity) // realized by property on source { @@ -282,8 +398,7 @@ protected override void WriteBidirectionalNonDependentAssociations(ModelClass mo visited.Add(association); List segments = new List(); - bool sourceRequired = false; - bool targetRequired = false; + bool required = false; segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); @@ -292,18 +407,12 @@ protected override void WriteBidirectionalNonDependentAssociations(ModelClass mo case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: { segments.Add($"HasMany<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); + required = association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One; break; } case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - { - segments.Add($"HasOne<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); - targetRequired = true; - - break; - } - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: { segments.Add($"HasOne<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); @@ -315,67 +424,31 @@ protected override void WriteBidirectionalNonDependentAssociations(ModelClass mo switch (association.SourceMultiplicity) // realized by property on target, but no property on target { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: + segments.Add($"WithMany(p => p.{association.SourcePropertyName})"); + + if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { - segments.Add($"WithMany(p => p.{association.SourcePropertyName})"); + ModelClass associationClass = modelClass.Store.ElementDirectory.AllElements.OfType().FirstOrDefault(m => m.DescribedAssociationElementId == association.Id); - if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) + if (associationClass == null) + segments.AddRange(WriteStandardBidirectionalAssociation(association, foreignKeyColumns, required)); + else { - string tableMap = string.IsNullOrEmpty(association.JoinTableName) - ? $"{association.Target.Name}_{association.SourcePropertyName}_x_{association.Source.Name}_{association.TargetPropertyName}" - : association.JoinTableName; - - segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap.Trim('"')}\"))"); + OutputNoTerminator(segments); + WriteBidirectionalAssociationWithAssociationClass(modelClass, associationClass, association); } - - break; } - case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - { - segments.Add($"WithOne(p => p.{association.SourcePropertyName})"); - sourceRequired = true; - - break; - } + break; + case Sawczyn.EFDesigner.EFModel.Multiplicity.One: case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"WithOne(p => p.{association.SourcePropertyName})"); break; } - string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); - - if (!string.IsNullOrEmpty(foreignKeySegment)) - segments.Add(foreignKeySegment); - - if (association.Dependent == association.Target) - { - if (association.SourceDeleteAction == DeleteAction.None) - segments.Add("OnDelete(DeleteBehavior.NoAction)"); - else if (association.SourceDeleteAction == DeleteAction.Cascade) - segments.Add("OnDelete(DeleteBehavior.Cascade)"); - - if (targetRequired) - segments.Add("IsRequired()"); - } - else if (association.Dependent == association.Source) - { - if (association.TargetDeleteAction == DeleteAction.None) - segments.Add("OnDelete(DeleteBehavior.NoAction)"); - else if (association.TargetDeleteAction == DeleteAction.Cascade) - segments.Add("OnDelete(DeleteBehavior.Cascade)"); - - if (sourceRequired) - segments.Add("IsRequired()"); - } - - Output(segments); - - if (association.Principal == association.Target && targetRequired) - Output($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName}).IsRequired();"); - else if (association.Principal == association.Source && sourceRequired) - Output($"modelBuilder.Entity<{association.Target.FullName}>().Navigation(e => e.{association.SourcePropertyName}).IsRequired();"); + if (segments.Any()) Output(segments); if (association.TargetAutoInclude) Output($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName}).AutoInclude();"); @@ -385,19 +458,11 @@ protected override void WriteBidirectionalNonDependentAssociations(ModelClass mo if (!association.TargetAutoProperty) { segments.Add($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName})"); + segments.Add($"HasField(\"{association.TargetBackingFieldName}\")"); - if (association.Source == association.Principal) - { - segments.Add($"HasField(\"{association.TargetBackingFieldName}\")"); - segments.Add($"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode});"); - } - else if (association.Target == association.Principal) - { - segments.Add($"HasField(\"{association.TargetBackingFieldName}\")"); - segments.Add($"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode});"); - } - else - segments.Add($"HasField(\"{association.TargetBackingFieldName}\");"); + segments.Add(modelClass.ModelRoot.IsEFCore6Plus + ? $"UsePropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})" + : $"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})"); Output(segments); } @@ -405,26 +470,140 @@ protected override void WriteBidirectionalNonDependentAssociations(ModelClass mo if (!association.SourceAutoProperty) { segments.Add($"modelBuilder.Entity<{association.Target.FullName}>().Navigation(e => e.{association.SourcePropertyName})"); + segments.Add($"HasField(\"{association.SourceBackingFieldName}\")"); - if (association.Target == association.Principal) - { - segments.Add($"HasField(\"{association.SourceBackingFieldName}\")"); - segments.Add($"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.SourcePropertyAccessMode});"); - } - else if (association.Source == association.Principal) - { - segments.Add($"HasField(\"{association.SourceBackingFieldName}\")"); - segments.Add($"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.SourcePropertyAccessMode});"); - } - else - segments.Add($"HasField(\"{association.SourceBackingFieldName}\");"); + segments.Add(modelClass.ModelRoot.IsEFCore6Plus + ? $"UsePropertyAccessMode(PropertyAccessMode.{association.SourcePropertyAccessMode})" + : $"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.SourcePropertyAccessMode})"); Output(segments); } - } } + private IEnumerable WriteStandardBidirectionalAssociation(BidirectionalAssociation association, List foreignKeyColumns, bool required) + { + List segments = new List(); + + string tableMap = string.IsNullOrEmpty(association.JoinTableName) + ? $"{association.Target.Name}_{association.SourcePropertyName}_x_{association.Source.Name}_{association.TargetPropertyName}" + : association.JoinTableName; + + segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); + + string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); + + if (!string.IsNullOrEmpty(foreignKeySegment)) + segments.Add(foreignKeySegment); + + WriteSourceDeleteBehavior(association, segments); + WriteTargetDeleteBehavior(association, segments); + + if (required + && (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One + || association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One)) + segments.Add("IsRequired()"); + + return segments; + } + + private void WriteBidirectionalAssociationWithAssociationClass(ModelClass modelClass, ModelClass associationClass, BidirectionalAssociation association) + { + string indent = associationClass.ModelRoot.UseTabs + ? "\t" + : " "; + BidirectionalAssociation associationToSource = (BidirectionalAssociation)associationClass.AllNavigationProperties().First(n => n.AssociationObject.Source == association.Source).AssociationObject; + BidirectionalAssociation associationToTarget = (BidirectionalAssociation)associationClass.AllNavigationProperties().First(n => n.AssociationObject.Source == association.Target).AssociationObject; + + if (modelClass.ModelRoot.ChopMethodChains) + PushIndent(" "); + else + PushIndent(indent); + + Output($".UsingEntity<{associationClass.FullName}>("); + PushIndent(indent); + Output("j => j"); + PushIndent(indent); + Output($".HasOne(x => x.{associationToTarget.SourcePropertyName})"); + Output($".WithMany(x => x.{associationToTarget.TargetPropertyName})"); + Output($".HasForeignKey(x => x.{associationClass.Attributes.First(a => a.IsForeignKeyFor == associationToTarget.Id).Name}),"); + PopIndent(); + Output("j => j"); + PushIndent(indent); + Output($".HasOne(x => x.{associationToSource.SourcePropertyName})"); + Output($".WithMany(x => x.{associationToSource.TargetPropertyName})"); + Output($".HasForeignKey(x => x.{associationClass.Attributes.First(a => a.IsForeignKeyFor == associationToSource.Id).Name}),"); + PopIndent(); + Output("j =>"); + Output("{"); + + #region transient properties + + foreach (ModelAttribute transient in associationClass.Attributes.Where(x => !x.Persistent)) + Output($"j.Ignore(t => t.{transient.Name});"); + + #endregion + + #region table definition + + string tableName = string.IsNullOrEmpty(associationClass.TableName) + ? associationClass.Name + : associationClass.TableName; + + string schema = string.IsNullOrEmpty(associationClass.DatabaseSchema) || associationClass.DatabaseSchema == associationClass.ModelRoot.DatabaseSchema + ? string.Empty + : $", \"{associationClass.DatabaseSchema}\""; + + List modifiers = new List(); + + if (associationClass.UseTemporalTables) + modifiers.Add(" t.IsTemporal();"); + + string buildActions = modifiers.Any() + ? $", t => {{ {string.Join(" ", modifiers)} }}" + : string.Empty; + + Output($"j.ToTable(\"{tableName}\"{schema}{buildActions});"); + + List identityAttributes = associationClass.IdentityAttributes.ToList(); + + if (identityAttributes.Count == 1) + Output($"j.HasKey(t => t.{identityAttributes[0].Name});"); + else if (identityAttributes.Count > 1) + Output($"j.HasKey(t => new {{ t.{string.Join(", t.", identityAttributes.Select(ia => ia.Name))} }});"); + + #endregion + +#region model attributes + + foreach (ModelAttribute modelAttribute in associationClass.Attributes.Where(x => x.Persistent && !x.IsIdentity)) + { + List buffer = new List(); + buffer.AddRange(GatherModelAttributeSegments(modelAttribute)); + + if (buffer.Any()) + Output($"j.Property(t => t.{modelAttribute.Name}).{string.Join(".", buffer)};"); + + if (modelAttribute.Indexed) + { + buffer.Clear(); + buffer.Add($"HasIndex(t => t.{modelAttribute.Name})"); + + if (modelAttribute.IndexedUnique) + buffer.Add("IsUnique()"); + + Output($"j.{string.Join(".", buffer)};"); + } + } + +#endregion + + PopIndent(); + Output("});"); + PopIndent(); + PopIndent(); + } + [SuppressMessage("ReSharper", "RedundantNameQualifier")] protected override void ConfigureUnidirectionalAssociations(ModelClass modelClass , List visited @@ -448,7 +627,10 @@ protected override void WriteUnidirectionalDependentAssociations(ModelClass sour visited.Add(association); List segments = new List(); - string separator = sourceInstance.ModelRoot.ShadowKeyNamePattern == ShadowKeyPattern.TableColumn ? string.Empty : "_"; + + string separator = sourceInstance.ModelRoot.ShadowKeyNamePattern == ShadowKeyPattern.TableColumn + ? string.Empty + : "_"; switch (association.TargetMultiplicity) // realized by property on source { @@ -538,8 +720,7 @@ protected override void WriteUnidirectionalNonDependentAssociations(ModelClass m visited.Add(association); List segments = new List(); - bool sourceRequired = false; - bool targetRequired = false; + bool required = false; segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); @@ -547,15 +728,11 @@ protected override void WriteUnidirectionalNonDependentAssociations(ModelClass m { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: segments.Add($"HasMany<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); + required = (association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One); break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - segments.Add($"HasOne<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); - targetRequired = true; - - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"HasOne<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); @@ -566,6 +743,7 @@ protected override void WriteUnidirectionalNonDependentAssociations(ModelClass m { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: segments.Add("WithMany()"); + required = (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One); if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { @@ -579,11 +757,6 @@ protected override void WriteUnidirectionalNonDependentAssociations(ModelClass m break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - segments.Add("WithOne()"); - sourceRequired = true; - - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add("WithOne()"); @@ -601,9 +774,6 @@ protected override void WriteUnidirectionalNonDependentAssociations(ModelClass m segments.Add("OnDelete(DeleteBehavior.NoAction)"); else if (association.SourceDeleteAction == DeleteAction.Cascade) segments.Add("OnDelete(DeleteBehavior.Cascade)"); - - if (targetRequired) - segments.Add("IsRequired()"); } else if (association.Dependent == association.Source) { @@ -611,15 +781,12 @@ protected override void WriteUnidirectionalNonDependentAssociations(ModelClass m segments.Add("OnDelete(DeleteBehavior.NoAction)"); else if (association.TargetDeleteAction == DeleteAction.Cascade) segments.Add("OnDelete(DeleteBehavior.Cascade)"); - - if (sourceRequired) - segments.Add("IsRequired()"); } - Output(segments); + if (required) + segments.Add("IsRequired()"); - if (association.Principal == association.Target && targetRequired) - Output($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName}).IsRequired();"); + Output(segments); if (association.TargetAutoInclude) Output($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName}).AutoInclude();"); @@ -628,18 +795,9 @@ protected override void WriteUnidirectionalNonDependentAssociations(ModelClass m { segments.Add($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName})"); - if (association.Source == association.Principal) - { - segments.Add($"HasField(\"{association.TargetBackingFieldName}\")"); - segments.Add($"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode});"); - } - else if (association.Target == association.Principal) - { - segments.Add($"HasField(\"{association.TargetBackingFieldName}\")"); - segments.Add($"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode});"); - } - else - segments.Add($"HasField(\"{association.TargetBackingFieldName}\");"); + segments.Add(modelClass.ModelRoot.IsEFCore6Plus + ? $"UsePropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})" + : $"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})"); Output(segments); } @@ -649,3 +807,6 @@ protected override void WriteUnidirectionalNonDependentAssociations(ModelClass m #endregion Template } } + + + diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFCoreDesigner.cs b/src/DslPackage/TextTemplates/EditingOnly/EFCoreDesigner.cs index fad413b23..5084cbd27 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EFCoreDesigner.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EFCoreDesigner.cs @@ -1,6 +1,6 @@ #region Template -// EFDesigner v3.0.8.0 -// Copyright (c) 2017-2021 Michael Sawczyn +// EFDesigner v4.1.2.0 +// Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner // This file is only present to support transition between v2.x EFModel templates and v3+ EFModel templates @@ -8,4 +8,3 @@ #endregion Template - diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFCoreModelGenerator.cs b/src/DslPackage/TextTemplates/EditingOnly/EFCoreModelGenerator.cs index 5f5b4d471..48a90ed82 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EFCoreModelGenerator.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EFCoreModelGenerator.cs @@ -11,8 +11,8 @@ namespace Sawczyn.EFDesigner.EFModel.EditingOnly public partial class GeneratedTextTransformation { #region Template - // EFDesigner v3.0.8.0 - // Copyright (c) 2017-2021 Michael Sawczyn + // EFDesigner v4.1.2.0 + // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner public abstract class EFCoreModelGenerator : EFModelGenerator @@ -124,7 +124,7 @@ protected void WriteDbContextFactory() Output("}"); Output("}"); NL(); - + Output("/// "); Output("/// Defines a factory for creating derived DbContext instances."); Output("/// "); @@ -181,79 +181,87 @@ protected override List GetAdditionalUsingStatements() return result; } - protected virtual void ConfigureModelClasses(List segments, ModelClass[] classesWithTables, List foreignKeyColumns, List visited) + protected virtual void ConfigureModelClass(List segments, ModelClass[] classesWithTables, List foreignKeyColumns, List visited, ModelClass modelClass) { - foreach (ModelClass modelClass in modelRoot.Classes.OrderBy(x => x.Name)) - { - segments.Clear(); - foreignKeyColumns.Clear(); - NL(); + segments.Clear(); + foreignKeyColumns.Clear(); + NL(); - if (modelClass.IsDependentType) - { - segments.Add($"modelBuilder.Owned<{modelClass.FullName}>()"); - Output(segments); - continue; - } + if (modelClass.IsDependentType) + { + segments.Add($"modelBuilder.Owned<{modelClass.FullName}>()"); + Output(segments); + return; + } - segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); + segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); - foreach (ModelAttribute transient in modelClass.Attributes.Where(x => !x.Persistent)) - segments.Add($"Ignore(t => t.{transient.Name})"); + ConfigureTransientProperties(segments, modelClass); - //if (modelRoot.InheritanceStrategy == CodeStrategy.TablePerConcreteType && modelClass.Superclass != null) - // segments.Add("Map(x => x.MapInheritedProperties())"); + //if (modelRoot.InheritanceStrategy == CodeStrategy.TablePerConcreteType && modelClass.Superclass != null) + // segments.Add("Map(x => x.MapInheritedProperties())"); - if (classesWithTables.Contains(modelClass)) + if (classesWithTables.Contains(modelClass)) + { + if (modelClass.IsQueryType) { - if (modelClass.IsQueryType) - { - Output($"// There is no storage defined for {modelClass.Name} because its IsQueryType value is"); - Output($"// set to 'true'. Please provide the {modelRoot.FullName}.Get{modelClass.Name}SqlQuery() method in the partial class."); - Output("// "); - Output($"// private string Get{modelClass.Name}SqlQuery()"); - Output("// {"); - Output($"// return the defining SQL query that pulls all the properties for {modelClass.FullName}"); - Output("// }"); - - segments.Add($"ToSqlQuery(Get{modelClass.Name}SqlQuery())"); - } - else - ConfigureTable(segments, modelClass); + Output($"// There is no storage defined for {modelClass.Name} because its IsQueryType value is"); + Output($"// set to 'true'. Please provide the {modelRoot.FullName}.Get{modelClass.Name}SqlQuery() method in the partial class."); + Output("// "); + Output($"// private string Get{modelClass.Name}SqlQuery()"); + Output("// {"); + Output($"// return the defining SQL query that pulls all the properties for {modelClass.FullName}"); + Output("// }"); + + segments.Add($"ToSqlQuery(Get{modelClass.Name}SqlQuery())"); } + else + ConfigureTable(segments, modelClass); + } - if (segments.Count > 1 || modelClass.IsDependentType) - Output(segments); + if (segments.Count > 1 || modelClass.IsDependentType) + Output(segments); - // attribute level - ConfigureModelAttributes(segments, modelClass); + // attribute level + ConfigureModelAttributes(segments, modelClass); - bool hasDefinedConcurrencyToken = modelClass.AllAttributes.Any(x => x.IsConcurrencyToken); + bool hasDefinedConcurrencyToken = modelClass.AllAttributes.Any(x => x.IsConcurrencyToken); - if (!hasDefinedConcurrencyToken && modelClass.EffectiveConcurrency == ConcurrencyOverride.Optimistic) - Output($@"modelBuilder.Entity<{modelClass.FullName}>().Property(""Timestamp"").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. So how do these map to each other? Short answer: they don't - they're orthogonal concepts. - // Source and Target are accidents of where the user started drawing the association, and help define where the - // properties are in unidirectional associations. Principal and Dependent define where the foreign keys go in - // the persistence mechanism. + // Navigation endpoints are distingished as Source and Target. They are also distinguished as Principal + // and Dependent. So how do these map to each other? Short answer: they don't - they're orthogonal concepts. + // Source and Target are accidents of where the user started drawing the association, and help define where the + // properties are in unidirectional associations. Principal and Dependent define where the foreign keys go in + // the persistence mechanism. - // What matters to code generation is the Principal and Dependent classifications, so we focus on those. - // In the case of 1-1 or 0/1-0/1, it's situational, so the user has to tell us. - // In all other cases, we can tell by the cardinalities of the associations. + // What matters to code generation is the Principal and Dependent classifications, so we focus on those. + // In the case of 1-1 or 0/1-0/1, it's situational, so the user has to tell us. + // In all other cases, we can tell by the cardinalities of the associations. - // navigation properties - List declaredShadowProperties = new List(); + // navigation properties + List declaredShadowProperties = new List(); - if (!modelClass.IsDependentType) - { - ConfigureUnidirectionalAssociations(modelClass, visited, foreignKeyColumns, declaredShadowProperties); - ConfigureBidirectionalAssociations(modelClass, visited, foreignKeyColumns, declaredShadowProperties); - } + if (!modelClass.IsDependentType) + { + ConfigureUnidirectionalAssociations(modelClass, visited, foreignKeyColumns, declaredShadowProperties); + ConfigureBidirectionalAssociations(modelClass, visited, foreignKeyColumns, declaredShadowProperties); } } + protected virtual void ConfigureModelClasses(List segments, ModelClass[] classesWithTables, List foreignKeyColumns, List visited) + { + foreach (ModelClass modelClass in modelRoot.Classes.OrderBy(x => x.Name)) + ConfigureModelClass(segments, classesWithTables, foreignKeyColumns, visited, modelClass); + } + + protected static void ConfigureTransientProperties(List segments, ModelClass modelClass) + { + foreach (ModelAttribute transient in modelClass.Attributes.Where(x => !x.Persistent)) + segments.Add($"Ignore(t => t.{transient.Name})"); + } + protected virtual void ConfigureTable(List segments, ModelClass modelClass) { string tableName = string.IsNullOrEmpty(modelClass.TableName) ? modelClass.Name : modelClass.TableName; @@ -562,106 +570,106 @@ protected virtual void WriteBidirectionalDependentAssociations(ModelClass source switch (association.TargetMultiplicity) // realized by property on source { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: - { - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add($"ToTable(\"{(string.IsNullOrEmpty(association.Target.TableName) ? association.Target.Name : association.Target.TableName)}\")"); - Output(segments); + { + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add($"ToTable(\"{(string.IsNullOrEmpty(association.Target.TableName) ? association.Target.Name : association.Target.TableName)}\")"); + Output(segments); - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add($"WithOwner(\"{association.SourcePropertyName}\")"); - segments.Add($"HasForeignKey(\"{association.SourcePropertyName}{separator}Id\")"); - Output(segments); + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add($"WithOwner(\"{association.SourcePropertyName}\")"); + segments.Add($"HasForeignKey(\"{association.SourcePropertyName}{separator}Id\")"); + Output(segments); - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add($"Property<{modelRoot.DefaultIdentityType}>(\"Id\")"); + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add($"Property<{modelRoot.DefaultIdentityType}>(\"Id\")"); - Output(segments); + Output(segments); - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add("HasKey(\"Id\")"); + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add("HasKey(\"Id\")"); - Output(segments); + Output(segments); - WriteBidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsMany(p => p.{association.TargetPropertyName})", visited); + WriteBidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsMany(p => p.{association.TargetPropertyName})", visited); - break; - } + break; + } case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - { - segments.Add(baseSegment); - segments.Add($"OwnsOne(p => p.{association.TargetPropertyName})"); - segments.Add($"WithOwner(p => p.{association.SourcePropertyName})"); - Output(segments); - - if (!string.IsNullOrEmpty(association.Target.TableName)) { segments.Add(baseSegment); segments.Add($"OwnsOne(p => p.{association.TargetPropertyName})"); - segments.Add($"ToTable(\"{association.Target.TableName}\")"); + segments.Add($"WithOwner(p => p.{association.SourcePropertyName})"); Output(segments); - } - foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) - { - segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); + if (!string.IsNullOrEmpty(association.Target.TableName)) + { + segments.Add(baseSegment); + segments.Add($"OwnsOne(p => p.{association.TargetPropertyName})"); + segments.Add($"ToTable(\"{association.Target.TableName}\")"); + Output(segments); + } - if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) - segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); + foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) + { + segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); - if (modelAttribute.Required) - segments.Add("IsRequired()"); + if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) + segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); - if (segments.Count > 1) - Output(segments); + if (modelAttribute.Required) + segments.Add("IsRequired()"); - segments.Clear(); - } + if (segments.Count > 1) + Output(segments); - WriteBidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); + segments.Clear(); + } - break; - } + WriteBidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: - { - segments.Add(baseSegment); - segments.Add($"OwnsOne(p => p.{association.TargetPropertyName})"); - segments.Add($"WithOwner(p => p.{association.SourcePropertyName})"); - Output(segments); + break; + } - if (!string.IsNullOrEmpty(association.Target.TableName)) + case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: { segments.Add(baseSegment); segments.Add($"OwnsOne(p => p.{association.TargetPropertyName})"); - segments.Add($"ToTable(\"{association.Target.TableName}\")"); + segments.Add($"WithOwner(p => p.{association.SourcePropertyName})"); Output(segments); - } - foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) - { - segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); + if (!string.IsNullOrEmpty(association.Target.TableName)) + { + segments.Add(baseSegment); + segments.Add($"OwnsOne(p => p.{association.TargetPropertyName})"); + segments.Add($"ToTable(\"{association.Target.TableName}\")"); + Output(segments); + } - if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) - segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); + foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) + { + segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); - if (modelAttribute.Required) - segments.Add("IsRequired()"); + if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) + segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); - if (segments.Count > 1) - Output(segments); + if (modelAttribute.Required) + segments.Add("IsRequired()"); - segments.Clear(); - } + if (segments.Count > 1) + Output(segments); - WriteBidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); + segments.Clear(); + } - break; - } + WriteBidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); + + break; + } } } } @@ -709,12 +717,12 @@ protected virtual void WriteBidirectionalNonDependentAssociations(ModelClass mod if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { - string tableMap = string.IsNullOrEmpty(association.JoinTableName) - ? $"{association.Target.Name}_{association.SourcePropertyName}_x_{association.Source.Name}_{association.TargetPropertyName}" - : association.JoinTableName; + string tableMap = string.IsNullOrEmpty(association.JoinTableName) + ? $"{association.Target.Name}_{association.SourcePropertyName}_x_{association.Source.Name}_{association.TargetPropertyName}" + : association.JoinTableName; - segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); - } + segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); + } break; @@ -730,23 +738,23 @@ protected virtual void WriteBidirectionalNonDependentAssociations(ModelClass mod break; } - string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); + string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); - if (!string.IsNullOrEmpty(foreignKeySegment)) - segments.Add(foreignKeySegment); + if (!string.IsNullOrEmpty(foreignKeySegment)) + segments.Add(foreignKeySegment); - WriteSourceDeleteBehavior(association, segments); + WriteSourceDeleteBehavior(association, segments); - if (required - && (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One - || association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One)) - segments.Add("IsRequired()"); + if (required + && (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One + || association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One)) + segments.Add("IsRequired()"); Output(segments); } } - protected virtual void WriteTargetDeleteBehavior(UnidirectionalAssociation association, List segments) + protected virtual void WriteTargetDeleteBehavior(Association association, List segments) { if (!association.Source.IsDependentType && !association.Target.IsDependentType @@ -824,69 +832,69 @@ protected virtual void WriteUnidirectionalDependentAssociations(ModelClass sourc switch (association.TargetMultiplicity) // realized by property on source { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: - { - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add($"WithOwner(\"{association.Source.Name}_{association.TargetPropertyName}\")"); - segments.Add($"HasForeignKey(\"{association.Source.Name}_{association.TargetPropertyName}{separator}Id\")"); - Output(segments); + { + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add($"WithOwner(\"{association.Source.Name}_{association.TargetPropertyName}\")"); + segments.Add($"HasForeignKey(\"{association.Source.Name}_{association.TargetPropertyName}{separator}Id\")"); + Output(segments); - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add($"Property<{modelRoot.DefaultIdentityType}>(\"Id\")"); + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add($"Property<{modelRoot.DefaultIdentityType}>(\"Id\")"); - Output(segments); + Output(segments); - segments.Add(baseSegment); - segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); - segments.Add("HasKey(\"Id\")"); + segments.Add(baseSegment); + segments.Add($"OwnsMany(p => p.{association.TargetPropertyName})"); + segments.Add("HasKey(\"Id\")"); - Output(segments); + Output(segments); - WriteUnidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsMany(p => p.{association.TargetPropertyName})", visited); + WriteUnidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsMany(p => p.{association.TargetPropertyName})", visited); - break; - } + break; + } case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - { - foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) { - segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); + foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) + { + segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); - if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) - segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); + if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) + segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); - if (modelAttribute.Required) - segments.Add("IsRequired()"); + if (modelAttribute.Required) + segments.Add("IsRequired()"); - Output(segments); - } + Output(segments); + } - WriteUnidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); + WriteUnidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); - break; - } + break; + } case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: - { - foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) { - segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); + foreach (ModelAttribute modelAttribute in association.Target.AllAttributes) + { + segments.Add($"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName}).Property(p => p.{modelAttribute.Name})"); - if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) - segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); + if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) + segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); - if (modelAttribute.Required) - segments.Add("IsRequired()"); + if (modelAttribute.Required) + segments.Add("IsRequired()"); - Output(segments); - } + Output(segments); + } - WriteUnidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); + WriteUnidirectionalDependentAssociations(association.Target, $"{baseSegment}.OwnsOne(p => p.{association.TargetPropertyName})", visited); - break; - } + break; + } } } } @@ -1037,4 +1045,4 @@ protected virtual IEnumerable GetForeignKeys(Association association, Li } #endregion Template } -} \ No newline at end of file +} diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFDesigner.cs b/src/DslPackage/TextTemplates/EditingOnly/EFDesigner.cs index ade75c7e6..dd3a9b2a1 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EFDesigner.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EFDesigner.cs @@ -25,8 +25,8 @@ public partial class GeneratedTextTransformation #region Template - // EFDesigner v3.0.8.0 - // Copyright (c) 2017-2021 Michael Sawczyn + // EFDesigner v4.1.2.0 + // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner public void GenerateEF6(Manager manager, ModelRoot modelRoot) diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFModelFileManager.cs b/src/DslPackage/TextTemplates/EditingOnly/EFModelFileManager.cs index 683da638d..1ef60c857 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EFModelFileManager.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EFModelFileManager.cs @@ -16,8 +16,8 @@ public partial class GeneratedTextTransformation { #region Template - // EFDesigner v3.0.8.0 - // Copyright (c) 2017-2021 Michael Sawczyn + // EFDesigner v4.1.2.0 + // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner public class Manager @@ -201,7 +201,7 @@ public override string OutputPath private void CheckoutFileIfRequired(string fileName) { - SourceControl sc = dte.SourceControl; + EnvDTE.SourceControl sc = dte.SourceControl; if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName)) sc.CheckOutItem(fileName); diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFModelGenerator.cs b/src/DslPackage/TextTemplates/EditingOnly/EFModelGenerator.cs index 2aae691f6..c7e5341b4 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EFModelGenerator.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EFModelGenerator.cs @@ -12,8 +12,8 @@ namespace Sawczyn.EFDesigner.EFModel.EditingOnly public partial class GeneratedTextTransformation { #region Template - // EFDesigner v3.0.8.0 - // Copyright (c) 2017-2021 Michael Sawczyn + // EFDesigner v4.1.2.0 + // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner protected void NL() @@ -31,6 +31,16 @@ protected void Output(List segments) segments.Clear(); } + protected void OutputNoTerminator(List segments) + { + if (ModelRoot.ChopMethodChains) + OutputChoppedNoTerminator(segments); + else + Output(string.Join(".", segments)); + + segments.Clear(); + } + protected void Output(string text) { if (text == "}") @@ -79,6 +89,34 @@ protected void OutputChopped(List segments) segments.Clear(); } + protected void OutputChoppedNoTerminator(List 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]}"; + + foreach (string segment in segmentArray) + Output(segment); + + segments.Clear(); + } + public abstract class EFModelGenerator { protected static string[] xmlDocTags = @@ -139,8 +177,11 @@ protected EFModelGenerator(GeneratedTextTransformation host) // implementations delegated to the surrounding GeneratedTextTransformation for backward compatability protected void NL() { host.NL(); } protected void Output(List segments) { host.Output(segments); } + protected void OutputNoTerminator(List segments) { host.OutputNoTerminator(segments); } protected void Output(string text) { host.Output(text); } protected void Output(string template, params object[] items) { host.Output(template, items); } + protected void PushIndent(string indent) { host.PushIndent(indent); } + protected void PopIndent() { host.PopIndent(); } protected void ClearIndent() { host.ClearIndent(); } public static string[] NonNullableTypes @@ -278,10 +319,10 @@ protected void GeneratePropertyAnnotations(ModelAttribute modelAttribute) } if (!string.IsNullOrWhiteSpace(modelAttribute.DisplayText)) - Output($"[Display(Name=\"{modelAttribute.DisplayText.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.DataAnnotations.Display(Name=\"{modelAttribute.DisplayText.Replace("\"", "\\\"")}\")]"); if (!string.IsNullOrWhiteSpace(modelAttribute.Summary)) - Output($"[System.ComponentModel.Description(\"{modelAttribute.Summary.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.Description(\"{modelAttribute.Summary.Trim('\r', '\n').Replace("\"", "\\\"")}\")]"); } protected abstract List GetAdditionalUsingStatements(); @@ -362,7 +403,8 @@ protected string GetMigrationNamespace() nsParts.Add("Migrations"); return string.Join(".", nsParts); - } protected List GetRequiredParameterNames(ModelClass modelClass, bool publicOnly = false) + } + protected List GetRequiredParameterNames(ModelClass modelClass, bool publicOnly = false) { return GetRequiredParameters(modelClass, null, publicOnly).Select(p => p.Split(' ')[1]).ToList(); } @@ -479,7 +521,7 @@ protected virtual void WriteClass(ModelClass modelClass) Output($"[{modelClass.CustomAttributes.Trim('[', ']')}]"); if (!string.IsNullOrWhiteSpace(modelClass.Summary)) - Output($"[System.ComponentModel.Description(\"{modelClass.Summary.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.Description(\"{modelClass.Summary.Trim('\r', '\n').Replace("\"", "\\\"")}\")]"); Output(baseClass.Length > 0 ? $"public {isAbstract}partial class {modelClass.Name}: {baseClass}" @@ -504,7 +546,7 @@ protected string[] GenerateCommentBody(string comment) if (!string.IsNullOrEmpty(comment)) { int chunkSize = 80; - string[] parts = comment.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.RemoveEmptyEntries); + string[] parts = comment.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); foreach (string value in parts) { @@ -536,7 +578,7 @@ protected string[] GenerateCommentBody(string comment) protected void WriteCommentBody(string comment) { foreach (string s in GenerateCommentBody(comment)) - Output($"/// {s}"); + Output($"/// {s}"); } protected void WriteConstructor(ModelClass modelClass) @@ -674,7 +716,11 @@ protected void WriteConstructor(ModelClass modelClass) 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()};"); + string lhs = requiredAttribute.AutoProperty || string.IsNullOrEmpty(requiredAttribute.BackingFieldName) + ? requiredAttribute.Name + : requiredAttribute.BackingFieldName; + + Output($"this.{lhs} = {requiredAttribute.Name.ToLower()};"); NL(); } @@ -692,9 +738,13 @@ protected void WriteConstructor(ModelClass modelClass) if (modelAttribute.Type == "decimal") initialValue += "m"; + string lhs = modelAttribute.AutoProperty || string.IsNullOrEmpty(modelAttribute.BackingFieldName) + ? modelAttribute.Name + : modelAttribute.BackingFieldName; + Output(quote.Length > 0 - ? $"this.{modelAttribute.Name} = {quote}{FullyQualified(initialValue.Trim(quote[0]))}{quote};" - : $"this.{modelAttribute.Name} = {quote}{FullyQualified(initialValue)}{quote};"); + ? $"this.{lhs} = {quote}{FullyQualified(initialValue.Trim(quote[0]))}{quote};" + : $"this.{lhs} = {quote}{FullyQualified(initialValue)}{quote};"); } // all required navigation properties that aren't a 1..1 relationship @@ -706,17 +756,21 @@ protected void WriteConstructor(ModelClass modelClass) string parameterName = requiredNavigationProperty.PropertyName.ToLower(); Output($"if ({parameterName} == null) throw new ArgumentNullException(nameof({parameterName}));"); + string targetObjectName = requiredNavigationProperty.IsAutoProperty + ? requiredNavigationProperty.PropertyName + : requiredNavigationProperty.BackingFieldName; + if (!requiredNavigationProperty.ConstructorParameterOnly) { Output(requiredNavigationProperty.IsCollection - ? $"{requiredNavigationProperty.PropertyName}.Add({parameterName});" - : $"this.{requiredNavigationProperty.PropertyName} = {parameterName};"); + ? $"this.{targetObjectName}.Add({parameterName});" + : $"this.{targetObjectName} = {parameterName};"); } if (!string.IsNullOrEmpty(otherSide.PropertyName)) { - Output(otherSide.IsCollection - ? $"{parameterName}.{otherSide.PropertyName}.Add(this);" + Output(otherSide.IsCollection + ? $"{parameterName}.{otherSide.PropertyName}.Add(this);" : $"{parameterName}.{otherSide.PropertyName} = this;"); } @@ -841,9 +895,13 @@ protected void WriteDefaultConstructorBody(ModelClass modelClass) if (modelAttribute.Type == "decimal") initialValue += "m"; + string lhs = modelAttribute.AutoProperty || string.IsNullOrEmpty(modelAttribute.BackingFieldName) + ? modelAttribute.Name + : modelAttribute.BackingFieldName; + Output(quote.Length == 1 - ? $"{modelAttribute.Name} = {quote}{FullyQualified(initialValue.Trim(quote[0]))}{quote};" - : $"{modelAttribute.Name} = {quote}{FullyQualified(initialValue)}{quote};"); + ? $"{lhs} = {quote}{FullyQualified(initialValue.Trim(quote[0]))}{quote};" + : $"{lhs} = {quote}{FullyQualified(initialValue)}{quote};"); ++lineCount; } @@ -884,7 +942,7 @@ protected void WriteEnum(ModelEnum modelEnum) Output($"[{modelEnum.CustomAttributes.Trim('[', ']')}]"); if (!string.IsNullOrWhiteSpace(modelEnum.Summary)) - Output($"[System.ComponentModel.Description(\"{modelEnum.Summary.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.Description(\"{modelEnum.Summary.Trim('\r', '\n').Replace("\"", "\\\"")}\")]"); Output($"public enum {modelEnum.Name} : {modelEnum.ValueType}"); Output("{"); @@ -911,10 +969,10 @@ protected void WriteEnum(ModelEnum modelEnum) Output($"[{values[index].CustomAttributes.Trim('[', ']')}]"); if (!string.IsNullOrWhiteSpace(values[index].Summary)) - Output($"[System.ComponentModel.Description(\"{values[index].Summary.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.Description(\"{values[index].Summary.Trim('\r', '\n').Replace("\"", "\\\"")}\")]"); if (!string.IsNullOrWhiteSpace(values[index].DisplayText)) - Output($"[System.ComponentModel.DataAnnotations.Display(Name=\"{values[index].DisplayText.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.DataAnnotations.Display(Name=\"{values[index].DisplayText.Trim('\r', '\n').Replace("\"", "\\\"")}\")]"); Output(string.IsNullOrEmpty(values[index].Value) ? $"{values[index].Name}{(index < values.Length - 1 ? "," : string.Empty)}" @@ -937,7 +995,7 @@ protected void WriteNavigationProperties(ModelClass modelClass) Output(" *************************************************************************/"); NL(); - foreach (NavigationProperty navigationProperty in modelClass.LocalNavigationProperties().Where(x => !x.ConstructorParameterOnly)) + foreach (NavigationProperty navigationProperty in modelClass.LocalNavigationProperties().Where(x => !x.ConstructorParameterOnly).OrderBy(x => x.PropertyName)) { string type = navigationProperty.IsCollection ? $"ICollection<{navigationProperty.ClassType.FullName}>" @@ -1004,10 +1062,10 @@ protected void WriteNavigationProperties(ModelClass modelClass) Output($"[{navigationProperty.CustomAttributes.Trim('[', ']')}]"); if (!string.IsNullOrWhiteSpace(navigationProperty.Summary)) - Output($"[Description(\"{navigationProperty.Summary.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.Description(\"{navigationProperty.Summary.Replace("\"", "\\\"")}\")]"); if (!string.IsNullOrWhiteSpace(navigationProperty.DisplayText)) - Output($"[Display(Name=\"{navigationProperty.DisplayText.Replace("\"", "\\\"")}\")]"); + Output($"[System.ComponentModel.DataAnnotations.Display(Name=\"{navigationProperty.DisplayText.Replace("\"", "\\\"")}\")]"); if (navigationProperty.IsAutoProperty) { @@ -1042,7 +1100,7 @@ protected void WriteNavigationProperties(ModelClass modelClass) Output("}"); Output("set"); Output("{"); - Output($"{type} oldValue = {navigationProperty.BackingFieldName};"); + Output($"{type} oldValue = {navigationProperty.PropertyName};"); Output($"Set{navigationProperty.PropertyName}(oldValue, ref value);"); Output("if (oldValue != value)"); Output("{"); @@ -1069,7 +1127,7 @@ protected void WriteProperties(ModelClass modelClass) List segments = new List(); - foreach (ModelAttribute modelAttribute in modelClass.Attributes) + foreach (ModelAttribute modelAttribute in modelClass.Attributes.OrderBy(x => x.Name)) { segments.Clear(); @@ -1174,7 +1232,7 @@ protected void WriteProperties(ModelClass modelClass) Output("}"); Output($"{setterVisibility}set"); Output("{"); - Output($"{modelAttribute.FQPrimitiveType}{nullable} oldValue = {modelAttribute.BackingFieldName};"); + Output($"{modelAttribute.FQPrimitiveType}{nullable} oldValue = {modelAttribute.Name};"); Output($"Set{modelAttribute.Name}(oldValue, ref value);"); Output("if (oldValue != value)"); Output("{"); @@ -1197,7 +1255,7 @@ protected void WriteProperties(ModelClass modelClass) Output("/// "); Output("/// Concurrency token"); Output("/// "); - Output("[Timestamp]"); + Output("[System.ComponentModel.DataAnnotations.Timestamp]"); Output("public Byte[] Timestamp { get; set; }"); NL(); } @@ -1206,3 +1264,4 @@ protected void WriteProperties(ModelClass modelClass) #endregion Template } } + diff --git a/src/DslPackage/TextTemplates/EditingOnly/MultipleOutputHelper.cs b/src/DslPackage/TextTemplates/EditingOnly/MultipleOutputHelper.cs index cfca03202..913940b0b 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/MultipleOutputHelper.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/MultipleOutputHelper.cs @@ -1,10 +1,8 @@ #region Template -// EFDesigner v3.0.8.0 -// Copyright (c) 2017-2021 Michael Sawczyn +// EFDesigner v4.1.2.0 +// Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner // This file is only present to support transition between v2.x EFModel templates and v3+ EFModel templates // It will be removed in a future version of the tool #endregion Template - - diff --git a/src/DslPackage/TextTemplates/EditingOnly/VSIntegration.cs b/src/DslPackage/TextTemplates/EditingOnly/VSIntegration.cs index 2b14f56e7..dd986b9bf 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/VSIntegration.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/VSIntegration.cs @@ -14,8 +14,8 @@ public partial class GeneratedTextTransformation { #region Template - // EFDesigner v3.0.8.0 - // Copyright (c) 2017-2021 Michael Sawczyn + // EFDesigner v4.1.2.0 + // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner // this bit is based on EntityFramework Reverse POCO Code First Generator @@ -27,7 +27,7 @@ public partial class GeneratedTextTransformation * Interactions with Visual Studio */ - public IEnumerable GetAllProjects() + public IEnumerable GetAllProjects() { foreach (Project project in GetSolution().Projects.OfType()) { @@ -67,7 +67,7 @@ public Project GetCurrentProject() throw new InvalidOperationException("Error in GetCurrentProject(). Unable to find project."); } - private ProjectItem GetDirectoryItem(string target) + private EnvDTE.ProjectItem GetDirectoryItem(string target) { DTE dte = GetDTE(); Array projects = dte?.ActiveSolutionProjects as Array; @@ -80,7 +80,7 @@ private ProjectItem GetDirectoryItem(string target) Directory.CreateDirectory(Path.Combine(rootDirectory, target)); Queue paths = new Queue(target.Split('\\')); - ProjectItems currentItemList = currentProject.ProjectItems; + EnvDTE.ProjectItems currentItemList = currentProject.ProjectItems; bool found = false; while (paths.Any()) @@ -117,7 +117,7 @@ private ProjectItem GetDirectoryItem(string target) return targetProjectItem; } - public DTE GetDTE() + public EnvDTE.DTE GetDTE() { IServiceProvider serviceProvider = (IServiceProvider)Host; @@ -153,7 +153,7 @@ private string GetProjectPath(Project project) } } - public Solution GetSolution() + public EnvDTE.Solution GetSolution() { return GetDTE().Solution; } @@ -181,3 +181,5 @@ private IEnumerable RecurseSolutionFolder(Project project) #endregion Template } } + + diff --git a/src/DslPackage/TextTemplates/MultipleOutputHelper.ttinclude b/src/DslPackage/TextTemplates/MultipleOutputHelper.ttinclude index 3a3a235e0..7a297cbdc 100644 --- a/src/DslPackage/TextTemplates/MultipleOutputHelper.ttinclude +++ b/src/DslPackage/TextTemplates/MultipleOutputHelper.ttinclude @@ -23,7 +23,7 @@ #><#@ import namespace="System.Data.Entity.Design.PluralizationServices" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ -// EFDesigner v3.1.0.0 +// EFDesigner v4.1.2.0 // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner diff --git a/src/DslPackage/TextTemplates/VSIntegration.ttinclude b/src/DslPackage/TextTemplates/VSIntegration.ttinclude index 5d5f51745..78e6fe15f 100644 --- a/src/DslPackage/TextTemplates/VSIntegration.ttinclude +++ b/src/DslPackage/TextTemplates/VSIntegration.ttinclude @@ -23,7 +23,7 @@ #><#@ import namespace="System.Data.Entity.Design.PluralizationServices" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ - // EFDesigner v3.1.0.0 + // EFDesigner v4.1.2.0 // Copyright (c) 2017-2022 Michael Sawczyn // https://github.com/msawczyn/EFDesigner @@ -36,7 +36,7 @@ * Interactions with Visual Studio */ - public IEnumerable GetAllProjects() + public IEnumerable GetAllProjects() { foreach (Project project in GetSolution().Projects.OfType()) { @@ -76,7 +76,7 @@ throw new InvalidOperationException("Error in GetCurrentProject(). Unable to find project."); } - private ProjectItem GetDirectoryItem(string target) + private EnvDTE.ProjectItem GetDirectoryItem(string target) { DTE dte = GetDTE(); Array projects = dte?.ActiveSolutionProjects as Array; @@ -89,7 +89,7 @@ Directory.CreateDirectory(Path.Combine(rootDirectory, target)); Queue paths = new Queue(target.Split('\\')); - ProjectItems currentItemList = currentProject.ProjectItems; + EnvDTE.ProjectItems currentItemList = currentProject.ProjectItems; bool found = false; while (paths.Any()) @@ -126,7 +126,7 @@ return targetProjectItem; } - public DTE GetDTE() + public EnvDTE.DTE GetDTE() { IServiceProvider serviceProvider = (IServiceProvider)Host; @@ -162,7 +162,7 @@ } } - public Solution GetSolution() + public EnvDTE.Solution GetSolution() { return GetDTE().Solution; } diff --git a/src/DslPackage/source.extension.vsixmanifest b/src/DslPackage/source.extension.vsixmanifest index 88c97878e..3b294bc2e 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/EFCoreV5/EFCore5Net5/EFModel1.efmodel b/src/Testing/EFCoreV5/EFCore5Net5/EFModel1.efmodel index d03ef259a..4bbc927df 100644 --- a/src/Testing/EFCoreV5/EFCore5Net5/EFModel1.efmodel +++ b/src/Testing/EFCoreV5/EFCore5Net5/EFModel1.efmodel @@ -1,10 +1,10 @@  - + - + - + @@ -14,36 +14,65 @@ - + - + + + + + + + + + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Testing/EFCoreV5/EFCore5Net5/EFModel1.efmodel.diagramx b/src/Testing/EFCoreV5/EFCore5Net5/EFModel1.efmodel.diagramx index a6604c26d..654a06f83 100644 --- a/src/Testing/EFCoreV5/EFCore5Net5/EFModel1.efmodel.diagramx +++ b/src/Testing/EFCoreV5/EFCore5Net5/EFModel1.efmodel.diagramx @@ -1,40 +1,43 @@ - + - + + - - - + + + - + + - - - + + + - + - + + - - - + + + - + @@ -42,15 +45,16 @@ - + + - - - + + + - + @@ -58,6 +62,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Testing/EFCoreV5/EFCore5Net5/Generated/AssocClass.generated.cs b/src/Testing/EFCoreV5/EFCore5Net5/Generated/AssocClass.generated.cs new file mode 100644 index 000000000..2bbb4675e --- /dev/null +++ b/src/Testing/EFCoreV5/EFCore5Net5/Generated/AssocClass.generated.cs @@ -0,0 +1,139 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor v4.1.2.0 +// Source: https://github.com/msawczyn/EFDesigner +// Visual Studio Marketplace: https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner +// Documentation: https://msawczyn.github.io/EFDesigner/ +// License (MIT): https://github.com/msawczyn/EFDesigner/blob/master/LICENSE +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Testing +{ + public partial class AssocClass + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected AssocClass() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static AssocClass CreateAssocClassUnsafe() + { + return new AssocClass(); + } + + /// + /// Public constructor with required data + /// + /// Unique identifier + /// Foreign key for EntityImplementation.Entity2_Entity3 <--> Entity3.EntityImplementations. + /// Foreign key for Entity2.EntityImplementations_Entity3 <--> Entity3.Entity2. + /// Association class for EntityImplementations + /// Association class for Entity2 + public AssocClass(long id, long entity2id, long entityimplementationsid, global::Testing.EntityImplementation entityimplementations, global::Testing.Entity2 entity2) + { + this.Id = id; + + this.Entity2Id = entity2id; + + this.EntityImplementationsId = entityimplementationsid; + + if (entityimplementations == null) throw new ArgumentNullException(nameof(entityimplementations)); + this.EntityImplementations = entityimplementations; + entityimplementations.Entity2_Entity3.Add(this); + + if (entity2 == null) throw new ArgumentNullException(nameof(entity2)); + this.Entity2 = entity2; + entity2.EntityImplementations_Entity3.Add(this); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// Unique identifier + /// Foreign key for EntityImplementation.Entity2_Entity3 <--> Entity3.EntityImplementations. + /// Foreign key for Entity2.EntityImplementations_Entity3 <--> Entity3.Entity2. + /// Association class for EntityImplementations + /// Association class for Entity2 + public static AssocClass Create(long id, long entity2id, long entityimplementationsid, global::Testing.EntityImplementation entityimplementations, global::Testing.Entity2 entity2) + { + return new AssocClass(id, entity2id, entityimplementationsid, entityimplementations, entity2); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// Foreign key for EntityImplementation.Entity2_Entity3 <--> Entity3.EntityImplementations. + /// + [Key] + [Required] + [System.ComponentModel.Description("Foreign key for EntityImplementation.Entity2_Entity3 <--> Entity3.EntityImplementations. ")] + public long Entity2Id { get; set; } + + /// + /// Identity, Indexed, Required + /// Foreign key for Entity2.EntityImplementations_Entity3 <--> Entity3.Entity2. + /// + [Key] + [Required] + [System.ComponentModel.Description("Foreign key for Entity2.EntityImplementations_Entity3 <--> Entity3.Entity2. ")] + public long EntityImplementationsId { get; set; } + + /// + /// Indexed, Required + /// Unique identifier + /// + [Required] + [System.ComponentModel.Description("Unique identifier")] + public long Id { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// Required<br/> + /// Association class for Entity2 + /// + [System.ComponentModel.Description("Association class for Entity2")] + [System.ComponentModel.DataAnnotations.Display(Name="Association object for Entity2")] + public virtual global::Testing.Entity2 Entity2 { get; set; } + + /// + /// Required<br/> + /// Association class for EntityImplementations + /// + [System.ComponentModel.Description("Association class for EntityImplementations")] + [System.ComponentModel.DataAnnotations.Display(Name="Association object for EntityImplementations")] + public virtual global::Testing.EntityImplementation EntityImplementations { get; set; } + + } +} + diff --git a/src/Testing/EFCoreV5/EFCore5Net5/Generated/EFModel1.generated.cs b/src/Testing/EFCoreV5/EFCore5Net5/Generated/EFModel1.generated.cs index 5d4c42aa0..e53e7a0b1 100644 --- a/src/Testing/EFCoreV5/EFCore5Net5/Generated/EFModel1.generated.cs +++ b/src/Testing/EFCoreV5/EFCore5Net5/Generated/EFModel1.generated.cs @@ -5,7 +5,7 @@ // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // -// Produced by Entity Framework Visual Editor v3.0.4.7 +// Produced by Entity Framework Visual Editor v4.1.2.0 // Source: https://github.com/msawczyn/EFDesigner // Visual Studio Marketplace: https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner // Documentation: https://msawczyn.github.io/EFDesigner/ @@ -25,7 +25,9 @@ namespace Testing public partial class EFModel1 : DbContext { #region DbSets + public virtual Microsoft.EntityFrameworkCore.DbSet AssocClasses { get; set; } public virtual Microsoft.EntityFrameworkCore.DbSet Entity1 { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Entity2 { get; set; } public virtual Microsoft.EntityFrameworkCore.DbSet EntityAbstract { get; set; } public virtual Microsoft.EntityFrameworkCore.DbSet EntityImplementation { get; set; } public virtual Microsoft.EntityFrameworkCore.DbSet EntityRelated { get; set; } @@ -37,7 +39,14 @@ public partial class EFModel1 : DbContext /// public static string ConnectionString { get; set; } = @"Data Source=.\sqlexpress;Initial Catalog=Test;Integrated Security=True"; - /// + /// + /// + /// Initializes a new instance of the class using the specified options. + /// The method will still be called to allow further + /// configuration of the options. + /// + /// + /// The options for this context. public EFModel1(DbContextOptions options) : base(options) { } @@ -55,7 +64,20 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) partial void OnModelCreatingImpl(ModelBuilder modelBuilder); partial void OnModelCreatedImpl(ModelBuilder modelBuilder); - /// + /// + /// Override this method to further configure the model that was discovered by convention from the entity types + /// exposed in properties on your derived context. The resulting model may be cached + /// and re-used for subsequent instances of your derived context. + /// + /// + /// If a model is explicitly set on the options for this context (via ) + /// then this method will not be run. + /// + /// + /// The builder being used to construct the model for this context. Databases (and other extensions) typically + /// define extension methods on this object that allow you to configure aspects of the model that are specific + /// to a given database. + /// protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -71,11 +93,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .IsRequired(); modelBuilder.Entity() - .HasOne(p => p.EntityImplementation) - .WithOne(p => p.Entity1) - .HasForeignKey("Entity1", "EntityImplementationId") - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity().Navigation(e => e.EntityImplementation).IsRequired(); + .HasMany(p => p.EntityImplementations) + .WithMany(p => p.Entity1) + .UsingEntity(x => x.ToTable("EntityImplementation_Entity1_x_Entity1_EntityImplementations")); + + modelBuilder.Entity() + .ToTable("Entity2") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .ValueGeneratedOnAdd() + .IsRequired(); + modelBuilder.Entity() + .HasMany(p => p.EntityImplementations_Entity3) + .WithOne(p => p.Entity2); modelBuilder.Entity() .ToTable("EntityAbstract") @@ -89,6 +120,22 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .Property(t => t.Test) .HasMaxLength(255); + modelBuilder.Entity() + .HasMany(p => p.Entity2) + .WithMany(p => p.EntityImplementations) + .UsingEntity(j => j.HasOne(x => x.EntityImplementations).WithMany(x => x.Entity2_AssocClass).HasForeignKey(x => x.EntityImplementationsId) + , j => j.HasOne(x => x.Entity2).WithMany(x => x.EntityImplementations_AssocClass).HasForeignKey(x => x.Entity2Id) + , j => + { + j.ToTable("AssocClasses"); + j.HasKey(t => new { t.Entity2Id, t.EntityImplementationsId }); + j.Property(t => t.Id).IsRequired(); + j.HasIndex(t => t.Id).IsUnique(); + }); + modelBuilder.Entity() + .HasMany(p => p.Entity2_Entity3) + .WithOne(p => p.EntityImplementations); + modelBuilder.Entity() .ToTable("EntityRelated") .HasKey(t => t.Id); @@ -98,9 +145,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .IsRequired(); modelBuilder.Entity() .HasOne(p => p.EntityAbstract) - .WithMany(p => p.EntityRelated) - .HasForeignKey("EntityAbstractId"); - modelBuilder.Entity().Navigation(e => e.EntityAbstract).IsRequired(); + .WithMany(p => p.EntityRelated); OnModelCreatedImpl(modelBuilder); } diff --git a/src/Testing/EFCoreV5/EFCore5Net5/Generated/Entity1.generated.cs b/src/Testing/EFCoreV5/EFCore5Net5/Generated/Entity1.generated.cs index 69b8717bd..9625dbf16 100644 --- a/src/Testing/EFCoreV5/EFCore5Net5/Generated/Entity1.generated.cs +++ b/src/Testing/EFCoreV5/EFCore5Net5/Generated/Entity1.generated.cs @@ -5,7 +5,7 @@ // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // -// Produced by Entity Framework Visual Editor v3.0.4.7 +// Produced by Entity Framework Visual Editor v4.1.2.0 // Source: https://github.com/msawczyn/EFDesigner // Visual Studio Marketplace: https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner // Documentation: https://msawczyn.github.io/EFDesigner/ @@ -29,43 +29,15 @@ public partial class Entity1 partial void Init(); /// - /// Default constructor. Protected due to required properties, but present because EF needs it. + /// Default constructor /// - protected Entity1() + public Entity1() { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Entity1 CreateEntity1Unsafe() - { - return new Entity1(); - } - - /// - /// Public constructor with required data - /// - /// - public Entity1(global::Testing.EntityImplementation entityimplementation) - { - if (entityimplementation == null) throw new ArgumentNullException(nameof(entityimplementation)); - this.EntityImplementation = entityimplementation; - entityimplementation.Entity1 = this; + EntityImplementations = new System.Collections.Generic.HashSet(); Init(); } - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - public static Entity1 Create(global::Testing.EntityImplementation entityimplementation) - { - return new Entity1(entityimplementation); - } - /************************************************************************* * Properties *************************************************************************/ @@ -83,10 +55,7 @@ public static Entity1 Create(global::Testing.EntityImplementation entityimplemen * Navigation properties *************************************************************************/ - /// - /// Required - /// - public virtual global::Testing.EntityImplementation EntityImplementation { get; set; } + public virtual ICollection EntityImplementations { get; private set; } } } diff --git a/src/Testing/EFCoreV5/EFCore5Net5/Generated/Entity2.generated.cs b/src/Testing/EFCoreV5/EFCore5Net5/Generated/Entity2.generated.cs new file mode 100644 index 000000000..8cc175b2c --- /dev/null +++ b/src/Testing/EFCoreV5/EFCore5Net5/Generated/Entity2.generated.cs @@ -0,0 +1,70 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor v4.1.2.0 +// Source: https://github.com/msawczyn/EFDesigner +// Visual Studio Marketplace: https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner +// Documentation: https://msawczyn.github.io/EFDesigner/ +// License (MIT): https://github.com/msawczyn/EFDesigner/blob/master/LICENSE +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Testing +{ + public partial class Entity2 + { + partial void Init(); + + /// + /// Default constructor + /// + public Entity2() + { + EntityImplementations_Entity3 = new System.Collections.Generic.HashSet(); + EntityImplementations = new System.Collections.Generic.HashSet(); + + Init(); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// Unique identifier + /// + [Key] + [Required] + [System.ComponentModel.Description("Unique identifier")] + public long Id { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection EntityImplementations { get; private set; } + + /// + /// Association class for EntityImplementations + /// + [System.ComponentModel.Description("Association class for EntityImplementations")] + [System.ComponentModel.DataAnnotations.Display(Name="Association object for EntityImplementations")] + public virtual ICollection EntityImplementations_Entity3 { get; private set; } + + } +} + diff --git a/src/Testing/EFCoreV5/EFCore5Net5/Generated/EntityAbstract.generated.cs b/src/Testing/EFCoreV5/EFCore5Net5/Generated/EntityAbstract.generated.cs index 730eec85d..23959b114 100644 --- a/src/Testing/EFCoreV5/EFCore5Net5/Generated/EntityAbstract.generated.cs +++ b/src/Testing/EFCoreV5/EFCore5Net5/Generated/EntityAbstract.generated.cs @@ -5,7 +5,7 @@ // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // -// Produced by Entity Framework Visual Editor v3.0.4.7 +// Produced by Entity Framework Visual Editor v4.1.2.0 // Source: https://github.com/msawczyn/EFDesigner // Visual Studio Marketplace: https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner // Documentation: https://msawczyn.github.io/EFDesigner/ diff --git a/src/Testing/EFCoreV5/EFCore5Net5/Generated/EntityImplementation.generated.cs b/src/Testing/EFCoreV5/EFCore5Net5/Generated/EntityImplementation.generated.cs index 312dd4630..0b91286de 100644 --- a/src/Testing/EFCoreV5/EFCore5Net5/Generated/EntityImplementation.generated.cs +++ b/src/Testing/EFCoreV5/EFCore5Net5/Generated/EntityImplementation.generated.cs @@ -5,7 +5,7 @@ // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // -// Produced by Entity Framework Visual Editor v3.0.4.7 +// Produced by Entity Framework Visual Editor v4.1.2.0 // Source: https://github.com/msawczyn/EFDesigner // Visual Studio Marketplace: https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner // Documentation: https://msawczyn.github.io/EFDesigner/ @@ -33,6 +33,10 @@ public partial class EntityImplementation: global::Testing.EntityAbstract /// public EntityImplementation(): base() { + Entity2 = new System.Collections.Generic.HashSet(); + Entity2_Entity3 = new System.Collections.Generic.HashSet(); + Entity1 = new System.Collections.Generic.HashSet(); + Init(); } @@ -51,7 +55,16 @@ public EntityImplementation(): base() * Navigation properties *************************************************************************/ - public virtual global::Testing.Entity1 Entity1 { get; set; } + public virtual ICollection Entity1 { get; private set; } + + public virtual ICollection Entity2 { get; private set; } + + /// + /// Association class for Entity2 + /// + [System.ComponentModel.Description("Association class for Entity2")] + [System.ComponentModel.DataAnnotations.Display(Name="Association object for Entity2")] + public virtual ICollection Entity2_Entity3 { get; private set; } } } diff --git a/src/Testing/EFCoreV5/EFCore5Net5/Generated/EntityRelated.generated.cs b/src/Testing/EFCoreV5/EFCore5Net5/Generated/EntityRelated.generated.cs index 0f24cbb97..59ea20ae5 100644 --- a/src/Testing/EFCoreV5/EFCore5Net5/Generated/EntityRelated.generated.cs +++ b/src/Testing/EFCoreV5/EFCore5Net5/Generated/EntityRelated.generated.cs @@ -5,7 +5,7 @@ // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // -// Produced by Entity Framework Visual Editor v3.0.4.7 +// Produced by Entity Framework Visual Editor v4.1.2.0 // Source: https://github.com/msawczyn/EFDesigner // Visual Studio Marketplace: https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner // Documentation: https://msawczyn.github.io/EFDesigner/ diff --git a/src/Testing/EFCoreV5/EFCore5Net5/VSIntegration.ttinclude b/src/Testing/EFCoreV5/EFCore5Net5/VSIntegration.ttinclude new file mode 100644 index 000000000..78e6fe15f --- /dev/null +++ b/src/Testing/EFCoreV5/EFCore5Net5/VSIntegration.ttinclude @@ -0,0 +1,191 @@ +<#@ include file="EF6ModelGenerator.ttinclude" once="true" +#><#@ include file="EFCore2ModelGenerator.ttinclude" once="true" +#><#@ include file="EFCore3ModelGenerator.ttinclude" once="true" +#><#@ include file="EFCore5ModelGenerator.ttinclude" once="true" +#><#@ include file="EFCoreModelGenerator.ttinclude" once="true" +#><#@ include file="EFModelFileManager.ttinclude" once="true" +#><#@ include file="EFModelGenerator.ttinclude" once="true" +#><#@ include file="VSIntegration.ttinclude" once="true" +#><#@ assembly name="System.Core" +#><#@ assembly name="System.Data.Linq" +#><#@ assembly name="EnvDTE" +#><#@ assembly name="System.Xml" +#><#@ assembly name="System.Xml.Linq" +#><#@ import namespace="System" +#><#@ import namespace="System.IO" +#><#@ import namespace="System.Globalization" +#><#@ import namespace="System.Linq" +#><#@ import namespace="System.Security" +#><#@ import namespace="System.Text" +#><#@ import namespace="System.Collections.Generic" +#><#@ import namespace="System.Diagnostics.CodeAnalysis" +#><#@ import namespace="EnvDTE" +#><#@ import namespace="System.Data.Entity.Design.PluralizationServices" +#><#@ import namespace="Microsoft.VisualStudio.TextTemplating" +#><#+ + // EFDesigner v4.1.2.0 + // Copyright (c) 2017-2022 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; + } + } + + public Project GetCurrentProject() + { + DTE dte = GetDTE(); + + ProjectItem projectItem = dte.Solution.FindProjectItem(Host.TemplateFile); + + if (projectItem?.ContainingProject != null) + return projectItem.ContainingProject; + + // this returns SELECTED (active) project(s) - it may be a different project than the T4 template. + Array activeSolutionProjects = (Array)dte.ActiveSolutionProjects; + + if (activeSolutionProjects == null) + throw new Exception("DTE.ActiveSolutionProjects returned null"); + + if (activeSolutionProjects.Length > 0) + { + Project dteProject = (Project)activeSolutionProjects.GetValue(0); + + if (dteProject != null) + return dteProject; + } + + throw new InvalidOperationException("Error in GetCurrentProject(). Unable to find project."); + } + + private EnvDTE.ProjectItem GetDirectoryItem(string target) + { + DTE dte = GetDTE(); + Array projects = dte?.ActiveSolutionProjects as Array; + Project currentProject = projects?.GetValue(0) as Project; + ProjectItem targetProjectItem = null; + + if (currentProject != null) + { + string rootDirectory = Path.GetDirectoryName(currentProject.FullName); + Directory.CreateDirectory(Path.Combine(rootDirectory, target)); + + Queue paths = new Queue(target.Split('\\')); + EnvDTE.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; + } + } + } + + return targetProjectItem; + } + + public EnvDTE.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 EnvDTE.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; + } + } + + +#> diff --git a/src/Testing/Sandbox/Sandbox_EFCore/AppDbContext.generated.cs b/src/Testing/Sandbox/Sandbox_EFCore/AppDbContext.generated.cs index deb7b0e71..503905eac 100644 --- a/src/Testing/Sandbox/Sandbox_EFCore/AppDbContext.generated.cs +++ b/src/Testing/Sandbox/Sandbox_EFCore/AppDbContext.generated.cs @@ -5,7 +5,7 @@ // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // -// Produced by Entity Framework Visual Editor v3.0.7.1 +// Produced by Entity Framework Visual Editor v4.1.2.0 // Source: https://github.com/msawczyn/EFDesigner // Visual Studio Marketplace: https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner // Documentation: https://msawczyn.github.io/EFDesigner/ @@ -25,6 +25,8 @@ namespace SureImpact.Data.Framework public partial class AppDbContext : DbContext { #region DbSets + public virtual Microsoft.EntityFrameworkCore.DbSet Entity1 { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Entity2 { get; set; } public virtual Microsoft.EntityFrameworkCore.DbSet TestDatas { get; set; } public virtual Microsoft.EntityFrameworkCore.DbSet TestViews { get; set; } @@ -81,6 +83,25 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.HasDefaultSchema("dbo"); + modelBuilder.Entity() + .ToTable("Entity1") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .ValueGeneratedOnAdd() + .IsRequired(); + modelBuilder.Entity() + .Property(t => t.TestString) + .HasMaxLength(200) + .IsRequired(); + modelBuilder.Entity().HasIndex(t => t.TestString) + .IsUnique(); + modelBuilder.Entity() + .HasMany(p => p.TestDatas_Entity2) + .WithOne(p => p.Entity1) + .HasForeignKey(k => k.TestDatasId) + .IsRequired(); + modelBuilder.Entity() .ToTable("TestDatas") .HasKey(t => t.Id); @@ -94,9 +115,24 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .Property(t => t.Id) .ValueGeneratedOnAdd() .IsRequired(); + modelBuilder.Entity() + .HasMany(p => p.Entity1) + .WithMany(p => p.TestDatas) + .UsingEntity(x => x.ToTable("Entity1_TestDatas_x_TestData_Entity1")); + modelBuilder.Entity().Navigation(e => e.Entity1) + .HasField("_entity1") + .Metadata.SetPropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + modelBuilder.Entity().Navigation(e => e.TestDatas) + .HasField("_testDatas") + .Metadata.SetPropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + modelBuilder.Entity() + .HasMany(p => p.Entity1_Entity2) + .WithOne(p => p.TestDatas) + .HasForeignKey(k => k.Entity1Id) + .IsRequired(); modelBuilder.Entity() - .ToTable("TestViews"); + .ToView("TestView"); modelBuilder.Entity() .Property(t => t.TestString) .HasMaxLength(200) diff --git a/src/Testing/Sandbox/Sandbox_EFCore/EFModel1.efmodel b/src/Testing/Sandbox/Sandbox_EFCore/EFModel1.efmodel index 229e2c6ef..25fc58525 100644 --- a/src/Testing/Sandbox/Sandbox_EFCore/EFModel1.efmodel +++ b/src/Testing/Sandbox/Sandbox_EFCore/EFModel1.efmodel @@ -1,5 +1,5 @@  - + @@ -18,6 +18,9 @@ + + + @@ -27,6 +30,20 @@ + + + + + + + + + + + + + + diff --git a/src/Testing/Sandbox/Sandbox_EFCore/EFModel1.efmodel.diagramx b/src/Testing/Sandbox/Sandbox_EFCore/EFModel1.efmodel.diagramx index da0cb5190..578ecaf05 100644 --- a/src/Testing/Sandbox/Sandbox_EFCore/EFModel1.efmodel.diagramx +++ b/src/Testing/Sandbox/Sandbox_EFCore/EFModel1.efmodel.diagramx @@ -3,25 +3,25 @@ - + - - + + - + - - + + - + @@ -29,6 +29,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Testing/Sandbox/Sandbox_EFCore/Entity1.generated.cs b/src/Testing/Sandbox/Sandbox_EFCore/Entity1.generated.cs new file mode 100644 index 000000000..cf74a752d --- /dev/null +++ b/src/Testing/Sandbox/Sandbox_EFCore/Entity1.generated.cs @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor v4.1.2.0 +// Source: https://github.com/msawczyn/EFDesigner +// Visual Studio Marketplace: https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner +// Documentation: https://msawczyn.github.io/EFDesigner/ +// License (MIT): https://github.com/msawczyn/EFDesigner/blob/master/LICENSE +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace SureImpact.Data.Framework +{ + public partial class Entity1 + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Entity1() + { + TestDatas_Entity2 = new System.Collections.Generic.HashSet(); + _testDatas = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Entity1 CreateEntity1Unsafe() + { + return new Entity1(); + } + + /// + /// Public constructor with required data + /// + /// Test string + public Entity1(string teststring) + { + if (string.IsNullOrEmpty(teststring)) throw new ArgumentNullException(nameof(teststring)); + this.TestString = teststring; + + TestDatas_Entity2 = new System.Collections.Generic.HashSet(); + _testDatas = new System.Collections.Generic.HashSet(); + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// Test string + public static Entity1 Create(string teststring) + { + return new Entity1(teststring); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// Unique identifier + /// + [Key] + [Required] + [System.ComponentModel.Description("Unique identifier")] + public long Id { get; set; } + + /// + /// Indexed, Required, Max length = 200 + /// Test string + /// + [Required] + [MaxLength(200)] + [StringLength(200)] + [System.ComponentModel.Description("Test string")] + public string TestString { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// Backing field for TestDatas + /// + protected ICollection _testDatas; + + public virtual ICollection TestDatas + { + get + { + return _testDatas; + } + private set + { + _testDatas = value; + } + } + + /// + /// Association class for TestDatas + /// + [System.ComponentModel.Description("Association class for TestDatas")] + [System.ComponentModel.DataAnnotations.Display(Name="Association object for TestDatas")] + public virtual ICollection TestDatas_Entity2 { get; private set; } + + } +} + diff --git a/src/Testing/Sandbox/Sandbox_EFCore/Entity2.generated.cs b/src/Testing/Sandbox/Sandbox_EFCore/Entity2.generated.cs new file mode 100644 index 000000000..69e1beef5 --- /dev/null +++ b/src/Testing/Sandbox/Sandbox_EFCore/Entity2.generated.cs @@ -0,0 +1,139 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor v4.1.2.0 +// Source: https://github.com/msawczyn/EFDesigner +// Visual Studio Marketplace: https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner +// Documentation: https://msawczyn.github.io/EFDesigner/ +// License (MIT): https://github.com/msawczyn/EFDesigner/blob/master/LICENSE +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace SureImpact.Data.Framework +{ + public partial class Entity2 + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Entity2() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Entity2 CreateEntity2Unsafe() + { + return new Entity2(); + } + + /// + /// Public constructor with required data + /// + /// Unique identifier + /// Foreign key for TestData.Entity1_Entity2 <--> Entity2.TestDatas. + /// Foreign key for Entity1.TestDatas_Entity2 <--> Entity2.Entity1. + /// Association class for TestDatas + /// Association class for Entity1 + public Entity2(long id, long entity1id, long testdatasid, global::SureImpact.Data.Framework.TestData testdatas, global::SureImpact.Data.Framework.Entity1 entity1) + { + this.Id = id; + + this.Entity1Id = entity1id; + + this.TestDatasId = testdatasid; + + if (testdatas == null) throw new ArgumentNullException(nameof(testdatas)); + this.TestDatas = testdatas; + testdatas.Entity1_Entity2.Add(this); + + if (entity1 == null) throw new ArgumentNullException(nameof(entity1)); + this.Entity1 = entity1; + entity1.TestDatas_Entity2.Add(this); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// Unique identifier + /// Foreign key for TestData.Entity1_Entity2 <--> Entity2.TestDatas. + /// Foreign key for Entity1.TestDatas_Entity2 <--> Entity2.Entity1. + /// Association class for TestDatas + /// Association class for Entity1 + public static Entity2 Create(long id, long entity1id, long testdatasid, global::SureImpact.Data.Framework.TestData testdatas, global::SureImpact.Data.Framework.Entity1 entity1) + { + return new Entity2(id, entity1id, testdatasid, testdatas, entity1); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// Foreign key for TestData.Entity1_Entity2 <--> Entity2.TestDatas. + /// + [Key] + [Required] + [System.ComponentModel.Description("Foreign key for TestData.Entity1_Entity2 <--> Entity2.TestDatas. ")] + public long Entity1Id { get; set; } + + /// + /// Indexed, Required + /// Unique identifier + /// + [Required] + [System.ComponentModel.Description("Unique identifier")] + public long Id { get; set; } + + /// + /// Identity, Indexed, Required + /// Foreign key for Entity1.TestDatas_Entity2 <--> Entity2.Entity1. + /// + [Key] + [Required] + [System.ComponentModel.Description("Foreign key for Entity1.TestDatas_Entity2 <--> Entity2.Entity1. ")] + public long TestDatasId { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// Required<br/> + /// Association class for Entity1 + /// + [System.ComponentModel.Description("Association class for Entity1")] + [System.ComponentModel.DataAnnotations.Display(Name="Association object for Entity1")] + public virtual global::SureImpact.Data.Framework.Entity1 Entity1 { get; set; } + + /// + /// Required<br/> + /// Association class for TestDatas + /// + [System.ComponentModel.Description("Association class for TestDatas")] + [System.ComponentModel.DataAnnotations.Display(Name="Association object for TestDatas")] + public virtual global::SureImpact.Data.Framework.TestData TestDatas { get; set; } + + } +} + diff --git a/src/Testing/Sandbox/Sandbox_EFCore/Sandbox_EFCore.csproj b/src/Testing/Sandbox/Sandbox_EFCore/Sandbox_EFCore.csproj index bb3e1e363..b7f4e19d6 100644 --- a/src/Testing/Sandbox/Sandbox_EFCore/Sandbox_EFCore.csproj +++ b/src/Testing/Sandbox/Sandbox_EFCore/Sandbox_EFCore.csproj @@ -50,6 +50,12 @@ True EFModel1.tt + + EFModel1.tt + + + EFModel1.tt + EFModel1.tt diff --git a/src/Testing/Sandbox/Sandbox_EFCore/TestData.generated.cs b/src/Testing/Sandbox/Sandbox_EFCore/TestData.generated.cs index b483b6259..61b78a153 100644 --- a/src/Testing/Sandbox/Sandbox_EFCore/TestData.generated.cs +++ b/src/Testing/Sandbox/Sandbox_EFCore/TestData.generated.cs @@ -5,7 +5,7 @@ // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // -// Produced by Entity Framework Visual Editor v3.0.7.1 +// Produced by Entity Framework Visual Editor v4.1.2.0 // Source: https://github.com/msawczyn/EFDesigner // Visual Studio Marketplace: https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner // Documentation: https://msawczyn.github.io/EFDesigner/ @@ -33,6 +33,9 @@ public partial class TestData /// protected TestData() { + _entity1 = new System.Collections.Generic.HashSet(); + Entity1_Entity2 = new System.Collections.Generic.HashSet(); + Init(); } @@ -53,6 +56,8 @@ public TestData(string teststring) if (string.IsNullOrEmpty(teststring)) throw new ArgumentNullException(nameof(teststring)); this.TestString = teststring; + _entity1 = new System.Collections.Generic.HashSet(); + Entity1_Entity2 = new System.Collections.Generic.HashSet(); Init(); } @@ -69,6 +74,15 @@ public static TestData Create(string teststring) * Properties *************************************************************************/ + /// + /// Identity, Indexed, Required + /// Unique identifier + /// + [Key] + [Required] + [System.ComponentModel.Description("Unique identifier")] + public long Id { get; set; } + /// /// Indexed, Required, Max length = 200 /// Test string @@ -79,14 +93,33 @@ public static TestData Create(string teststring) [System.ComponentModel.Description("Test string")] public string TestString { get; set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ + /// - /// Identity, Indexed, Required - /// Unique identifier + /// Backing field for Entity1 /// - [Key] - [Required] - [System.ComponentModel.Description("Unique identifier")] - public long Id { get; set; } + protected ICollection _entity1; + + public virtual ICollection Entity1 + { + get + { + return _entity1; + } + private set + { + _entity1 = value; + } + } + + /// + /// Association class for Entity1 + /// + [System.ComponentModel.Description("Association class for Entity1")] + [System.ComponentModel.DataAnnotations.Display(Name="Association object for Entity1")] + public virtual ICollection Entity1_Entity2 { get; private set; } } } diff --git a/src/Testing/Sandbox/Sandbox_EFCore/TestView.generated.cs b/src/Testing/Sandbox/Sandbox_EFCore/TestView.generated.cs index 25eb95f9d..ba9f4810f 100644 --- a/src/Testing/Sandbox/Sandbox_EFCore/TestView.generated.cs +++ b/src/Testing/Sandbox/Sandbox_EFCore/TestView.generated.cs @@ -5,7 +5,7 @@ // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // -// Produced by Entity Framework Visual Editor v3.0.7.1 +// Produced by Entity Framework Visual Editor v4.1.2.0 // Source: https://github.com/msawczyn/EFDesigner // Visual Studio Marketplace: https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner // Documentation: https://msawczyn.github.io/EFDesigner/ diff --git a/src/Utilities/EF6Parser/Parser.cs b/src/Utilities/EF6Parser/Parser.cs index d7c8f3a10..36a458388 100644 --- a/src/Utilities/EF6Parser/Parser.cs +++ b/src/Utilities/EF6Parser/Parser.cs @@ -419,7 +419,7 @@ private ModelClass ProcessEntity(string entityFullName, EntityType oSpaceType, E IsAbstract = oSpaceType.Abstract, BaseClass = GetTypeFullName(oSpaceType.BaseType?.Name), CustomInterfaces = type.GetInterfaces().Any() - ? string.Join(",", type.GetInterfaces().Select(GetTypeFullName)) + ? string.Join(",", type.GetInterfaces().Select(GetTypeFullName).Where(s => s != null)) : null, IsDependentType = false, CustomAttributes = customAttributes.Length > 2 diff --git a/src/Utilities/EF6Parser/Properties/AssemblyInfo.cs b/src/Utilities/EF6Parser/Properties/AssemblyInfo.cs index d3b574be2..c216129cd 100644 --- a/src/Utilities/EF6Parser/Properties/AssemblyInfo.cs +++ b/src/Utilities/EF6Parser/Properties/AssemblyInfo.cs @@ -17,6 +17,6 @@ [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.0.5")] -[assembly: AssemblyFileVersion("3.0.5")] +[assembly: AssemblyVersion("4.1.2.0")] +[assembly: AssemblyFileVersion("4.1.2.0")] [assembly: ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)] diff --git a/src/Utilities/EFCore2Parser/Parser.cs b/src/Utilities/EFCore2Parser/Parser.cs index 5270b12ea..6ff5d8963 100644 --- a/src/Utilities/EFCore2Parser/Parser.cs +++ b/src/Utilities/EFCore2Parser/Parser.cs @@ -225,7 +225,7 @@ private ModelClass ProcessEntity(IEntityType entityType, ModelRoot modelRoot) result.CustomAttributes = GetCustomAttributes(type.CustomAttributes); result.CustomInterfaces = type.GetInterfaces().Any() - ? string.Join(",", type.GetInterfaces().Select(GetTypeFullName)) + ? string.Join(",", type.GetInterfaces().Select(GetTypeFullName).Where(s => s != null)) : null; result.Properties = entityType.GetDeclaredProperties() diff --git a/src/Utilities/EFCore2Parser/Properties/AssemblyInfo.cs b/src/Utilities/EFCore2Parser/Properties/AssemblyInfo.cs index a4aec548f..648aa37aa 100644 --- a/src/Utilities/EFCore2Parser/Properties/AssemblyInfo.cs +++ b/src/Utilities/EFCore2Parser/Properties/AssemblyInfo.cs @@ -17,6 +17,6 @@ [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.0.5")] -[assembly: AssemblyFileVersion("3.0.5")] +[assembly: AssemblyVersion("4.1.2.0")] +[assembly: AssemblyFileVersion("4.1.2.0")] [assembly: ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)] diff --git a/src/Utilities/EFCore3Parser/Parser.cs b/src/Utilities/EFCore3Parser/Parser.cs index 9a269d3c0..cdcd45cab 100644 --- a/src/Utilities/EFCore3Parser/Parser.cs +++ b/src/Utilities/EFCore3Parser/Parser.cs @@ -220,7 +220,7 @@ private ModelClass ProcessEntity(IEntityType entityType, ModelRoot modelRoot) result.CustomAttributes = GetCustomAttributes(type.CustomAttributes); result.CustomInterfaces = type.GetInterfaces().Any() - ? string.Join(",", type.GetInterfaces().Select(GetTypeFullName)) + ? string.Join(",", type.GetInterfaces().Select(GetTypeFullName).Where(s => s != null)) : null; result.Properties = entityType.GetDeclaredProperties() diff --git a/src/Utilities/EFCore3Parser/Properties/AssemblyInfo.cs b/src/Utilities/EFCore3Parser/Properties/AssemblyInfo.cs index 1a0edaf93..b78b4a645 100644 --- a/src/Utilities/EFCore3Parser/Properties/AssemblyInfo.cs +++ b/src/Utilities/EFCore3Parser/Properties/AssemblyInfo.cs @@ -17,6 +17,6 @@ [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.0.5")] -[assembly: AssemblyFileVersion("3.0.5")] +[assembly: AssemblyVersion("4.1.2.0")] +[assembly: AssemblyFileVersion("4.1.2.0")] [assembly: ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)] diff --git a/src/Utilities/EFCore5Parser/Parser.cs b/src/Utilities/EFCore5Parser/Parser.cs index c2d135403..3917a4d7a 100644 --- a/src/Utilities/EFCore5Parser/Parser.cs +++ b/src/Utilities/EFCore5Parser/Parser.cs @@ -220,7 +220,7 @@ private ModelClass ProcessEntity(IEntityType entityType, ModelRoot modelRoot) result.CustomAttributes = GetCustomAttributes(type.CustomAttributes); result.CustomInterfaces = type.GetInterfaces().Any() - ? string.Join(",", type.GetInterfaces().Select(GetTypeFullName)) + ? string.Join(",", type.GetInterfaces().Select(GetTypeFullName).Where(s => s != null)) : null; result.Properties = entityType.GetDeclaredProperties() diff --git a/src/Utilities/EFCore5Parser/Properties/AssemblyInfo.cs b/src/Utilities/EFCore5Parser/Properties/AssemblyInfo.cs index e21d45095..1c8258a99 100644 --- a/src/Utilities/EFCore5Parser/Properties/AssemblyInfo.cs +++ b/src/Utilities/EFCore5Parser/Properties/AssemblyInfo.cs @@ -17,5 +17,5 @@ [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.0.5")] -[assembly: AssemblyFileVersion("3.0.5")] +[assembly: AssemblyVersion("4.1.2.0")] +[assembly: AssemblyFileVersion("4.1.2.0")] diff --git a/src/Utilities/ParsingModels/AssemblyInfo.cs b/src/Utilities/ParsingModels/AssemblyInfo.cs index a38fb2452..50c8d7d71 100644 --- a/src/Utilities/ParsingModels/AssemblyInfo.cs +++ b/src/Utilities/ParsingModels/AssemblyInfo.cs @@ -19,6 +19,6 @@ [assembly: Guid("fb2035a3-09f5-43ff-8545-3af8b814b405")] -[assembly: AssemblyVersion("3.1.0.0")] -[assembly: AssemblyFileVersion("3.1.0.0")] +[assembly: AssemblyVersion("4.1.2.0")] +[assembly: AssemblyFileVersion("4.1.2.0")] [assembly: ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)] diff --git a/src/Utilities/ParsingModels/ParserBase.cs b/src/Utilities/ParsingModels/ParserBase.cs index 8436f146b..db3bbdfbe 100644 --- a/src/Utilities/ParsingModels/ParserBase.cs +++ b/src/Utilities/ParsingModels/ParserBase.cs @@ -12,11 +12,14 @@ public abstract class ParserBase protected static string GetTypeFullName(Type type) { - return GetTypeFullName(type.FullName); + return GetTypeFullName(type?.FullName); } protected static string GetTypeFullName(string fullName) { + if (string.IsNullOrWhiteSpace(fullName)) + return null; + Match m = TypeNameRegex.Match(fullName); if (m.Success)