Skip to content
This repository has been archived by the owner on Jan 16, 2024. It is now read-only.

Commit

Permalink
1.2.6.7
Browse files Browse the repository at this point in the history
   - An entity's concurrency token property is no longer a required parameter in its constructor (#24)
   - Simplified cascade delete settings in property editor for associations
   - Fixed bad code generation in EFCore for cascade delete overrides
   - Missing files when generating code for .NET Core projects fixed
   - Tightened up and swatted some bugs in INotifyPropertyChanged handling. Added documentation to doc site for this feature
  • Loading branch information
msawczyn committed Sep 23, 2018
1 parent b8831b0 commit 055afc4
Show file tree
Hide file tree
Showing 95 changed files with 2,924 additions and 1,326 deletions.
27 changes: 14 additions & 13 deletions TODO.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,22 @@ T4: Implement as CascadeDeleteConvention replacement generating delete triggers
Next release (v1.2.6.7+)

-------------------------------------------------------------
Cascade Delete fixes
-------------------------------------------------------------

- In EFCore, change WillCascadeOnDelete calls to OnDelete calls
- Property editor should only show cascade delete options on the principal entity
- Update documentation to discuss cascade deletion propagating from principal to dependents ONLY
reference https://docs.microsoft.com/en-us/ef/core/saving/cascade-delete and http://www.entityframeworktutorial.net/code-first/cascade-delete-in-code-first.aspx

-------------------------------------------------------------
Concurrency Token fixes
Misc
-------------------------------------------------------------
- Getter and Setter visibility not public for concurrency token property
- Property editor multiline attribute doesn't seem to be working
- (Comment text working)
- Enum summary (enum detail working)
- Class summary
- Class detail

-------------------------------------------------------------
Misc
Docs
-------------------------------------------------------------
- Property editor multiline attribute doesn't seem to be working
- When generating files, if you don't put in a directory name for the files they'll be created as children of the T4. Nothing can be done about that.
- Enhance discussion of INotifyPropertyChanged
- automatically creates backing field
- valid for properties and single objects, not collections (use ObservableCollection or BindingList)
- Enhance discussion of cascade delete
- Update documentation to discuss cascade deletion propagating from principal to dependents ONLY
reference https://docs.microsoft.com/en-us/ef/core/saving/cascade-delete and http://www.entityframeworktutorial.net/code-first/cascade-delete-in-code-first.aspx

6 changes: 5 additions & 1 deletion changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
1.2.6.7
- An entity's concurrency token property is no longer a required parameter in its constructor (https://github.com/msawczyn/EFDesigner/issues/24).
- An entity's concurrency token property is no longer a required parameter in its constructor (https://github.com/msawczyn/EFDesigner/issues/24)
- Simplified cascade delete settings in property editor for associations
- Fixed bad code generation in EFCore for cascade delete overrides
- Missing files when generating code for .NET Core projects fixed
- Tightened up and swatted some bugs in INotifyPropertyChanged handling. Added documentation to doc site for this feature

1.2.6.6
- Deleting a generalization or superclass gives the choice of pushing attributes and associations down to the former child class(es)
Expand Down
34 changes: 28 additions & 6 deletions src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude
Original file line number Diff line number Diff line change
Expand Up @@ -416,10 +416,21 @@ void WriteDbContextEFCore(ModelRoot modelRoot)
if (required)
segments.Add("IsRequired()");

if (association.TargetDeleteAction != DeleteAction.Default)
if ((association.TargetDeleteAction != DeleteAction.Default && association.SourceRole == EndpointRole.Principal) ||
(association.SourceDeleteAction != DeleteAction.Default && association.TargetRole == EndpointRole.Principal))
{
string willCascadeOnDelete = (association.TargetDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant();
segments.Add($"WillCascadeOnDelete({willCascadeOnDelete})");
DeleteAction deleteAction = (association.SourceRole == EndpointRole.Principal)
? association.TargetDeleteAction
: association.SourceDeleteAction;
switch (deleteAction)
{
case DeleteAction.None:
segments.Add("OnDelete(DeleteBehavior.Restrict)");
break;
case DeleteAction.Cascade:
segments.Add("OnDelete(DeleteBehavior.Cascade)");
break;
}
}

if (modelRoot.ChopMethodChains)
Expand Down Expand Up @@ -482,11 +493,22 @@ void WriteDbContextEFCore(ModelRoot modelRoot)
if (required)
segments.Add("IsRequired()");

if (association.SourceDeleteAction != DeleteAction.Default)
if ((association.TargetDeleteAction != DeleteAction.Default && association.SourceRole == EndpointRole.Principal) ||
(association.SourceDeleteAction != DeleteAction.Default && association.TargetRole == EndpointRole.Principal))
{
DeleteAction deleteAction = (association.SourceRole == EndpointRole.Principal)
? association.TargetDeleteAction
: association.SourceDeleteAction;
switch (deleteAction)
{
string willCascadeOnDelete = (association.SourceDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant();
segments.Add($"WillCascadeOnDelete({willCascadeOnDelete})");
case DeleteAction.None:
segments.Add("OnDelete(DeleteBehavior.Restrict)");
break;
case DeleteAction.Cascade:
segments.Add("OnDelete(DeleteBehavior.Cascade)");
break;
}
}

if (modelRoot.ChopMethodChains)
OutputChopped(segments);
Expand Down
84 changes: 66 additions & 18 deletions src/DslPackage/TextTemplates/EFDesigner.ttinclude
Original file line number Diff line number Diff line change
Expand Up @@ -524,9 +524,9 @@ void WritePersistentProperties(ModelClass modelClass)

string nullable = IsNullable(modelAttribute) ? "?" : "";

if (!modelAttribute.AutoProperty && !modelAttribute.IsConcurrencyToken)
if (!modelAttribute.IsConcurrencyToken && (modelClass.ImplementNotify || !modelAttribute.AutoProperty))
{
GenerateClassAnnotations(modelAttribute, "_");
//GeneratePropertyAnnotations(modelAttribute, "_");
Output($"protected {modelAttribute.FQPrimitiveType}{nullable} _{modelAttribute.Name};");
Output($"partial void Set{modelAttribute.Name}({modelAttribute.FQPrimitiveType}{nullable} oldValue, ref {modelAttribute.FQPrimitiveType}{nullable} newValue);");
Output($"partial void Get{modelAttribute.Name}(ref {modelAttribute.FQPrimitiveType}{nullable} result);");
Expand Down Expand Up @@ -554,24 +554,34 @@ void WritePersistentProperties(ModelClass modelClass)
modelAttribute.SetterVisibility == SetterAccessModifier.Internal ? "internal " :
"";

if (modelAttribute.AutoProperty || modelAttribute.IsConcurrencyToken)
GeneratePropertyAnnotations(modelAttribute);

if (modelAttribute.IsConcurrencyToken || (!modelClass.ImplementNotify && modelAttribute.AutoProperty))
{
GenerateClassAnnotations(modelAttribute);
Output($"public {modelAttribute.FQPrimitiveType}{nullable} {modelAttribute.Name} {{ get; {setterVisibility}set; }}");
}
else
{
string notifyCode = (modelClass.ImplementNotify) ? " OnPropertyChanged();" : "";

Output($"public {modelAttribute.FQPrimitiveType}{nullable} {modelAttribute.Name}");
Output("{");
Output($"get {{ {modelAttribute.FQPrimitiveType}{nullable} value = _{modelAttribute.Name}; Get{modelAttribute.Name}(ref value); return (_{modelAttribute.Name} = value); }}");
Output($"{setterVisibility}set {{ {modelAttribute.FQPrimitiveType}{nullable} oldValue = _{modelAttribute.Name}; Set{modelAttribute.Name}(oldValue, ref value); _{modelAttribute.Name} = value; {notifyCode} }}");
//Output($"{setterVisibility}set {{if (_{modelAttribute.Name} != value) {{{modelAttribute.FQPrimitiveType}{nullable} old{modelAttribute.Name}=_{modelAttribute.Name}; {modelAttribute.Name}Changing(old{modelAttribute.Name}, value); _{modelAttribute.Name} = value;OnPropertyChanged(nameof({modelAttribute.Name})); {modelAttribute.Name}Changed(old{modelAttribute.Name}, value);}}}}");
Output("get");
Output("{");
Output($"{modelAttribute.FQPrimitiveType}{nullable} value = _{modelAttribute.Name};");
Output($"Get{modelAttribute.Name}(ref value);");
Output($"return (_{modelAttribute.Name} = value);");
Output("}");
Output($"{setterVisibility}set");
Output("{");
Output($"{modelAttribute.FQPrimitiveType}{nullable} oldValue = _{modelAttribute.Name};");
Output($"Set{modelAttribute.Name}(oldValue, ref value);");
Output("if (oldValue != value)");
Output("{");
Output($"_{modelAttribute.Name} = value;");
if (modelClass.ImplementNotify)
Output("OnPropertyChanged();");
Output("}");
Output("}");
Output("}");

Output($"partial void {modelAttribute.Name}Changing({modelAttribute.FQPrimitiveType}{nullable} old{modelAttribute.Name}, ref {modelAttribute.FQPrimitiveType}{nullable} new{modelAttribute.Name});");
Output($"partial void {modelAttribute.Name}Changed({modelAttribute.FQPrimitiveType}{nullable} old{modelAttribute.Name}, {modelAttribute.FQPrimitiveType}{nullable} new{modelAttribute.Name});");
}

NL();
Expand All @@ -589,12 +599,12 @@ void WritePersistentProperties(ModelClass modelClass)
}
}

void GenerateClassAnnotations(ModelAttribute modelAttribute, string namePrefix = null)
void GeneratePropertyAnnotations(ModelAttribute modelAttribute, string namePrefix = null)
{
if (modelAttribute.IsIdentity) Output("[Key]");
if (modelAttribute.Required) Output("[Required]");
if (modelAttribute.MaxLength > 0) Output($"[MaxLength({modelAttribute.MaxLength})]");
if (modelAttribute.IsConcurrencyToken) Output("[ConcurrencyCheck]");
if (modelAttribute.MaxLength > 0) Output($"[MaxLength({modelAttribute.MaxLength})]");

if (modelAttribute.ModelClass.ModelRoot.EntityFrameworkVersion != EFVersion.EFCore)
{
Expand Down Expand Up @@ -660,10 +670,19 @@ void WritePersistentNavigationProperties(ModelClass modelClass, bool requireVirt
? $"ICollection<{navigationProperty.ClassType.FullName}>"
: navigationProperty.ClassType.FullName;

if (modelClass.ImplementNotify && !navigationProperty.IsCollection)
{
Output($"protected {type} _{navigationProperty.PropertyName};");
Output($"partial void Set{navigationProperty.PropertyName}({type} oldValue, ref {type} newValue);");
Output($"partial void Get{navigationProperty.PropertyName}(ref {type} result);");

NL();
}

List<string> comments = new List<string>();
if (navigationProperty.Required)
comments.Add("Required");
string comment = comments.Count > 0 ? $" // {string.Join(", ", comments)}" : "";
string comment = comments.Count > 0 ? string.Join(", ", comments) : string.Empty;
string _virtual = requireVirtual ? "virtual " : "";

if (!string.IsNullOrEmpty(navigationProperty.Summary) || !string.IsNullOrEmpty(comment))
Expand All @@ -680,7 +699,35 @@ void WritePersistentNavigationProperties(ModelClass modelClass, bool requireVirt
Output("/// </remarks>");
}

Output($"public {_virtual}{type} {navigationProperty.PropertyName} {{ get; set; }} {comment}");
if (!modelClass.ImplementNotify || navigationProperty.IsCollection)
{
Output($"public {_virtual}{type} {navigationProperty.PropertyName} {{ get; set; }}");
}
else
{
Output($"public {type} {navigationProperty.PropertyName}");
Output("{");
Output("get");
Output("{");
Output($"{type} value = _{navigationProperty.PropertyName};");
Output($"Get{navigationProperty.PropertyName}(ref value);");
Output($"return (_{navigationProperty.PropertyName} = value);");
Output("}");
Output($"set");
Output("{");
Output($"{type} oldValue = _{navigationProperty.PropertyName};");
Output($"Set{navigationProperty.PropertyName}(oldValue, ref value);");
Output("if (oldValue != value)");
Output("{");
Output($"_{navigationProperty.PropertyName} = value;");
if (modelClass.ImplementNotify)
Output("OnPropertyChanged();");
Output("}");
Output("}");
Output("}");
}

NL();
}
}

Expand Down Expand Up @@ -728,9 +775,10 @@ void WriteNotifyPropertyChanged(ModelClass modelClass)
if (!modelClass.ImplementNotify)
return;

Output("public event PropertyChangedEventHandler PropertyChanged;");
string modifier = (modelClass.Superclass?.ImplementNotify == true) ? "override" : "virtual";
Output($"public {modifier} event PropertyChangedEventHandler PropertyChanged;");
NL();
Output("protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)");
Output($"protected {modifier} void OnPropertyChanged([CallerMemberName] string propertyName = null)");
Output("{");
Output("PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));");
Output("}");
Expand Down
48 changes: 25 additions & 23 deletions src/DslPackage/TextTemplates/MultipleOutputHelper.ttinclude
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#><#@ import namespace="Microsoft.VisualStudio.TextTemplating"
#><#+

// EFDesigner v1.2.6.7
// EFDesigner v1.2.5.1
// Copyright (c) 2017-2018 Michael Sawczyn
// https://github.com/msawczyn/EFDesigner
//
Expand Down Expand Up @@ -176,10 +176,12 @@ class Manager

protected override void CreateFile(string fileName, string content)
{
string directory = Path.GetDirectoryName(fileName);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);

if (IsFileContentDifferent(fileName, content))
{
if (!Directory.Exists(Path.GetDirectoryName(fileName)))
Directory.CreateDirectory(Path.GetDirectoryName(fileName));
CheckoutFileIfRequired(fileName);
File.WriteAllText(fileName, content);
}
Expand Down Expand Up @@ -242,31 +244,31 @@ class Manager
internal void ProjectSync(IEnumerable<string> keepFileNames)
{
Dictionary<ProjectItem, List<string>> current = GetCurrentState();
List<string> allCurrentFiles = current.Keys.SelectMany(k => current[k]).ToList();

Dictionary<ProjectItem, List<string>> target = GetTargetState(keepFileNames);
List<string> allTargetFiles = target.Keys.SelectMany(k => target[k]).ToList();

Dictionary<ProjectItem, List<string>> add = new Dictionary<ProjectItem, List<string>>();
foreach (ProjectItem parentItem in target.Keys)
{
IEnumerable<string> additions = target[parentItem].Except(current.ContainsKey(parentItem) ? current[parentItem] : new List<string>());
foreach (string addition in additions)
parentItem.ProjectItems.AddFromFile(addition);
}
List<string> existingFiles = new List<string>();

Dictionary<ProjectItem, List<string>> remove = new Dictionary<ProjectItem, List<string>>();
foreach (ProjectItem parentItem in current.Keys)
{
IEnumerable<string> removals = current[parentItem].Except(target.ContainsKey(parentItem) ? target[parentItem] : new List<string>());
foreach (string removal in removals)
dte.Solution.FindProjectItem(removal).Delete();
}
foreach (ProjectItem parentItem in current.Keys.ToList())
{
foreach (string filename in current[parentItem])
{
if (!allTargetFiles.Contains(filename) && !keepFileNames.Contains(filename))
dte.Solution.FindProjectItem(filename)?.Delete();
else
existingFiles.Add(filename);
}
}

// just to be safe
existingFiles = existingFiles.Distinct().ToList();

List<string> keepUnderTemplate = (target.ContainsKey(templateProjectItem)
? target[templateProjectItem]
: new List<string>()).Select(s => Path.GetFileName(s)).ToList();
for (int index = 1; index <= templateProjectItem.ProjectItems.Count; ++index)
foreach (ProjectItem parentItem in target.Keys.ToList())
{
if (!keepUnderTemplate.Contains(templateProjectItem.ProjectItems.Item(index).Name))
templateProjectItem.ProjectItems.Item(index--).Delete();
foreach (string filename in target[parentItem].Except(existingFiles).ToList())
parentItem.ProjectItems.AddFromFile(filename);
}
}

Expand Down
6 changes: 0 additions & 6 deletions src/EFDesigner.sln
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_layouts", "_layouts", "{90
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dsl.Tests", "Dsl.Tests\Dsl.Tests.csproj", "{E6BE334C-26DF-4D6C-90D5-AB80D7881219}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Testing_CoreV2NetStd", "Testing\Testing_CoreV2\Testing_CoreV2NetStd.csproj", "{D589AC83-484E-4DDC-98F9-B7E75E10C2C0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -95,10 +93,6 @@ Global
{E6BE334C-26DF-4D6C-90D5-AB80D7881219}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E6BE334C-26DF-4D6C-90D5-AB80D7881219}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6BE334C-26DF-4D6C-90D5-AB80D7881219}.Release|Any CPU.Build.0 = Release|Any CPU
{D589AC83-484E-4DDC-98F9-B7E75E10C2C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D589AC83-484E-4DDC-98F9-B7E75E10C2C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D589AC83-484E-4DDC-98F9-B7E75E10C2C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D589AC83-484E-4DDC-98F9-B7E75E10C2C0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
2 changes: 1 addition & 1 deletion src/Testing/Sandbox/EFModel1.efmodel
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<modelRoot xmlns:dm0="http://schemas.microsoft.com/VisualStudio/2008/DslTools/Core" dslVersion="1.2.6.4" Id="cba47279-0950-4257-bb2c-e4d5e72a52a4" entityContainerName="EFModel1" namespace="Sandbox" automaticMigrationsEnabled="false" entityFrameworkVersion="EFCore" transformOnSave="false" showCascadeDeletes="false" databaseType="None" xmlns="http://schemas.microsoft.com/dsltools/EFModel">
<modelRoot xmlns:dm0="http://schemas.microsoft.com/VisualStudio/2008/DslTools/Core" dslVersion="1.2.6.7" Id="cba47279-0950-4257-bb2c-e4d5e72a52a4" entityContainerName="EFModel1" namespace="Sandbox" automaticMigrationsEnabled="false" entityFrameworkVersion="EFCore" transformOnSave="false" showCascadeDeletes="false" databaseType="None" xmlns="http://schemas.microsoft.com/dsltools/EFModel">
<comments>
<comment Id="d1cf1519-da91-441b-8846-2ee160d45898" text="B">
<subjects>
Expand Down
Loading

0 comments on commit 055afc4

Please sign in to comment.