diff --git a/changelog.txt b/changelog.txt index a7e409d1e..af09e49c7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,7 @@ - Added a model fixup for when user doesn't use full enumeration name for a property's initial value in an entity - Fix: Removed stray quote marks in default values for string properties (See https://github.com/msawczyn/EFDesigner/issues/86) - Fix: Minimum string length was ignored when setting properties via text edit (See https://github.com/msawczyn/EFDesigner/issues/86) + - Fix: Required string identity property is not present in the constructor (See https://github.com/msawczyn/EFDesigner/issues/93) 1.3.0.4 - Fixed problematic code generation in constructors for classes having 1..1 associations (See https://github.com/msawczyn/EFDesigner/issues/74) diff --git a/src/Dsl/GeneratedCode/SerializationHelper.cs b/src/Dsl/GeneratedCode/SerializationHelper.cs index e7c39fc0e..7710c4514 100644 --- a/src/Dsl/GeneratedCode/SerializationHelper.cs +++ b/src/Dsl/GeneratedCode/SerializationHelper.cs @@ -506,7 +506,7 @@ public virtual ModelRoot LoadModel(DslModeling::SerializationResult serializatio ///// Partition in which the new ModelRoot instance will be created. ///// Name of the file from which the ModelRoot instance will be deserialized. ///// The root of the file that was loaded. - // private void OnPostLoadModel(DslModeling::SerializationResult serializationResult, DslModeling::Partition partition, string fileName, ModelRoot modelRoot ) + // private void OnPostLoadModel(DslModeling::SerializationResult serializationResult, DslModeling::Partition partition, string location, ModelRoot modelRoot ) this.OnPostLoadModel(serializationResult, partition, location, modelRoot); if (serializationResult.Failed) diff --git a/src/DslPackage/TextTemplates/EFDesigner.ttinclude b/src/DslPackage/TextTemplates/EFDesigner.ttinclude index 85ffeddb3..7e07816a1 100644 --- a/src/DslPackage/TextTemplates/EFDesigner.ttinclude +++ b/src/DslPackage/TextTemplates/EFDesigner.ttinclude @@ -165,7 +165,7 @@ List GetRequiredParameters(ModelClass modelClass, bool? haveDefaults) if (haveDefaults != true) { requiredParameters.AddRange(modelClass.AllRequiredAttributes - .Where(x => !x.IsIdentity && + .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) && !x.IsConcurrencyToken && x.SetterVisibility == SetterAccessModifier.Public && string.IsNullOrEmpty(x.InitialValue)) @@ -181,7 +181,7 @@ List GetRequiredParameters(ModelClass modelClass, bool? haveDefaults) if (haveDefaults != false) { requiredParameters.AddRange(modelClass.AllRequiredAttributes - .Where(x => !x.IsIdentity && + .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) && !x.IsConcurrencyToken && x.SetterVisibility == SetterAccessModifier.Public && !string.IsNullOrEmpty(x.InitialValue)) @@ -199,7 +199,7 @@ List GetRequiredParameters(ModelClass modelClass, bool? haveDefaults) List GetRequiredParameterNames(ModelClass modelClass) { List requiredParameterNames = modelClass.AllRequiredAttributes - .Where(x => !x.IsIdentity && !x.IsConcurrencyToken && x.SetterVisibility == SetterAccessModifier.Public && string.IsNullOrEmpty(x.InitialValue)) + .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) && && !x.IsConcurrencyToken && x.SetterVisibility == SetterAccessModifier.Public && string.IsNullOrEmpty(x.InitialValue)) .Select(x => x.Name.ToLower()) .ToList(); @@ -209,7 +209,7 @@ List GetRequiredParameterNames(ModelClass modelClass) .Select(x => x.PropertyName.ToLower())); requiredParameterNames.AddRange(modelClass.AllRequiredAttributes - .Where(x => !x.IsIdentity && !x.IsConcurrencyToken && x.SetterVisibility == SetterAccessModifier.Public && !string.IsNullOrEmpty(x.InitialValue)) + .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) && && !x.IsConcurrencyToken && x.SetterVisibility == SetterAccessModifier.Public && !string.IsNullOrEmpty(x.InitialValue)) .Select(x => x.Name.ToLower())); return requiredParameterNames; @@ -519,7 +519,7 @@ void WriteConstructor(ModelClass modelClass) } foreach (ModelAttribute requiredAttribute in modelClass.AllRequiredAttributes - .Where(x => !x.IsIdentity && + .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) && !x.IsConcurrencyToken && x.SetterVisibility == SetterAccessModifier.Public)) { @@ -626,7 +626,7 @@ bool AllSuperclassesAreNullOrAbstract(ModelClass modelClass) void WriteConstructorComments(ModelClass modelClass) { - foreach (ModelAttribute requiredAttribute in modelClass.AllRequiredAttributes.Where(x => !x.IsIdentity && + foreach (ModelAttribute requiredAttribute in modelClass.AllRequiredAttributes.Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) && !x.IsConcurrencyToken && x.SetterVisibility == SetterAccessModifier.Public)) Output($@"/// {requiredAttribute.Summary}"); diff --git a/src/Testing/Sandbox_EF6/EF6Designer.ttinclude b/src/Testing/Sandbox_EF6/EF6Designer.ttinclude deleted file mode 100644 index d4bda84c8..000000000 --- a/src/Testing/Sandbox_EF6/EF6Designer.ttinclude +++ /dev/null @@ -1,740 +0,0 @@ -<#@ assembly name="System.Core" -#><#@ assembly name="System.Data.Linq" -#><#@ assembly name="EnvDTE" -#><#@ assembly name="System.Xml" -#><#@ assembly name="System.Xml.Linq" -#><#@ import namespace="System.Collections.Generic" -#><#@ import namespace="System.IO" -#><#@ import namespace="System.Linq" #><#+ - -// EFDesigner v1.3.0.4 -// Copyright (c) 2017-2019 Michael Sawczyn -// https://github.com/msawczyn/EFDesigner - -void GenerateEF6(Manager manager, ModelRoot modelRoot) -{ - // Entities - - foreach (ModelClass modelClass in modelRoot.Classes) - { - string dir = modelClass.IsDependentType ? modelRoot.StructOutputDirectory : modelRoot.EntityOutputDirectory; - if (!string.IsNullOrEmpty(modelClass.OutputDirectory)) dir = modelClass.OutputDirectory; - manager.StartNewFile(Path.Combine(dir, $"{modelClass.Name}.{modelRoot.FileNameMarker}.cs")); - WriteClass(modelClass); - } - - // Enums - - foreach (ModelEnum modelEnum in modelRoot.Enums) - { - string dir = !string.IsNullOrEmpty(modelEnum.OutputDirectory) ? modelEnum.OutputDirectory : modelRoot.EnumOutputDirectory; - manager.StartNewFile(Path.Combine(dir, $"{modelEnum.Name}.{modelRoot.FileNameMarker}.cs")); - WriteEnum(modelEnum); - } - - // Context - - if (modelRoot.DatabaseInitializerType != DatabaseInitializerKind.None) - { - manager.StartNewFile(Path.Combine(modelRoot.ContextOutputDirectory, $"{modelRoot.EntityContainerName}DatabaseInitializer.{modelRoot.FileNameMarker}.cs")); - WriteDatabaseInitializerEF6(modelRoot); - } - - manager.StartNewFile(Path.Combine(modelRoot.ContextOutputDirectory, $"{modelRoot.EntityContainerName}DbMigrationConfiguration.{modelRoot.FileNameMarker}.cs")); - WriteMigrationConfigurationEF6(modelRoot); - - manager.StartNewFile(Path.Combine(modelRoot.ContextOutputDirectory, $"{modelRoot.EntityContainerName}.{modelRoot.FileNameMarker}.cs")); - WriteDbContextEF6(modelRoot); -} - -List GetAdditionalUsingStatementsEF6(ModelRoot modelRoot) -{ - List result = new List(); - List attributeTypes = modelRoot.Classes.SelectMany(c => c.Attributes).Select(a => a.Type).Distinct().ToList(); - - if (attributeTypes.Any(t => t.IndexOf("Geometry", StringComparison.Ordinal) > -1 || t.IndexOf("Geography", StringComparison.Ordinal) > -1)) - { - result.Add("using System.Data.Entity.Spatial;"); - } - - return result; -} - -void WriteDatabaseInitializerEF6(ModelRoot modelRoot) -{ - Output("using System.Data.Entity;"); - NL(); - - BeginNamespace(modelRoot.Namespace); - - Output("/// "); - if (modelRoot.DatabaseInitializerType == DatabaseInitializerKind.MigrateDatabaseToLatestVersion) - Output($"public partial class {modelRoot.EntityContainerName}DatabaseInitializer : MigrateDatabaseToLatestVersion<{modelRoot.EntityContainerName}, {modelRoot.EntityContainerName}DbMigrationConfiguration>"); - else - Output($"public partial class {modelRoot.EntityContainerName}DatabaseInitializer : {modelRoot.DatabaseInitializerType}<{modelRoot.EntityContainerName}>"); - - Output("{"); - Output("}"); - EndNamespace(modelRoot.Namespace); -} - -void WriteMigrationConfigurationEF6(ModelRoot modelRoot) -{ - Output("using System.Data.Entity.Migrations;"); - NL(); - - BeginNamespace(modelRoot.Namespace); - Output("/// "); - Output($"public sealed partial class {modelRoot.EntityContainerName}DbMigrationConfiguration : DbMigrationsConfiguration<{modelRoot.EntityContainerName}>"); - - Output("{"); - Output("partial void Init();"); - NL(); - - Output("/// "); - Output($"public {modelRoot.EntityContainerName}DbMigrationConfiguration()"); - Output("{"); - Output($"AutomaticMigrationsEnabled = {modelRoot.AutomaticMigrationsEnabled.ToString().ToLower()};"); - Output("AutomaticMigrationDataLossAllowed = false;"); - Output("Init();"); - Output("}"); - - Output("}"); - EndNamespace(modelRoot.Namespace); -} - -void WriteDbContextEF6(ModelRoot modelRoot) -{ - Output("using System;"); - Output("using System.Collections.Generic;"); - Output("using System.Linq;"); - Output("using System.ComponentModel.DataAnnotations.Schema;"); - Output("using System.Data.Entity;"); - Output("using System.Data.Entity.Infrastructure.Annotations;"); - NL(); - - BeginNamespace(modelRoot.Namespace); - - if (!string.IsNullOrEmpty(modelRoot.Summary)) - { - Output("/// "); - WriteCommentBody(modelRoot.Summary); - Output("/// "); - if (!string.IsNullOrEmpty(modelRoot.Description)) - { - Output("/// "); - WriteCommentBody(modelRoot.Description); - Output("/// "); - } - } - else - { - Output("/// "); - } - - Output($"{modelRoot.EntityContainerAccess.ToString().ToLower()} partial class {modelRoot.EntityContainerName} : System.Data.Entity.DbContext"); - Output("{"); - - PluralizationService pluralizationService = ModelRoot.PluralizationService; - - /***********************************************************************/ - // generate DBSets - /***********************************************************************/ - - IEnumerable classesWithTables = null; - - switch (modelRoot.InheritanceStrategy) - { - case CodeStrategy.TablePerType: - classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType).OrderBy(x => x.Name); - break; - case CodeStrategy.TablePerConcreteType: - classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType && !mc.IsAbstract).OrderBy(x => x.Name); - break; - case CodeStrategy.TablePerHierarchy: - classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType && mc.Superclass == null).OrderBy(x => x.Name); - break; - } - - if (classesWithTables != null) - { - Output("#region DbSets"); - - foreach (ModelClass modelClass in modelRoot.Classes.Where(x => !x.IsDependentType).OrderBy(x => x.Name)) - { - string dbSetName; - - if (!string.IsNullOrEmpty(modelClass.DbSetName)) - dbSetName = modelClass.DbSetName; - else - dbSetName = pluralizationService?.IsSingular(modelClass.Name) == true - ? pluralizationService.Pluralize(modelClass.Name) - : modelClass.Name; - - if (!string.IsNullOrEmpty(modelClass.Summary)) - { - NL(); - Output("/// "); - WriteCommentBody($"Repository for {modelClass.FullName} - {modelClass.Summary}"); - Output("/// "); - } - Output($"{modelRoot.DbSetAccess.ToString().ToLower()} virtual System.Data.Entity.DbSet<{modelClass.FullName}> {dbSetName} {{ get; set; }}"); - } - - Output("#endregion DbSets"); - NL(); - } - - Output("#region Constructors"); - NL(); - Output("partial void CustomInit();"); - NL(); - - /***********************************************************************/ - // constructors - /***********************************************************************/ - - if (!string.IsNullOrEmpty(modelRoot.ConnectionString) || !string.IsNullOrEmpty(modelRoot.ConnectionStringName)) - { - string connectionString = string.IsNullOrEmpty(modelRoot.ConnectionString) - ? $"Name={modelRoot.ConnectionStringName}" - : modelRoot.ConnectionString; - - Output("/// "); - Output("/// Default connection string"); - Output("/// "); - Output($"public static string ConnectionString {{ get; set; }} = @\"{connectionString}\";"); - - Output("/// "); - Output($"public {modelRoot.EntityContainerName}() : base(ConnectionString)"); - Output("{"); - Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); - Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); - if (modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None) - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);"); - else - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); - Output("CustomInit();"); - Output("}"); - NL(); - } - else - { - Output($"#warning Default constructor not generated for {modelRoot.EntityContainerName} since no default connection string was specified in the model"); - NL(); - } - - Output("/// "); - Output($"public {modelRoot.EntityContainerName}(string connectionString) : base(connectionString)"); - Output("{"); - Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); - Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); - if (modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None) - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);"); - else - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); - Output("CustomInit();"); - Output("}"); - NL(); - - Output("/// "); - Output($"public {modelRoot.EntityContainerName}(string connectionString, System.Data.Entity.Infrastructure.DbCompiledModel model) : base(connectionString, model)"); - Output("{"); - Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); - Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); - if (modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None) - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);"); - else - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); - Output("CustomInit();"); - Output("}"); - NL(); - - Output("/// "); - Output($"public {modelRoot.EntityContainerName}(System.Data.Common.DbConnection existingConnection, bool contextOwnsConnection) : base(existingConnection, contextOwnsConnection)"); - Output("{"); - Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); - Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); - if (modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None) - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);"); - else - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); - Output("CustomInit();"); - Output("}"); - NL(); - - Output("/// "); - Output($"public {modelRoot.EntityContainerName}(System.Data.Common.DbConnection existingConnection, System.Data.Entity.Infrastructure.DbCompiledModel model, bool contextOwnsConnection) : base(existingConnection, model, contextOwnsConnection)"); - Output("{"); - Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); - Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); - if (modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None) - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);"); - else - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); - Output("CustomInit();"); - Output("}"); - NL(); - - Output("/// "); - Output($"public {modelRoot.EntityContainerName}(System.Data.Entity.Infrastructure.DbCompiledModel model) : base(model)"); - Output("{"); - Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); - Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); - if (modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None) - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);"); - else - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); - Output("CustomInit();"); - Output("}"); - NL(); - - Output("/// "); - Output($"public {modelRoot.EntityContainerName}(System.Data.Entity.Core.Objects.ObjectContext objectContext, bool dbContextOwnsObjectContext) : base(objectContext, dbContextOwnsObjectContext)"); - Output("{"); - Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); - Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); - if (modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None) - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);"); - else - Output($"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); - Output("CustomInit();"); - Output("}"); - NL(); - Output("#endregion Constructors"); - NL(); - - /***********************************************************************/ - // Experimental - /***********************************************************************/ - /* - Output("private void HandleOrphans()"); - Output("{"); - Output("ChangeTracker.DetectChanges();"); - Output("bool wasLazyLoading = Configuration.LazyLoadingEnabled;"); - Output("Configuration.LazyLoadingEnabled = false;"); - NL(); - Output("try"); - Output("{"); - - Dictionary> orphanRemoval = new Dictionary>(); - foreach (ModelClass modelClass in modelRoot.Classes.Where(x => !x.IsDependentType).OrderBy(x => x.Name)) - { - if (!orphanRemoval.ContainsKey(modelClass.DbSetName)) - orphanRemoval.Add(modelClass.DbSetName, new List()); - foreach (NavigationProperty navigationProperty in modelClass.AllRequiredNavigationProperties()) - { - Association association = navigationProperty.AssociationObject; - if (!string.IsNullOrEmpty(navigationProperty.PropertyName)) - { - if (association is BidirectionalAssociation && association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One || - (association is UnidirectionalAssociation && (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One && - association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One))) - { - orphanRemoval[modelClass.DbSetName].Add($"{modelClass.DbSetName}.RemoveRange({modelClass.DbSetName}.Local.Where(x => Entry(x).State != EntityState.Deleted && x.{navigationProperty.PropertyName} == null));"); - } - else if (association is UnidirectionalAssociation && - association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) - { - orphanRemoval[modelClass.DbSetName].Add($"{modelClass.DbSetName}.RemoveRange({modelClass.DbSetName}.Local.Where(x => Entry(x).State != EntityState.Deleted).Except({association.Source.DbSetName}.Local.Where(x => Entry(x).State != EntityState.Deleted).SelectMany(x => x.{association.TargetPropertyName})));"); - } - else if (association is UnidirectionalAssociation && - !association.Source.IsDependentType && !association.Target.IsDependentType && - (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne || - association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One)) - { - orphanRemoval[modelClass.DbSetName].Add($"{modelClass.DbSetName}.RemoveRange({modelClass.DbSetName}.Local.Where(x => Entry(x).State != EntityState.Deleted).Except({association.Source.DbSetName}.Local.Where(x => Entry(x).State != EntityState.Deleted).Select(x => x.{association.TargetPropertyName})));"); - } - else if (association is BidirectionalAssociation bidirectional && - !association.Source.IsDependentType && !association.Target.IsDependentType && - bidirectional.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne) - { - orphanRemoval[modelClass.DbSetName].Add($"{modelClass.DbSetName}.RemoveRange({modelClass.DbSetName}.Local.Where(x => Entry(x).State != EntityState.Deleted).Except({bidirectional.Target.DbSetName}.Local.Where(x => Entry(x).State != EntityState.Deleted).Select(x => x.{bidirectional.SourcePropertyName})));"); - } - } - } - } - - foreach (string key in orphanRemoval.Keys.Where(k => orphanRemoval[k].Any())) - { - Output($"if ({key}.Local.Any(x => Entry(x).State != EntityState.Deleted))"); - Output("{"); - foreach (string action in orphanRemoval[key]) - { - Output(action); - } - Output("}"); - } - - Output("}"); - Output("finally"); - Output("{"); - Output("Configuration.LazyLoadingEnabled = wasLazyLoading;"); - Output("}"); - - Output("}"); - NL(); - */ - - /***********************************************************************/ - // OnModelCreating - /***********************************************************************/ - Output("partial void OnModelCreatingImpl(System.Data.Entity.DbModelBuilder modelBuilder);"); - Output("partial void OnModelCreatedImpl(System.Data.Entity.DbModelBuilder modelBuilder);"); - NL(); - - Output("/// "); - Output("protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)"); - Output("{"); - Output("base.OnModelCreating(modelBuilder);"); - Output("OnModelCreatingImpl(modelBuilder);"); - NL(); - - Output($"modelBuilder.HasDefaultSchema(\"{modelRoot.DatabaseSchema}\");"); - - List segments = new List(); - - Sawczyn.EFDesigner.EFModel.Multiplicity[] singles = { Sawczyn.EFDesigner.EFModel.Multiplicity.One, Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne }; - Sawczyn.EFDesigner.EFModel.Multiplicity[] multiples = { Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany }; - List visited = new List(); - List foreignKeyColumns = new List(); - - foreach (ModelClass modelClass in modelRoot.Classes.OrderBy(x => x.Name)) - { - segments.Clear(); - foreignKeyColumns.Clear(); - NL(); - - // class level - bool isDependent = modelClass.IsDependentType; - segments.Add($"modelBuilder.{(isDependent ? "ComplexType" : "Entity")}<{modelClass.FullName}>()"); - - // note: this must come before the 'ToTable' call or there's a runtime error - if (modelRoot.InheritanceStrategy == CodeStrategy.TablePerConcreteType && modelClass.Superclass != null) - segments.Add("Map(x => x.MapInheritedProperties())"); - - if (classesWithTables.Contains(modelClass)) - { - if (modelClass.DatabaseSchema == modelClass.ModelRoot.DatabaseSchema) - segments.Add($"ToTable(\"{modelClass.TableName}\")"); - else - segments.Add($"ToTable(\"{modelClass.TableName}\", \"{modelClass.DatabaseSchema}\")"); - - // primary key code segments must be output last, since HasKey returns a different type - List identityAttributes = modelClass.IdentityAttributes.ToList(); - if (identityAttributes.Count() == 1) - segments.Add($"HasKey(t => t.{identityAttributes[0].Name})"); - else if (identityAttributes.Count() > 1) - segments.Add($"HasKey(t => new {{ t.{string.Join(", t.", identityAttributes.Select(ia => ia.Name))} }})"); - } - - foreach (ModelAttribute transient in modelClass.Attributes.Where(x => !x.Persistent)) - segments.Add($"Ignore(t => t.{transient.Name})"); - - if (segments.Count > 1) - { - if (modelRoot.ChopMethodChains) - OutputChopped(segments); - else - Output(string.Join(".", segments) + ";"); - } - - // indexed properties - foreach (ModelAttribute indexed in modelClass.Attributes.Where(x => x.Indexed && !x.IsIdentity)) - { - segments.Clear(); - string attributeName = !indexed.AutoProperty ? $"_{indexed.Name}" : indexed.Name; - segments.Add($"modelBuilder.Entity<{modelClass.FullName}>().HasIndex(t => t.{attributeName})"); - if (indexed.IndexedUnique) segments.Add("IsUnique()"); - if (segments.Count > 1) - { - if (modelRoot.ChopMethodChains) - OutputChopped(segments); - else - Output(string.Join(".", segments) + ";"); - } - } - - // attribute level - foreach (ModelAttribute modelAttribute in modelClass.Attributes.Where(x => x.Persistent && !SpatialTypes.Contains(x.Type))) - { - segments.Clear(); - - if (modelAttribute.MaxLength > 0) - segments.Add($"HasMaxLength({modelAttribute.MaxLength})"); - if (modelAttribute.Required) - segments.Add("IsRequired()"); - if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) - segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); - if (!string.IsNullOrEmpty(modelAttribute.ColumnType) && modelAttribute.ColumnType.ToLowerInvariant() != "default") - segments.Add($"HasColumnType(\"{modelAttribute.ColumnType}\")"); - if (modelAttribute.Indexed && !modelAttribute.IsIdentity) - segments.Add("HasColumnAnnotation(\"Index\", new IndexAnnotation(new IndexAttribute()))"); - if (modelAttribute.IsConcurrencyToken) - segments.Add("IsRowVersion()"); - if (modelAttribute.IsIdentity) - { - if (modelAttribute.IdentityType == IdentityType.AutoGenerated) - segments.Add("HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)"); - else - segments.Add("HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)"); - } - - if (segments.Any()) - { - segments.Insert(0, $"modelBuilder.{(isDependent ? "ComplexType" : "Entity")}<{modelClass.FullName}>()"); - segments.Insert(1, $"Property(t => t.{modelAttribute.Name})"); - - if (modelRoot.ChopMethodChains) - OutputChopped(segments); - else - Output(string.Join(".", segments) + ";"); - } - } - - if (!isDependent) - { - // Navigation endpoints are distingished as Source and Target. They are also distinguished as Principal - // and Dependent. How do these map? - // In the case of one-to-one or zero-to-one-to-zero-to-one, it's model dependent and the user has to tell us - // In all other cases, we can tell by the cardinalities of the associations - // What matters is the Principal and Dependent classifications, so we look at those. - // Source and Target are accidents of where the user started drawing the association. - - // navigation properties - foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass).OfType().Where(x => !x.Target.IsDependentType)) - { - if (visited.Contains(association)) continue; - visited.Add(association); - - segments.Clear(); - segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); - - switch (association.TargetMultiplicity) // realized by property on source - { - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: - segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - segments.Add($"HasRequired(x => x.{association.TargetPropertyName})"); - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: - segments.Add($"HasOptional(x => x.{association.TargetPropertyName})"); - break; - //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: - // segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); - // break; - } - - switch (association.SourceMultiplicity) // realized by property on target, but no property on target - { - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: - segments.Add("WithMany()"); - if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) - { - if (modelClass == association.Source) - { - segments.Add("Map(x => { " + - $@"x.ToTable(""{association.Source.Name}_x_{association.TargetPropertyName}""); " + - $@"x.MapLeftKey(""{association.Source.Name}_{association.Source.AllAttributes.FirstOrDefault(a => a.IsIdentity)?.Name}""); " + - $@"x.MapRightKey(""{association.Target.Name}_{association.Target.AllAttributes.FirstOrDefault(a => a.IsIdentity)?.Name}""); " + - "})"); - } - else - { - segments.Add("Map(x => { " + - $@"x.ToTable(""{association.Source.Name}_x_{association.TargetPropertyName}""); " + - $@"x.MapRightKey(""{association.Source.Name}_{association.Source.AllAttributes.FirstOrDefault(a => a.IsIdentity)?.Name}""); " + - $@"x.MapLeftKey(""{association.Target.Name}_{association.Target.AllAttributes.FirstOrDefault(a => a.IsIdentity)?.Name}""); " + - "})"); - } - } - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One) - { - if (association.TargetRole == EndpointRole.Dependent) - segments.Add("WithRequiredDependent()"); - else - segments.Add("WithRequiredPrincipal()"); - } - else - { - segments.Add("WithRequired()"); - } - - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: - if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne) - { - if (association.TargetRole == EndpointRole.Dependent) - segments.Add("WithOptionalDependent()"); - else - segments.Add("WithOptionalPrincipal()"); - } - else - { - segments.Add("WithOptional()"); - } - - break; - //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: - // segments.Add("HasMany()"); - // break; - } - - string foreignKeySegment = CreateForeignKeyColumnSegmentEF6(association, foreignKeyColumns); - if (foreignKeySegment != null) segments.Add(foreignKeySegment); - - // Certain associations cascade delete automatically. Also, the user may ask for it. - // We only generate a cascade delete call if the user asks for it. - if ((association.TargetDeleteAction != DeleteAction.Default && association.TargetRole == EndpointRole.Principal) || - (association.SourceDeleteAction != DeleteAction.Default && association.SourceRole == EndpointRole.Principal)) - { - string willCascadeOnDelete = (association.TargetDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant(); - segments.Add($"WillCascadeOnDelete({willCascadeOnDelete})"); - } - - if (modelRoot.ChopMethodChains) - OutputChopped(segments); - else - Output(string.Join(".", segments) + ";"); - } - - foreach (BidirectionalAssociation association in Association.GetLinksToSources(modelClass).OfType()) - { - if (visited.Contains(association)) continue; - visited.Add(association); - - segments.Clear(); - segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); - - switch (association.SourceMultiplicity) // realized by property on target - { - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: - segments.Add($"HasMany(x => x.{association.SourcePropertyName})"); - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - segments.Add($"HasRequired(x => x.{association.SourcePropertyName})"); - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: - segments.Add($"HasOptional(x => x.{association.SourcePropertyName})"); - break; - //one or more constraint not supported in EF. TODO: make this possible ... later - //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: - // segments.Add($"HasMany(x => x.{association.SourcePropertyName})"); - // break; - } - - switch (association.TargetMultiplicity) // realized by property on source - { - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: - segments.Add($"WithMany(x => x.{association.TargetPropertyName})"); - if (association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) - { - if (modelClass == association.Source) - { - segments.Add("Map(x => { " + - $@"x.ToTable(""{association.SourcePropertyName}_x_{association.TargetPropertyName}""); " + - $@"x.MapLeftKey(""{association.Source.Name}_{association.Source.AllAttributes.FirstOrDefault(a => a.IsIdentity)?.Name}""); " + - $@"x.MapRightKey(""{association.Target.Name}_{association.Target.AllAttributes.FirstOrDefault(a => a.IsIdentity)?.Name}""); " + - "})"); - } - else - { - segments.Add("Map(x => { " + - $@"x.ToTable(""{association.SourcePropertyName}_x_{association.TargetPropertyName}""); " + - $@"x.MapRightKey(""{association.Source.Name}_{association.Source.AllAttributes.FirstOrDefault(a => a.IsIdentity)?.Name}""); " + - $@"x.MapLeftKey(""{association.Target.Name}_{association.Target.AllAttributes.FirstOrDefault(a => a.IsIdentity)?.Name}""); " + - "})"); - } - } - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.One: - if (association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One) - { - if (association.SourceRole == EndpointRole.Dependent) - segments.Add($"WithRequiredDependent(x => x.{association.TargetPropertyName})"); - else - segments.Add($"WithRequiredPrincipal(x => x.{association.TargetPropertyName})"); - } - else - { - segments.Add($"WithRequired(x => x.{association.TargetPropertyName})"); - } - - break; - case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: - if (association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne) - { - if (association.SourceRole == EndpointRole.Dependent) - segments.Add($"WithOptionalDependent(x => x.{association.TargetPropertyName})"); - else - segments.Add($"WithOptionalPrincipal(x => x.{association.TargetPropertyName})"); - } - else - { - segments.Add($"WithOptional(x => x.{association.TargetPropertyName})"); - } - - break; - //one or more constraint not supported in EF. TODO: make this possible ... later - //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: - // segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); - // break; - } - - string foreignKeySegment = CreateForeignKeyColumnSegmentEF6(association, foreignKeyColumns); - if (foreignKeySegment != null) segments.Add(foreignKeySegment); - - if (association.SourceDeleteAction != DeleteAction.Default) - { - string willCascadeOnDelete = (association.SourceDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant(); - segments.Add($"WillCascadeOnDelete({willCascadeOnDelete})"); - } - - if (modelRoot.ChopMethodChains) - OutputChopped(segments); - else - Output(string.Join(".", segments) + ";"); - } - } - } - - NL(); - - Output("OnModelCreatedImpl(modelBuilder);"); - Output("}"); - - Output("}"); - - EndNamespace(modelRoot.Namespace); -} - -string CreateForeignKeyColumnSegmentEF6(Association association, List foreignKeyColumns) -{ - // foreign key definitions always go in the table representing the Dependent end of the association - // if there is no dependent end (i.e., many-to-many), there are no foreign keys - string nameBase = string.Empty; - - if (association.SourceRole == EndpointRole.Dependent) - nameBase = association.TargetPropertyName; - else if (association.TargetRole == EndpointRole.Dependent) - nameBase = association is BidirectionalAssociation b ? b.SourcePropertyName : $"{association.Source.Name}.{association.TargetPropertyName}"; - else - return null; - - string columnName = $"{nameBase}_Id"; - if (foreignKeyColumns.Contains(columnName)) - { - int index = 0; - do - { - columnName = $"{nameBase}{++index}_Id"; - } while (foreignKeyColumns.Contains(columnName)); - } - foreignKeyColumns.Add(columnName); - return $@"Map(x => x.MapKey(""{columnName}""))"; -} - -#> \ No newline at end of file diff --git a/src/Testing/Sandbox_EF6/Sandbox_EF6.csproj b/src/Testing/Sandbox_EF6/Sandbox_EF6.csproj index 130ca5ab1..5b79c4616 100644 --- a/src/Testing/Sandbox_EF6/Sandbox_EF6.csproj +++ b/src/Testing/Sandbox_EF6/Sandbox_EF6.csproj @@ -66,7 +66,6 @@ Designer - EFModel.efmodel