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 87c4b1bc0..d3b17f6f0 100644 Binary files a/src/DslPackage/Parsers/EF6Parser.exe and b/src/DslPackage/Parsers/EF6Parser.exe differ diff --git a/src/DslPackage/Parsers/EFCore2Parser.exe b/src/DslPackage/Parsers/EFCore2Parser.exe index f4a5cea8c..7fe532a82 100644 Binary files a/src/DslPackage/Parsers/EFCore2Parser.exe and b/src/DslPackage/Parsers/EFCore2Parser.exe differ diff --git a/src/DslPackage/Parsers/EFCore3Parser.exe b/src/DslPackage/Parsers/EFCore3Parser.exe index 97a723975..e379e3367 100644 Binary files a/src/DslPackage/Parsers/EFCore3Parser.exe and b/src/DslPackage/Parsers/EFCore3Parser.exe differ diff --git a/src/DslPackage/Parsers/EFCore5Parser.exe b/src/DslPackage/Parsers/EFCore5Parser.exe index dcc21151a..297fd0c2c 100644 Binary files a/src/DslPackage/Parsers/EFCore5Parser.exe and b/src/DslPackage/Parsers/EFCore5Parser.exe differ diff --git a/src/DslPackage/PreviewImage.png b/src/DslPackage/PreviewImage.png new file mode 100644 index 000000000..abd79712b Binary files /dev/null and b/src/DslPackage/PreviewImage.png differ 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)