diff --git a/README.md b/README.md index 33e839a..bf4e2ea 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,12 @@ If you're interested in helping out, **please** drop a note at the GitHub projec More hands make the work lighter, and I know there are some really bright people amongst the userbase who are much better at writing documentation than I am. ### Change Log +4.2.8 + - Better handling of N-N relations in assembly parser + - Better detection of principal/dependent roles in assembly parser + - Removed "dbo" as default schema in design surface properties. Not specifying will allow the database to use whatever default schema is appropriate for it. + - Corrected some deficits in how column name overrides were being handled in EFCore + [4.2.7](https://github.com/msawczyn/EFDesigner2022/releases/download/v4.2.7/Sawczyn.EFDesigner.EFModel.DslPackage.vsix) - Support for per-entity inheritance in EF7 - Added option to generate nullable indicators diff --git a/VSMarketplace blurb.md b/VSMarketplace blurb.md index c9bd85a..a71cf2f 100644 --- a/VSMarketplace blurb.md +++ b/VSMarketplace blurb.md @@ -59,6 +59,12 @@ More hands make the work lighter, and I know there are some really bright people **ChangeLog** +**4.2.8** + - Better handling of N-N relations in assembly parser + - Better detection of principal/dependent roles in assembly parser + - Removed "dbo" as default schema in design surface properties. Not specifying will allow the database to use whatever default schema is appropriate for it. + - Corrected some deficits in how column name overrides were being handled in EFCore + **4.2.7** - **[NEW]** Support for per-entity inheritance in EF7 - **[NEW]** Added option to generate nullable indicators diff --git a/changelog.txt b/changelog.txt index 25f9860..103fffd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,7 @@ - Better handling of N-N relations in assembly parser - Better detection of principal/dependent roles in assembly parser - Removed "dbo" as default schema in design surface properties. Not specifying will allow the database to use whatever default schema is appropriate for it. + - Corrected some deficits in how column name overrides were being handled in EFCore 4.2.7 - Support for per-entity inheritance in EF7 diff --git a/dist/Sawczyn.EFDesigner.EFModel.DslPackage.vsix b/dist/Sawczyn.EFDesigner.EFModel.DslPackage.vsix index f62c50c..9a7d136 100644 --- a/dist/Sawczyn.EFDesigner.EFModel.DslPackage.vsix +++ b/dist/Sawczyn.EFDesigner.EFModel.DslPackage.vsix @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:565d57bf21c07aff72bdc00fa4c326c19fb014e5f25c383d68a6bda4e91f9e06 -size 300436903 +oid sha256:436cb8b903b2503ea3b9f4d5d7fb75fa189c332789a156e56cf60a6750c80bc0 +size 300409245 diff --git a/src/Dsl/CustomCode/Partials/Association.cs b/src/Dsl/CustomCode/Partials/Association.cs index e5eb600..823fcae 100644 --- a/src/Dsl/CustomCode/Partials/Association.cs +++ b/src/Dsl/CustomCode/Partials/Association.cs @@ -541,7 +541,12 @@ internal IEnumerable GetFKAutoIdentityErrors() /// public string[] GetForeignKeyPropertyNames() { - return FKPropertyName?.Split(',').Select(n => n.Trim()).ToArray() ?? Array.Empty(); + if (!string.IsNullOrEmpty(FKPropertyName)) + return FKPropertyName?.Split(',').Select(n => n.Trim()).ToArray() ?? Array.Empty(); + + string[] fkProperties = Source.ModelRoot.Store.GetAll().Where(a => a.IsForeignKeyFor == Id).Select(a => a.Name).ToArray(); + + return fkProperties; } private string GetNameValue() diff --git a/src/Dsl/CustomCode/Type Descriptors/AssociationTypeDescriptor.cs b/src/Dsl/CustomCode/Type Descriptors/AssociationTypeDescriptor.cs index 1a43709..fe1b398 100644 --- a/src/Dsl/CustomCode/Type Descriptors/AssociationTypeDescriptor.cs +++ b/src/Dsl/CustomCode/Type Descriptors/AssociationTypeDescriptor.cs @@ -64,19 +64,13 @@ private PropertyDescriptorCollection GetCustomProperties(Attribute[] attributes) propertyDescriptors.Remove("IsJSON"); } - // don't display roles for N..N, 1..N or 0-1..N associations + // don't display roles for X..N associations if (association.SourceMultiplicity == Multiplicity.ZeroMany || association.TargetMultiplicity == Multiplicity.ZeroMany) { propertyDescriptors.Remove("SourceRole"); propertyDescriptors.Remove("TargetRole"); } - if (association.SourceMultiplicity != Multiplicity.ZeroMany || association.TargetMultiplicity != Multiplicity.ZeroMany) - { - propertyDescriptors.Remove("SourceRole"); - propertyDescriptors.Remove("TargetRole"); - } - // only display delete behavior on the principal end // except that owned types don't have deletion behavior choices if (association.SourceRole != EndpointRole.Principal || association.Source.IsDependentType || association.Target.IsDependentType) @@ -85,8 +79,19 @@ private PropertyDescriptorCollection GetCustomProperties(Attribute[] attributes) if (association.TargetRole != EndpointRole.Principal || association.Source.IsDependentType || association.Target.IsDependentType) propertyDescriptors.Remove("TargetDeleteAction"); - // only show join table details if is *..* association and no association class - if ((!isManyToMany || !modelRoot.IsEFCore5Plus) && association.GetAssociationClass() != null) + if (association.Source.IsDependentType || association.Target.IsDependentType) + { + propertyDescriptors.Remove("SourceFKColumnName"); + propertyDescriptors.Remove("TargetFKColumnName"); + } + + if (!isManyToMany/* || association.GetAssociationClass() != null*/) + { + propertyDescriptors.Remove("JoinTableName"); + propertyDescriptors.Remove("SourceFKColumnName"); + } + + if (!modelRoot.IsEFCore5Plus) { propertyDescriptors.Remove("JoinTableName"); propertyDescriptors.Remove("SourceFKColumnName"); diff --git a/src/Dsl/CustomCode/Utilities/Import/AssemblyProcessor.cs b/src/Dsl/CustomCode/Utilities/Import/AssemblyProcessor.cs index d978235..6e8e220 100644 --- a/src/Dsl/CustomCode/Utilities/Import/AssemblyProcessor.cs +++ b/src/Dsl/CustomCode/Utilities/Import/AssemblyProcessor.cs @@ -496,7 +496,7 @@ private void ProcessUnidirectionalAssociations(ParsingModels.ModelClass modelCla if (string.IsNullOrWhiteSpace(existing.FKPropertyName) && !string.IsNullOrWhiteSpace(data.ForeignKey)) { existing.FKPropertyName = data.ForeignKey; - existing.FKColumnName = data.ForeignKeyColumnName; + existing.TargetFKColumnName = data.ForeignKeyColumnName; existing.Source.ModelRoot.ExposeForeignKeys = true; } @@ -516,7 +516,7 @@ private void ProcessUnidirectionalAssociations(ParsingModels.ModelClass modelCla elementLink.TargetSummary = data.TargetSummary; elementLink.TargetDescription = data.TargetDescription; elementLink.FKPropertyName = data.ForeignKey; - elementLink.FKColumnName = data.ForeignKeyColumnName; + elementLink.TargetFKColumnName = data.ForeignKeyColumnName; elementLink.SourceRole = ConvertRole(data.SourceRole); elementLink.TargetRole = ConvertRole(data.TargetRole); @@ -576,7 +576,7 @@ private void ProcessBidirectionalAssociations(ParsingModels.ModelClass modelClas if (string.IsNullOrWhiteSpace(existing.FKPropertyName) && !string.IsNullOrWhiteSpace(data.ForeignKey)) { existing.FKPropertyName = string.Join(",", data.ForeignKey.Split(',').ToList().Select(p => p.Split('/').Last().Split(' ').Last())); - existing.FKColumnName = data.ForeignKeyColumnName; + existing.TargetFKColumnName = data.ForeignKeyColumnName; existing.Source.ModelRoot.ExposeForeignKeys = true; } @@ -596,7 +596,7 @@ private void ProcessBidirectionalAssociations(ParsingModels.ModelClass modelClas elementLink.TargetSummary = data.TargetSummary; elementLink.TargetDescription = data.TargetDescription; elementLink.FKPropertyName = data.ForeignKey; - elementLink.FKColumnName = data.ForeignKeyColumnName; + elementLink.TargetFKColumnName = data.ForeignKeyColumnName; elementLink.SourceRole = ConvertRole(data.SourceRole); elementLink.TargetRole = ConvertRole(data.TargetRole); elementLink.SourcePropertyName = data.SourcePropertyName; diff --git a/src/Dsl/DslDefinition.dsl b/src/Dsl/DslDefinition.dsl index 49abb2d..f186c01 100644 --- a/src/Dsl/DslDefinition.dsl +++ b/src/Dsl/DslDefinition.dsl @@ -1420,7 +1420,7 @@ - + @@ -1571,7 +1571,7 @@ - + diff --git a/src/Dsl/DslDefinition.dsl.diagram b/src/Dsl/DslDefinition.dsl.diagram index c0e39e9..a033c34 100644 --- a/src/Dsl/DslDefinition.dsl.diagram +++ b/src/Dsl/DslDefinition.dsl.diagram @@ -4,18 +4,18 @@ - + - + - + - + @@ -32,18 +32,18 @@ - + - + - + - + @@ -95,28 +95,28 @@ - + - + - + - + - + - + - + @@ -125,16 +125,16 @@ - + - + - + - + @@ -164,40 +164,40 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -233,31 +233,31 @@ - + - + - + - + - + - + - + - + - + @@ -333,20 +333,20 @@ - + - + - + @@ -374,27 +374,27 @@ - + - + - + - + @@ -412,13 +412,13 @@ - + - + @@ -478,14 +478,14 @@ - + - + @@ -499,14 +499,14 @@ - + - + @@ -519,13 +519,13 @@ - + - + @@ -565,7 +565,7 @@ - + @@ -585,7 +585,7 @@ - + diff --git a/src/Dsl/GeneratedCode/DomainModel.cs b/src/Dsl/GeneratedCode/DomainModel.cs index b7871db..65cfbb7 100644 --- a/src/Dsl/GeneratedCode/DomainModel.cs +++ b/src/Dsl/GeneratedCode/DomainModel.cs @@ -306,7 +306,7 @@ protected sealed override DomainMemberInfo[] GetGeneratedDomainProperties() new DomainMemberInfo(typeof(Association), "IsTargetAbstract", Association.IsTargetAbstractDomainPropertyId, typeof(Association.IsTargetAbstractPropertyHandler)), new DomainMemberInfo(typeof(Association), "TargetAutoInclude", Association.TargetAutoIncludeDomainPropertyId, typeof(Association.TargetAutoIncludePropertyHandler)), new DomainMemberInfo(typeof(Association), "IsJSON", Association.IsJSONDomainPropertyId, typeof(Association.IsJSONPropertyHandler)), - new DomainMemberInfo(typeof(Association), "FKColumnName", Association.FKColumnNameDomainPropertyId, typeof(Association.FKColumnNamePropertyHandler)), + new DomainMemberInfo(typeof(Association), "TargetFKColumnName", Association.TargetFKColumnNameDomainPropertyId, typeof(Association.TargetFKColumnNamePropertyHandler)), new DomainMemberInfo(typeof(Generalization), "Name", Generalization.NameDomainPropertyId, typeof(Generalization.NamePropertyHandler)), new DomainMemberInfo(typeof(BidirectionalAssociation), "SourcePropertyName", BidirectionalAssociation.SourcePropertyNameDomainPropertyId, typeof(BidirectionalAssociation.SourcePropertyNamePropertyHandler)), new DomainMemberInfo(typeof(BidirectionalAssociation), "SourceDescription", BidirectionalAssociation.SourceDescriptionDomainPropertyId, typeof(BidirectionalAssociation.SourceDescriptionPropertyHandler)), diff --git a/src/Dsl/GeneratedCode/DomainModelResx.resx b/src/Dsl/GeneratedCode/DomainModelResx.resx index 42178e6..b77ff89 100644 --- a/src/Dsl/GeneratedCode/DomainModelResx.resx +++ b/src/Dsl/GeneratedCode/DomainModelResx.resx @@ -2333,17 +2333,17 @@ Database Category for DomainProperty 'IsJSON' on DomainClass 'Association' - + Optional name of column holding foreign key value for this end of the association - Description for DomainProperty 'FKColumnName' on DomainClass 'Association' + Description for DomainProperty 'TargetFKColumnName' on DomainClass 'Association' - - Foreign Key Column Name - DisplayName for DomainProperty 'FKColumnName' on DomainClass 'Association' + + End2 Foreign Key Column Name + DisplayName for DomainProperty 'TargetFKColumnName' on DomainClass 'Association' - - Database - Category for DomainProperty 'FKColumnName' on DomainClass 'Association' + + End 2 + Category for DomainProperty 'TargetFKColumnName' on DomainClass 'Association' @@ -2578,7 +2578,7 @@ Description for DomainProperty 'SourceImplementNotify' on DomainClass 'BidirectionalAssociation' - Implement INotifyPropertyChanged + End1 Implement INotifyPropertyChanged DisplayName for DomainProperty 'SourceImplementNotify' on DomainClass 'BidirectionalAssociation' @@ -2682,7 +2682,7 @@ DisplayName for DomainProperty 'SourceFKColumnName' on DomainClass 'BidirectionalAssociation' - Database + End 1 Category for DomainProperty 'SourceFKColumnName' on DomainClass 'BidirectionalAssociation' diff --git a/src/Dsl/GeneratedCode/DomainRelationships.cs b/src/Dsl/GeneratedCode/DomainRelationships.cs index 5512075..918e461 100644 --- a/src/Dsl/GeneratedCode/DomainRelationships.cs +++ b/src/Dsl/GeneratedCode/DomainRelationships.cs @@ -2796,61 +2796,61 @@ public override sealed void SetValue(Association element, global::System.Boolean } #endregion - #region FKColumnName domain property code + #region TargetFKColumnName domain property code /// - /// FKColumnName domain property Id. + /// TargetFKColumnName domain property Id. /// - public static readonly global::System.Guid FKColumnNameDomainPropertyId = new global::System.Guid(0x33c1f80e, 0x1661, 0x4666, 0x91, 0x12, 0x91, 0x84, 0x63, 0xab, 0xc4, 0xec); + public static readonly global::System.Guid TargetFKColumnNameDomainPropertyId = new global::System.Guid(0x33c1f80e, 0x1661, 0x4666, 0x91, 0x12, 0x91, 0x84, 0x63, 0xab, 0xc4, 0xec); /// - /// Storage for FKColumnName + /// Storage for TargetFKColumnName /// - private global::System.String fKColumnNamePropertyStorage = string.Empty; + private global::System.String targetFKColumnNamePropertyStorage = string.Empty; /// - /// Gets or sets the value of FKColumnName domain property. + /// Gets or sets the value of TargetFKColumnName domain property. /// Optional name of column holding foreign key value for this end of the /// association /// - [DslDesign::DisplayNameResource("Sawczyn.EFDesigner.EFModel.Association/FKColumnName.DisplayName", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] - [DslDesign::CategoryResource("Sawczyn.EFDesigner.EFModel.Association/FKColumnName.Category", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] - [DslDesign::DescriptionResource("Sawczyn.EFDesigner.EFModel.Association/FKColumnName.Description", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] + [DslDesign::DisplayNameResource("Sawczyn.EFDesigner.EFModel.Association/TargetFKColumnName.DisplayName", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] + [DslDesign::CategoryResource("Sawczyn.EFDesigner.EFModel.Association/TargetFKColumnName.Category", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] + [DslDesign::DescriptionResource("Sawczyn.EFDesigner.EFModel.Association/TargetFKColumnName.Description", typeof(global::Sawczyn.EFDesigner.EFModel.EFModelDomainModel), "Sawczyn.EFDesigner.EFModel.GeneratedCode.DomainModelResx")] [DslModeling::DomainObjectId("33c1f80e-1661-4666-9112-918463abc4ec")] - public global::System.String FKColumnName + public global::System.String TargetFKColumnName { [global::System.Diagnostics.DebuggerStepThrough] get { - return fKColumnNamePropertyStorage; + return targetFKColumnNamePropertyStorage; } [global::System.Diagnostics.DebuggerStepThrough] set { - FKColumnNamePropertyHandler.Instance.SetValue(this, value); + TargetFKColumnNamePropertyHandler.Instance.SetValue(this, value); } } /// - /// Value handler for the Association.FKColumnName domain property. + /// Value handler for the Association.TargetFKColumnName domain property. /// - internal sealed partial class FKColumnNamePropertyHandler : DslModeling::DomainPropertyValueHandler + internal sealed partial class TargetFKColumnNamePropertyHandler : DslModeling::DomainPropertyValueHandler { - private FKColumnNamePropertyHandler() { } + private TargetFKColumnNamePropertyHandler() { } /// - /// Gets the singleton instance of the Association.FKColumnName domain property value handler. + /// Gets the singleton instance of the Association.TargetFKColumnName domain property value handler. /// - public static readonly FKColumnNamePropertyHandler Instance = new FKColumnNamePropertyHandler(); + public static readonly TargetFKColumnNamePropertyHandler Instance = new TargetFKColumnNamePropertyHandler(); /// - /// Gets the Id of the Association.FKColumnName domain property. + /// Gets the Id of the Association.TargetFKColumnName domain property. /// public sealed override global::System.Guid DomainPropertyId { [global::System.Diagnostics.DebuggerStepThrough] get { - return FKColumnNameDomainPropertyId; + return TargetFKColumnNameDomainPropertyId; } } @@ -2862,7 +2862,7 @@ private FKColumnNamePropertyHandler() { } public override sealed global::System.String GetValue(Association element) { if (element == null) throw new global::System.ArgumentNullException("element"); - return element.fKColumnNamePropertyStorage; + return element.targetFKColumnNamePropertyStorage; } /// @@ -2878,7 +2878,7 @@ public override sealed void SetValue(Association element, global::System.String if (newValue != oldValue) { ValueChanging(element, oldValue, newValue); - element.fKColumnNamePropertyStorage = newValue; + element.targetFKColumnNamePropertyStorage = newValue; ValueChanged(element, oldValue, newValue); } } diff --git a/src/Dsl/GeneratedCode/EFModelSchema.xsd b/src/Dsl/GeneratedCode/EFModelSchema.xsd index 54d32ba..9dd6eb4 100644 --- a/src/Dsl/GeneratedCode/EFModelSchema.xsd +++ b/src/Dsl/GeneratedCode/EFModelSchema.xsd @@ -1509,8 +1509,8 @@ If true, the aggregate will be stored as JSON in the database - - + + Optional name of column holding foreign key value for this end of the association diff --git a/src/Dsl/GeneratedCode/LanguageNameSchema.xsd b/src/Dsl/GeneratedCode/LanguageNameSchema.xsd index 54d32ba..9dd6eb4 100644 --- a/src/Dsl/GeneratedCode/LanguageNameSchema.xsd +++ b/src/Dsl/GeneratedCode/LanguageNameSchema.xsd @@ -1509,8 +1509,8 @@ If true, the aggregate will be stored as JSON in the database - - + + Optional name of column holding foreign key value for this end of the association diff --git a/src/Dsl/GeneratedCode/Serializer.cs b/src/Dsl/GeneratedCode/Serializer.cs index b1c27e3..c8165a9 100644 --- a/src/Dsl/GeneratedCode/Serializer.cs +++ b/src/Dsl/GeneratedCode/Serializer.cs @@ -10842,20 +10842,20 @@ protected override void ReadPropertiesFromAttributes(DslModeling::SerializationC } } } - // FKColumnName + // TargetFKColumnName if (!serializationContext.Result.Failed) { - string attribFKColumnName = EFModelSerializationHelper.Instance.ReadAttribute(serializationContext, element, reader, "fKColumnName"); - if (attribFKColumnName != null) + string attribTargetFKColumnName = EFModelSerializationHelper.Instance.ReadAttribute(serializationContext, element, reader, "targetFKColumnName"); + if (attribTargetFKColumnName != null) { - global::System.String valueOfFKColumnName; - if (DslModeling::SerializationUtilities.TryGetValue(serializationContext, attribFKColumnName, out valueOfFKColumnName)) + global::System.String valueOfTargetFKColumnName; + if (DslModeling::SerializationUtilities.TryGetValue(serializationContext, attribTargetFKColumnName, out valueOfTargetFKColumnName)) { - instanceOfAssociation.FKColumnName = valueOfFKColumnName; + instanceOfAssociation.TargetFKColumnName = valueOfTargetFKColumnName; } else { // Invalid property value, ignored. - EFModelSerializationBehaviorSerializationMessages.IgnoredPropertyValue(serializationContext, reader, "fKColumnName", typeof(global::System.String), attribFKColumnName); + EFModelSerializationBehaviorSerializationMessages.IgnoredPropertyValue(serializationContext, reader, "targetFKColumnName", typeof(global::System.String), attribTargetFKColumnName); } } } @@ -11498,14 +11498,14 @@ protected override void WritePropertiesAsAttributes(DslModeling::SerializationCo } } } - // FKColumnName + // TargetFKColumnName if (!serializationContext.Result.Failed) { - global::System.String propValue = instanceOfAssociation.FKColumnName; + global::System.String propValue = instanceOfAssociation.TargetFKColumnName; if (!serializationContext.Result.Failed) { if (!string.IsNullOrEmpty(propValue)) - EFModelSerializationHelper.Instance.WriteAttributeString(serializationContext, element, writer, "fKColumnName", propValue); + EFModelSerializationHelper.Instance.WriteAttributeString(serializationContext, element, writer, "targetFKColumnName", propValue); } } diff --git a/src/DslPackage/DslPackage.csproj b/src/DslPackage/DslPackage.csproj index 239ae5a..4b96803 100644 --- a/src/DslPackage/DslPackage.csproj +++ b/src/DslPackage/DslPackage.csproj @@ -283,18 +283,6 @@ true - - - - - - - - - - - - Always @@ -387,7 +375,6 @@ true - diff --git a/src/DslPackage/ProjectItemTemplates/EFModel.xsd b/src/DslPackage/ProjectItemTemplates/EFModel.xsd index 54d32ba..9dd6eb4 100644 --- a/src/DslPackage/ProjectItemTemplates/EFModel.xsd +++ b/src/DslPackage/ProjectItemTemplates/EFModel.xsd @@ -1509,8 +1509,8 @@ If true, the aggregate will be stored as JSON in the database - - + + Optional name of column holding foreign key value for this end of the association diff --git a/src/DslPackage/TextTemplates/EFCore5ModelGenerator.ttinclude b/src/DslPackage/TextTemplates/EFCore5ModelGenerator.ttinclude index 4f3ecdd..ae55e4a 100644 --- a/src/DslPackage/TextTemplates/EFCore5ModelGenerator.ttinclude +++ b/src/DslPackage/TextTemplates/EFCore5ModelGenerator.ttinclude @@ -552,10 +552,10 @@ case Sawczyn.EFDesigner.EFModel.Multiplicity.One: case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"WithOne(p => p.{association.SourcePropertyName})"); - string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); + string[] fkNames = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); - if (!string.IsNullOrEmpty(foreignKeySegment)) - segments.Add(foreignKeySegment); + if (fkNames.Length > 0) + segments.Add($"HasForeignKey({string.Join(",",fkNames)})"); WriteSourceDeleteBehavior(association, segments); @@ -598,6 +598,28 @@ Output(segments); } + + if (association.Dependent == association.Target && !string.IsNullOrEmpty(association.TargetFKColumnName)) + { + string fk = association.GetForeignKeyPropertyNames().FirstOrDefault(); + + if (string.IsNullOrEmpty(fk)) + fk = association.TargetFKColumnName; + + string requiredIndicator = association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One ? "?" : string.Empty; + Output($"modelBuilder.Entity<{association.Target.FullName}>().Property<{association.Source.AllIdentityAttributes.First().CLRType}{requiredIndicator}>(\"{fk}\").HasColumnName(\"{association.TargetFKColumnName}\");"); + } + + if (association.Dependent == association.Source && !string.IsNullOrEmpty(association.SourceFKColumnName)) + { + string fk = association.GetForeignKeyPropertyNames().FirstOrDefault(); + if (string.IsNullOrEmpty(fk)) + fk = association.SourceFKColumnName; + + string requiredIndicator = association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One ? "?" : string.Empty; + Output($"modelBuilder.Entity<{association.Source.FullName}>().Property<{association.Target.AllIdentityAttributes.First().CLRType}{requiredIndicator}>(\"{fk}\").HasColumnName(\"{association.SourceFKColumnName}\");"); + } + } } @@ -659,9 +681,17 @@ if (association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany && association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { - string targetFKs = string.Join(", ", association.Target.IdentityAttributes.Select(a => $"\"{association.Target.Name}_{a.Name}\"")); - string sourceFKs = string.Join(", ", association.Source.IdentityAttributes.Select(a => $"\"{association.Source.Name}_{a.Name}\"")); - string joinTable = string.IsNullOrEmpty(association.JoinTableName) ? $"{association.Target.Name}_x_{association.Source.Name}" : association.JoinTableName; + string targetFKs = string.IsNullOrEmpty(association.TargetFKColumnName) + ? string.Join(", ", association.Target.IdentityAttributes.Select(a => $"\"{association.Target.Name}_{a.Name}\"")) + : "\"" + association.TargetFKColumnName + "\""; + + string sourceFKs = string.IsNullOrEmpty(association.SourceFKColumnName) + ? string.Join(", ", association.Source.IdentityAttributes.Select(a => $"\"{association.Source.Name}_{a.Name}\"")) + : "\"" + association.SourceFKColumnName + "\""; + + string joinTable = string.IsNullOrEmpty(association.JoinTableName) + ? $"{association.Target.Name}_x_{association.Source.Name}" + : association.JoinTableName; string segment = "UsingEntity>(" @@ -675,10 +705,9 @@ { segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); - string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); - - if (!string.IsNullOrEmpty(foreignKeySegment)) - segments.Add(foreignKeySegment); + string[] foreignKeys = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); + if (foreignKeys.Length > 0) + segments.Add($"HasForeignKey({string.Join(", ", foreignKeys)})"); WriteSourceDeleteBehavior(association, segments); WriteTargetDeleteBehavior(association, segments); @@ -806,109 +835,7 @@ return segment; } - - /// - /// Writes unidirectional non-dependent associations - /// - /// The model class - /// The list of already-visited association - /// The list of foreign key columns - protected override void WriteUnidirectionalNonDependentAssociations(ModelClass modelClass, List visited, List foreignKeyColumns) - { - // ReSharper disable once LoopCanBePartlyConvertedToQuery - foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) - .OfType() - .Where(x => x.Persistent && x.Target.Persistent)) - { - if (visited.Contains(association)) - continue; - - visited.Add(association); - - List segments = new List(); - bool required = false; - - segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); - - switch (association.TargetMultiplicity) // realized by property on source - { - 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: - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: - segments.Add($"HasOne<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); - - break; - } - - switch (association.SourceMultiplicity) // realized by property on target, but no property on target - { - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: - segments.Add("WithMany()"); - required = association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One; - - if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) - { - string tableMap = string.IsNullOrEmpty(association.JoinTableName) - ? $"{association.Target.Name}_x_{association.Source.Name}_{association.TargetPropertyName}" - : association.JoinTableName; - - segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); - } - - break; - - case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: - segments.Add("WithOne()"); - - 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)"); - } - 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 (required) - segments.Add("IsRequired()"); - - Output(segments); - - if (association.TargetAutoInclude) - Output($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName}).AutoInclude();"); - - if (!association.TargetAutoProperty) - { - segments.Add($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName})"); - - segments.Add(modelClass.ModelRoot.IsEFCore6Plus - ? $"UsePropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})" - : $"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})"); - - Output(segments); - } - } - } } + #> diff --git a/src/DslPackage/TextTemplates/EFCoreModelGenerator.ttinclude b/src/DslPackage/TextTemplates/EFCoreModelGenerator.ttinclude index eedc116..4174ef3 100644 --- a/src/DslPackage/TextTemplates/EFCoreModelGenerator.ttinclude +++ b/src/DslPackage/TextTemplates/EFCoreModelGenerator.ttinclude @@ -239,48 +239,6 @@ WriteUnidirectionalDependentAssociations(modelClass, $"modelBuilder.Entity<{modelClass.FullName}>()", visited); } - /// - /// Creates a string representing a foreign key configuration for the given association and foreign key column(s). - /// - /// The association for which the foreign key segment should be created. - /// The foreign key columns to be included in the segment. - /// A string representing the foreign key segment of an SQL statement. - [SuppressMessage("ReSharper", "RedundantNameQualifier")] - protected virtual string CreateForeignKeySegment(Association association, List foreignKeyColumns) - { - List foreignKeys = GetForeignKeys(association, foreignKeyColumns).ToList(); - - if (!foreignKeys.Any()) // only happens if many-to-many - return null; - - // 1-1, 1-0/1 and 0/1-0/1 - bool dependentClassDesignationRequired = (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) - && (association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany); - - string result = string.Join(",", foreignKeys); - - if (foreignKeys.First().StartsWith("\"")) - { - // foreign keys are shadow properties - result = dependentClassDesignationRequired - ? $"HasForeignKey(\"{association.Dependent.Name}\", {result})" - : $"HasForeignKey({result})"; - } - else - { - // foreign keys are real properties - result = foreignKeys.Count == 1 - ? $"k => {result}" - : $"k => new {{ {result} }}"; - - result = dependentClassDesignationRequired - ? $"HasForeignKey<{association.Dependent.FullName}>({result})" - : $"HasForeignKey({result})"; - } - - return result; - } - /// /// Creates the code segments of a model attribute. /// @@ -399,7 +357,7 @@ IEnumerable result = new List(); // foreign key definitions always go in the table representing the Dependent end of the association - // if there is no dependent end (i.e., many-to-many), there are no foreign keys + // if there is no dependent end (i.e., many-to-many), we won't return foreign keys ModelClass principal = association.Principal; ModelClass dependent = association.Dependent; @@ -410,7 +368,8 @@ else { // defined properties - result = association.FKPropertyName.Split(',').Select(prop => "k." + prop.Trim()).ToList(); + //result = association.FKPropertyName.Split(',').Select(prop => "k." + prop.Trim()).ToList(); + result = association.FKPropertyName.Split(',').Select(prop => "\"" + prop.Trim() + "\"").ToList(); foreignKeyColumns.AddRange(result); } } @@ -592,6 +551,8 @@ break; } + string[] foreignKeys; + switch (association.SourceMultiplicity) // realized by property on target, but no property on target { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: @@ -605,11 +566,22 @@ segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); } + else + { + foreignKeys = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); + if (foreignKeys.Length > 0) + segments.Add($"HasForeignKey({string.Join(", ", foreignKeys)})"); + } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: segments.Add($"WithOne(p => p.{association.SourcePropertyName})"); + + foreignKeys = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); + if (foreignKeys.Length > 0) + segments.Add($"HasForeignKey({string.Join(", ", foreignKeys)})"); + required = modelClass == association.Principal; break; @@ -617,14 +589,13 @@ case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"WithOne(p => p.{association.SourcePropertyName})"); + foreignKeys = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); + if (foreignKeys.Length > 0) + segments.Add($"HasForeignKey({string.Join(", ", foreignKeys)})"); + break; } - string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); - - if (!string.IsNullOrEmpty(foreignKeySegment)) - segments.Add(foreignKeySegment); - WriteSourceDeleteBehavior(association, segments); if (required @@ -1138,7 +1109,7 @@ } } - /// + /// /// Writes unidirectional non-dependent associations /// /// The model class @@ -1149,7 +1120,7 @@ // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) .OfType() - .Where(x => x.Persistent && !x.Target.IsDependentType)) + .Where(x => x.Persistent && x.Target.Persistent)) { if (visited.Contains(association)) continue; @@ -1165,25 +1136,24 @@ { 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})"); - required = modelClass == association.Principal; - - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"HasOne<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); break; } + string[] foreignKeys; + switch (association.SourceMultiplicity) // realized by property on target, but no property on target { 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) { @@ -1193,37 +1163,79 @@ segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); } + else + { + foreignKeys = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); + if (foreignKeys.Length > 0) + { + if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One || association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne) + segments.Add($"HasForeignKey(\"{association.Dependent.Name}\", {string.Join(", ", foreignKeys)})"); + else + segments.Add($"HasForeignKey({string.Join(", ", foreignKeys)})"); + } + } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - segments.Add("WithOne()"); - required = modelClass == association.Principal; - - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add("WithOne()"); + foreignKeys = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); + if (foreignKeys.Length > 0) + { + if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One || association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne) + segments.Add($"HasForeignKey(\"{association.Dependent.Name}\", {string.Join(", ", foreignKeys)})"); + else + segments.Add($"HasForeignKey({string.Join(", ", foreignKeys)})"); + } break; } - string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); - - if (!string.IsNullOrEmpty(foreignKeySegment)) - segments.Add(foreignKeySegment); - else if (association.Is(Sawczyn.EFDesigner.EFModel.Multiplicity.One, Sawczyn.EFDesigner.EFModel.Multiplicity.One) - || association.Is(Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne, Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne)) - segments.Add($"HasForeignKey<{association.Dependent.FullName}>()"); - - WriteTargetDeleteBehavior(association, segments); + 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)"); + } + 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 (required - && ((association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One) - || (association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One))) + if (required) segments.Add("IsRequired()"); Output(segments); + + if (association.TargetAutoInclude) + Output($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName}).AutoInclude();"); + + if (!association.TargetAutoProperty) + { + segments.Add($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName})"); + + segments.Add(modelClass.ModelRoot.IsEFCore6Plus + ? $"UsePropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})" + : $"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})"); + + Output(segments); + } + + if (!string.IsNullOrEmpty(association.TargetFKColumnName)) + { + string fk = association.GetForeignKeyPropertyNames().FirstOrDefault(); + + if (string.IsNullOrEmpty(fk)) + fk = association.TargetFKColumnName; + + string requiredIndicator = association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One ? "?" : string.Empty; + Output($"modelBuilder.Entity<{association.Target.FullName}>().Property<{association.Source.AllIdentityAttributes.First().CLRType}{requiredIndicator}>(\"{fk}\").HasColumnName(\"{association.TargetFKColumnName}\");"); + } } } } diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFCore5ModelGenerator.cs b/src/DslPackage/TextTemplates/EditingOnly/EFCore5ModelGenerator.cs index 550aede..fbda9c8 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EFCore5ModelGenerator.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EFCore5ModelGenerator.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -542,10 +543,10 @@ protected override void WriteBidirectionalNonDependentAssociations(ModelClass mo case Sawczyn.EFDesigner.EFModel.Multiplicity.One: case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"WithOne(p => p.{association.SourcePropertyName})"); - string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); + string[] fkNames = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); - if (!string.IsNullOrEmpty(foreignKeySegment)) - segments.Add(foreignKeySegment); + if (fkNames.Length > 0) + segments.Add($"HasForeignKey({string.Join(",",fkNames)})"); WriteSourceDeleteBehavior(association, segments); @@ -588,6 +589,28 @@ protected override void WriteBidirectionalNonDependentAssociations(ModelClass mo Output(segments); } + + if (association.Dependent == association.Target && !string.IsNullOrEmpty(association.TargetFKColumnName)) + { + string fk = association.GetForeignKeyPropertyNames().FirstOrDefault(); + + if (string.IsNullOrEmpty(fk)) + fk = association.TargetFKColumnName; + + string requiredIndicator = association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One ? "?" : string.Empty; + Output($"modelBuilder.Entity<{association.Target.FullName}>().Property<{association.Source.AllIdentityAttributes.First().CLRType}{requiredIndicator}>(\"{fk}\").HasColumnName(\"{association.TargetFKColumnName}\");"); + } + + if (association.Dependent == association.Source && !string.IsNullOrEmpty(association.SourceFKColumnName)) + { + string fk = association.GetForeignKeyPropertyNames().FirstOrDefault(); + if (string.IsNullOrEmpty(fk)) + fk = association.SourceFKColumnName; + + string requiredIndicator = association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One ? "?" : string.Empty; + Output($"modelBuilder.Entity<{association.Source.FullName}>().Property<{association.Target.AllIdentityAttributes.First().CLRType}{requiredIndicator}>(\"{fk}\").HasColumnName(\"{association.SourceFKColumnName}\");"); + } + } } @@ -649,9 +672,17 @@ private IEnumerable WriteStandardBidirectionalAssociation(BidirectionalA if (association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany && association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { - string targetFKs = string.Join(", ", association.Target.IdentityAttributes.Select(a => $"\"{association.Target.Name}_{a.Name}\"")); - string sourceFKs = string.Join(", ", association.Source.IdentityAttributes.Select(a => $"\"{association.Source.Name}_{a.Name}\"")); - string joinTable = string.IsNullOrEmpty(association.JoinTableName) ? $"{association.Target.Name}_x_{association.Source.Name}" : association.JoinTableName; + string targetFKs = string.IsNullOrEmpty(association.TargetFKColumnName) + ? string.Join(", ", association.Target.IdentityAttributes.Select(a => $"\"{association.Target.Name}_{a.Name}\"")) + : "\"" + association.TargetFKColumnName + "\""; + + string sourceFKs = string.IsNullOrEmpty(association.SourceFKColumnName) + ? string.Join(", ", association.Source.IdentityAttributes.Select(a => $"\"{association.Source.Name}_{a.Name}\"")) + : "\"" + association.SourceFKColumnName + "\""; + + string joinTable = string.IsNullOrEmpty(association.JoinTableName) + ? $"{association.Target.Name}_x_{association.Source.Name}" + : association.JoinTableName; string segment = "UsingEntity>(" @@ -665,10 +696,9 @@ private IEnumerable WriteStandardBidirectionalAssociation(BidirectionalA { segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); - string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); - - if (!string.IsNullOrEmpty(foreignKeySegment)) - segments.Add(foreignKeySegment); + string[] foreignKeys = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); + if (foreignKeys.Length > 0) + segments.Add($"HasForeignKey({string.Join(", ", foreignKeys)})"); WriteSourceDeleteBehavior(association, segments); WriteTargetDeleteBehavior(association, segments); @@ -796,110 +826,8 @@ private string WriteAggregateTargetConfiguration(Association association, List - /// Writes unidirectional non-dependent associations - /// - /// The model class - /// The list of already-visited association - /// The list of foreign key columns - protected override void WriteUnidirectionalNonDependentAssociations(ModelClass modelClass, List visited, List foreignKeyColumns) - { - // ReSharper disable once LoopCanBePartlyConvertedToQuery - foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) - .OfType() - .Where(x => x.Persistent && x.Target.Persistent)) - { - if (visited.Contains(association)) - continue; - - visited.Add(association); - - List segments = new List(); - bool required = false; - - segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); - - switch (association.TargetMultiplicity) // realized by property on source - { - 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: - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: - segments.Add($"HasOne<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); - - break; - } - - switch (association.SourceMultiplicity) // realized by property on target, but no property on target - { - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: - segments.Add("WithMany()"); - required = association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One; - - if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) - { - string tableMap = string.IsNullOrEmpty(association.JoinTableName) - ? $"{association.Target.Name}_x_{association.Source.Name}_{association.TargetPropertyName}" - : association.JoinTableName; - - segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); - } - - break; - - case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: - segments.Add("WithOne()"); - - 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)"); - } - 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 (required) - segments.Add("IsRequired()"); - - Output(segments); - - if (association.TargetAutoInclude) - Output($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName}).AutoInclude();"); - - if (!association.TargetAutoProperty) - { - segments.Add($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName})"); - - segments.Add(modelClass.ModelRoot.IsEFCore6Plus - ? $"UsePropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})" - : $"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})"); - - Output(segments); - } - } - } } + #endregion Template } } \ No newline at end of file diff --git a/src/DslPackage/TextTemplates/EditingOnly/EFCoreModelGenerator.cs b/src/DslPackage/TextTemplates/EditingOnly/EFCoreModelGenerator.cs index 863c884..6123a03 100644 --- a/src/DslPackage/TextTemplates/EditingOnly/EFCoreModelGenerator.cs +++ b/src/DslPackage/TextTemplates/EditingOnly/EFCoreModelGenerator.cs @@ -233,48 +233,6 @@ protected virtual void ConfigureUnidirectionalAssociations(ModelClass modelClass WriteUnidirectionalDependentAssociations(modelClass, $"modelBuilder.Entity<{modelClass.FullName}>()", visited); } - /// - /// Creates a string representing a foreign key configuration for the given association and foreign key column(s). - /// - /// The association for which the foreign key segment should be created. - /// The foreign key columns to be included in the segment. - /// A string representing the foreign key segment of an SQL statement. - [SuppressMessage("ReSharper", "RedundantNameQualifier")] - protected virtual string CreateForeignKeySegment(Association association, List foreignKeyColumns) - { - List foreignKeys = GetForeignKeys(association, foreignKeyColumns).ToList(); - - if (!foreignKeys.Any()) // only happens if many-to-many - return null; - - // 1-1, 1-0/1 and 0/1-0/1 - bool dependentClassDesignationRequired = (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) - && (association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany); - - string result = string.Join(",", foreignKeys); - - if (foreignKeys.First().StartsWith("\"")) - { - // foreign keys are shadow properties - result = dependentClassDesignationRequired - ? $"HasForeignKey(\"{association.Dependent.Name}\", {result})" - : $"HasForeignKey({result})"; - } - else - { - // foreign keys are real properties - result = foreignKeys.Count == 1 - ? $"k => {result}" - : $"k => new {{ {result} }}"; - - result = dependentClassDesignationRequired - ? $"HasForeignKey<{association.Dependent.FullName}>({result})" - : $"HasForeignKey({result})"; - } - - return result; - } - /// /// Creates the code segments of a model attribute. /// @@ -393,7 +351,7 @@ protected virtual IEnumerable GetForeignKeys(Association association, Li IEnumerable result = new List(); // foreign key definitions always go in the table representing the Dependent end of the association - // if there is no dependent end (i.e., many-to-many), there are no foreign keys + // if there is no dependent end (i.e., many-to-many), we won't return foreign keys ModelClass principal = association.Principal; ModelClass dependent = association.Dependent; @@ -404,7 +362,8 @@ protected virtual IEnumerable GetForeignKeys(Association association, Li else { // defined properties - result = association.FKPropertyName.Split(',').Select(prop => "k." + prop.Trim()).ToList(); + //result = association.FKPropertyName.Split(',').Select(prop => "k." + prop.Trim()).ToList(); + result = association.FKPropertyName.Split(',').Select(prop => "\"" + prop.Trim() + "\"").ToList(); foreignKeyColumns.AddRange(result); } } @@ -586,6 +545,8 @@ protected virtual void WriteBidirectionalNonDependentAssociations(ModelClass mod break; } + string[] foreignKeys; + switch (association.SourceMultiplicity) // realized by property on target, but no property on target { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: @@ -599,11 +560,22 @@ protected virtual void WriteBidirectionalNonDependentAssociations(ModelClass mod segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); } + else + { + foreignKeys = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); + if (foreignKeys.Length > 0) + segments.Add($"HasForeignKey({string.Join(", ", foreignKeys)})"); + } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: segments.Add($"WithOne(p => p.{association.SourcePropertyName})"); + + foreignKeys = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); + if (foreignKeys.Length > 0) + segments.Add($"HasForeignKey({string.Join(", ", foreignKeys)})"); + required = modelClass == association.Principal; break; @@ -611,14 +583,13 @@ protected virtual void WriteBidirectionalNonDependentAssociations(ModelClass mod case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"WithOne(p => p.{association.SourcePropertyName})"); + foreignKeys = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); + if (foreignKeys.Length > 0) + segments.Add($"HasForeignKey({string.Join(", ", foreignKeys)})"); + break; } - string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); - - if (!string.IsNullOrEmpty(foreignKeySegment)) - segments.Add(foreignKeySegment); - WriteSourceDeleteBehavior(association, segments); if (required @@ -1132,7 +1103,7 @@ protected virtual void WriteUnidirectionalDependentAssociations(ModelClass sourc } } - /// + /// /// Writes unidirectional non-dependent associations /// /// The model class @@ -1143,7 +1114,7 @@ protected virtual void WriteUnidirectionalNonDependentAssociations(ModelClass mo // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) .OfType() - .Where(x => x.Persistent && !x.Target.IsDependentType)) + .Where(x => x.Persistent && x.Target.Persistent)) { if (visited.Contains(association)) continue; @@ -1159,25 +1130,24 @@ protected virtual void WriteUnidirectionalNonDependentAssociations(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})"); - required = modelClass == association.Principal; - - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"HasOne<{association.Target.FullName}>(p => p.{association.TargetPropertyName})"); break; } + string[] foreignKeys; + switch (association.SourceMultiplicity) // realized by property on target, but no property on target { 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) { @@ -1187,37 +1157,79 @@ protected virtual void WriteUnidirectionalNonDependentAssociations(ModelClass mo segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\"))"); } + else + { + foreignKeys = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); + if (foreignKeys.Length > 0) + { + if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One || association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne) + segments.Add($"HasForeignKey(\"{association.Dependent.Name}\", {string.Join(", ", foreignKeys)})"); + else + segments.Add($"HasForeignKey({string.Join(", ", foreignKeys)})"); + } + } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - segments.Add("WithOne()"); - required = modelClass == association.Principal; - - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add("WithOne()"); + foreignKeys = GetForeignKeys(association, foreignKeyColumns).Select(x => x.Trim('"')).Select(x => $"\"{x}\"").ToArray(); + if (foreignKeys.Length > 0) + { + if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One || association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne) + segments.Add($"HasForeignKey(\"{association.Dependent.Name}\", {string.Join(", ", foreignKeys)})"); + else + segments.Add($"HasForeignKey({string.Join(", ", foreignKeys)})"); + } break; } - string foreignKeySegment = CreateForeignKeySegment(association, foreignKeyColumns); - - if (!string.IsNullOrEmpty(foreignKeySegment)) - segments.Add(foreignKeySegment); - else if (association.Is(Sawczyn.EFDesigner.EFModel.Multiplicity.One, Sawczyn.EFDesigner.EFModel.Multiplicity.One) - || association.Is(Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne, Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne)) - segments.Add($"HasForeignKey<{association.Dependent.FullName}>()"); - - WriteTargetDeleteBehavior(association, segments); + 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)"); + } + 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 (required - && ((association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One) - || (association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One))) + if (required) segments.Add("IsRequired()"); Output(segments); + + if (association.TargetAutoInclude) + Output($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName}).AutoInclude();"); + + if (!association.TargetAutoProperty) + { + segments.Add($"modelBuilder.Entity<{association.Source.FullName}>().Navigation(e => e.{association.TargetPropertyName})"); + + segments.Add(modelClass.ModelRoot.IsEFCore6Plus + ? $"UsePropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})" + : $"Metadata.SetPropertyAccessMode(PropertyAccessMode.{association.TargetPropertyAccessMode})"); + + Output(segments); + } + + if (!string.IsNullOrEmpty(association.TargetFKColumnName)) + { + string fk = association.GetForeignKeyPropertyNames().FirstOrDefault(); + + if (string.IsNullOrEmpty(fk)) + fk = association.TargetFKColumnName; + + string requiredIndicator = association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One ? "?" : string.Empty; + Output($"modelBuilder.Entity<{association.Target.FullName}>().Property<{association.Source.AllIdentityAttributes.First().CLRType}{requiredIndicator}>(\"{fk}\").HasColumnName(\"{association.TargetFKColumnName}\");"); + } } } }