private void WriteOnModelCreateEFCore(ModelRoot modelRoot, List <string> segments, ModelClass[] classesWithTables) { Output("partial void OnModelCreatingImpl(ModelBuilder modelBuilder);"); Output("partial void OnModelCreatedImpl(ModelBuilder modelBuilder);"); NL(); Output("/// <inheritdoc />"); Output("protected override void OnModelCreating(ModelBuilder modelBuilder)"); Output("{"); Output("base.OnModelCreating(modelBuilder);"); Output("OnModelCreatingImpl(modelBuilder);"); NL(); Output($"modelBuilder.HasDefaultSchema(\"{modelRoot.DatabaseSchema}\");"); List <Association> visited = new List <Association>(); List <string> foreignKeyColumns = new List <string>(); foreach (ModelClass modelClass in modelRoot.Classes.OrderBy(x => x.Name)) { segments.Clear(); foreignKeyColumns.Clear(); NL(); // class level segments.Add($"modelBuilder.{(modelClass.IsDependentType ? "Owned" : "Entity")}<{modelClass.FullName}>()"); foreach (ModelAttribute transient in modelClass.Attributes.Where(x => !x.Persistent)) { segments.Add($"Ignore(t => t.{transient.Name})"); } if (!modelClass.IsDependentType) { // 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)) { segments.Add(modelClass.DatabaseSchema == modelClass.ModelRoot.DatabaseSchema ? $"ToTable(\"{modelClass.TableName}\")" : $"ToTable(\"{modelClass.TableName}\", \"{modelClass.DatabaseSchema}\")"); // primary key code segments must be output last, since HasKey returns a different type List <ModelAttribute> 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))} }})"); } } } if (segments.Count > 1 || modelClass.IsDependentType) { if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } if (modelClass.IsDependentType) { continue; } // attribute level foreach (ModelAttribute modelAttribute in modelClass.Attributes.Where(x => x.Persistent && !SpatialTypes.Contains(x.Type))) { segments.Clear(); if ((modelAttribute.MaxLength ?? 0) > 0) { segments.Add($"HasMaxLength({modelAttribute.MaxLength.Value})"); } if (modelAttribute.Required) { segments.Add("IsRequired()"); } if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) { segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); } if (!modelAttribute.AutoProperty) { segments.Add($"HasField(\"_{modelAttribute.Name}\")"); segments.Add($"UsePropertyAccessMode(PropertyAccessMode.{(modelAttribute.PersistencePoint == PersistencePointType.Field ? "Field" : "Property")})"); } if (!string.IsNullOrEmpty(modelAttribute.ColumnType) && modelAttribute.ColumnType.ToLowerInvariant() != "default") { if (modelAttribute.ColumnType.ToLowerInvariant() == "varchar" || modelAttribute.ColumnType.ToLowerInvariant() == "nvarchar" || modelAttribute.ColumnType.ToLowerInvariant() == "char") { segments.Add($"HasColumnType(\"{modelAttribute.ColumnType}({(modelAttribute.MaxLength > 0 ? modelAttribute.MaxLength.ToString() : "max")})\")"); } else { segments.Add($"HasColumnType(\"{modelAttribute.ColumnType}\")"); } } if (modelAttribute.IsConcurrencyToken) { segments.Add("IsRowVersion()"); } if (modelAttribute.IsIdentity) { segments.Add(modelAttribute.IdentityType == IdentityType.AutoGenerated ? "ValueGeneratedOnAdd()" : "ValueGeneratedNever()"); } if (segments.Any()) { segments.Insert(0, $"modelBuilder.{(modelClass.IsDependentType ? "Owned" : "Entity")}<{modelClass.FullName}>()"); segments.Insert(1, $"Property(t => t.{modelAttribute.Name})"); if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } if (modelAttribute.Indexed && !modelAttribute.IsIdentity) { segments.Clear(); segments.Add($"modelBuilder.Entity<{modelClass.FullName}>().HasIndex(t => t.{modelAttribute.Name})"); if (modelAttribute.IndexedUnique) { segments.Add("IsUnique()"); } if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } } bool hasDefinedConcurrencyToken = modelClass.AllAttributes.Any(x => x.IsConcurrencyToken); if (!hasDefinedConcurrencyToken && modelClass.EffectiveConcurrency == ConcurrencyOverride.Optimistic) { Output($@"modelBuilder.Entity<{modelClass.FullName}>().Property<byte[]>(""Timestamp"").IsConcurrencyToken();"); } // 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 // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) .OfType <UnidirectionalAssociation>() .Where(x => x.Persistent && !x.Target.IsDependentType)) { if (visited.Contains(association)) { continue; } visited.Add(association); segments.Clear(); segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); bool required = false; switch (association.TargetMultiplicity) // realized by property on source { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: // TODO: Implement many-to-many if (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); } else { continue; } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: segments.Add($"HasOne(x => x.{association.TargetPropertyName})"); break; case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"HasOne(x => x.{association.TargetPropertyName})"); break; //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: // segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); // break; } string columnPrefix = association.SourceRole == EndpointRole.Dependent ? "" : association.Target.Name + "_"; switch (association.SourceMultiplicity) // realized by shadow property on target { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: // TODO: Implement many-to-many if (association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { segments.Add("WithMany()"); segments.Add($"HasForeignKey(\"{columnPrefix}{association.TargetPropertyName}_Id\")"); } else { continue; } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: segments.Add("WithOne()"); segments.Add(association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany ? $"HasForeignKey<{association.Source.FullName}>(\"{columnPrefix}{association.TargetPropertyName}_Id\")" : $"HasForeignKey(\"{columnPrefix}{association.TargetPropertyName}_Id\")"); required = true; break; case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add("WithOne()"); segments.Add(association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany ? $"HasForeignKey<{association.Source.FullName}>(\"{columnPrefix}{association.TargetPropertyName}_Id\")" : $"HasForeignKey(\"{columnPrefix}{association.TargetPropertyName}_Id\")"); break; //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: // segments.Add("HasMany()"); // break; } if (required) { segments.Add("IsRequired()"); } if (association.TargetRole == EndpointRole.Principal || association.SourceRole == EndpointRole.Principal) { DeleteAction deleteAction = association.SourceRole == EndpointRole.Principal ? association.SourceDeleteAction : association.TargetDeleteAction; switch (deleteAction) { case DeleteAction.None: segments.Add("OnDelete(DeleteBehavior.Restrict)"); break; case DeleteAction.Cascade: segments.Add("OnDelete(DeleteBehavior.Cascade)"); break; } } if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) .OfType <UnidirectionalAssociation>() .Where(x => x.Persistent && x.Target.IsDependentType)) { if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne || association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One) { Output($"modelBuilder.Entity<{modelClass.FullName}>().OwnsOne(x => x.{association.TargetPropertyName});"); } else { Output($"// Dependent 1-many association seen ({association.TargetPropertyName}). Code generation still unsupported in designer."); } } // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (BidirectionalAssociation association in Association.GetLinksToSources(modelClass) .OfType <BidirectionalAssociation>() .Where(x => x.Persistent)) { if (visited.Contains(association)) { continue; } visited.Add(association); // TODO: fix cascade delete bool required = false; segments.Clear(); segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); switch (association.SourceMultiplicity) // realized by property on target { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: // TODO: Implement many-to-many if (association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { segments.Add($"HasMany(x => x.{association.SourcePropertyName})"); } else { continue; } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: segments.Add($"HasOne(x => x.{association.SourcePropertyName})"); break; case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"HasOne(x => x.{association.SourcePropertyName})"); break; //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: // TODO: Implement many-to-many if (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { segments.Add($"WithMany(x => x.{association.TargetPropertyName})"); } else { continue; } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: segments.Add($"WithOne(x => x.{association.TargetPropertyName})"); required = true; break; case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"WithOne(x => x.{association.TargetPropertyName})"); break; //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: // segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); // break; } string foreignKeySegment = CreateForeignKeySegmentEFCore(association, foreignKeyColumns); if (foreignKeySegment != null) { segments.Add(foreignKeySegment); } if (required) { segments.Add("IsRequired()"); } if (association.TargetRole == EndpointRole.Principal || association.SourceRole == EndpointRole.Principal) { DeleteAction deleteAction = association.SourceRole == EndpointRole.Principal ? association.SourceDeleteAction : association.TargetDeleteAction; switch (deleteAction) { case DeleteAction.None: segments.Add("OnDelete(DeleteBehavior.Restrict)"); break; case DeleteAction.Cascade: segments.Add("OnDelete(DeleteBehavior.Cascade)"); break; } } if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } } NL(); Output("OnModelCreatedImpl(modelBuilder);"); Output("}"); }
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("/// <summary>"); WriteCommentBody(modelRoot.Summary); Output("/// </summary>"); if (!string.IsNullOrEmpty(modelRoot.Description)) { Output("/// <remarks>"); WriteCommentBody(modelRoot.Description); Output("/// </remarks>"); } } else { Output("/// <inheritdoc/>"); } Output($"{modelRoot.EntityContainerAccess.ToString().ToLower()} partial class {modelRoot.EntityContainerName} : System.Data.Entity.DbContext"); Output("{"); PluralizationService pluralizationService = ModelRoot.PluralizationService; /***********************************************************************/ // generate DBSets /***********************************************************************/ ModelClass[] classesWithTables = null; switch (modelRoot.InheritanceStrategy) { case CodeStrategy.TablePerType: classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType).OrderBy(x => x.Name).ToArray(); break; case CodeStrategy.TablePerConcreteType: classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType && !mc.IsAbstract).OrderBy(x => x.Name).ToArray(); break; case CodeStrategy.TablePerHierarchy: classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType && mc.Superclass == null).OrderBy(x => x.Name).ToArray(); break; } if (classesWithTables?.Any() == true) { 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("/// <summary>"); WriteCommentBody($"Repository for {modelClass.FullName} - {modelClass.Summary}"); Output("/// </summary>"); } 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("/// <summary>"); Output("/// Default connection string"); Output("/// </summary>"); Output($"public static string ConnectionString {{ get; set; }} = @\"{connectionString}\";"); Output("/// <inheritdoc />"); Output($"public {modelRoot.EntityContainerName}() : base(ConnectionString)"); Output("{"); Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" : $"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("/// <inheritdoc />"); Output($"public {modelRoot.EntityContainerName}(string connectionString) : base(connectionString)"); Output("{"); Output($"Configuration.LazyLoadingEnabled = {modelRoot.LazyLoadingEnabled.ToString().ToLower()};"); Output($"Configuration.ProxyCreationEnabled = {modelRoot.ProxyGenerationEnabled.ToString().ToLower()};"); Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); Output("CustomInit();"); Output("}"); NL(); Output("/// <inheritdoc />"); 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()};"); Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); Output("CustomInit();"); Output("}"); NL(); Output("/// <inheritdoc />"); 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()};"); Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); Output("CustomInit();"); Output("}"); NL(); Output("/// <inheritdoc />"); 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()};"); Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); Output("CustomInit();"); Output("}"); NL(); Output("/// <inheritdoc />"); 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()};"); Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); Output("CustomInit();"); Output("}"); NL(); Output("/// <inheritdoc />"); 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()};"); Output(modelRoot.DatabaseInitializerType == DatabaseInitializerKind.None ? $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(null);" : $"System.Data.Entity.Database.SetInitializer<{modelRoot.EntityContainerName}>(new {modelRoot.EntityContainerName}DatabaseInitializer());"); Output("CustomInit();"); Output("}"); NL(); Output("#endregion Constructors"); NL(); /***********************************************************************/ // OnModelCreating /***********************************************************************/ Output("partial void OnModelCreatingImpl(System.Data.Entity.DbModelBuilder modelBuilder);"); Output("partial void OnModelCreatedImpl(System.Data.Entity.DbModelBuilder modelBuilder);"); NL(); Output("/// <inheritdoc />"); 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 <string> segments = new List <string>(); List <Association> visited = new List <Association>(); List <string> foreignKeyColumns = new List <string>(); 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}>()"); foreach (ModelAttribute transient in modelClass.Attributes.Where(x => !x.Persistent)) { segments.Add($"Ignore(t => t.{transient.Name})"); } // 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)) { segments.Add(modelClass.DatabaseSchema == modelClass.ModelRoot.DatabaseSchema ? $"ToTable(\"{modelClass.TableName}\")" : $"ToTable(\"{modelClass.TableName}\", \"{modelClass.DatabaseSchema}\")"); // primary key code segments must be output last, since HasKey returns a different type List <ModelAttribute> 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))} }})"); } } if (segments.Count > 1) { if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } if (modelClass.IsDependentType) { continue; } // indexed properties foreach (ModelAttribute indexed in modelClass.Attributes.Where(x => x.Indexed && !x.IsIdentity)) { segments.Clear(); segments.Add(indexed.AutoProperty ? $"modelBuilder.Entity<{modelClass.FullName}>().HasIndex(t => t.{indexed.Name})" : $"modelBuilder.Entity<{modelClass.FullName}>().HasIndex(\"_{indexed.Name}\")"); 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) { segments.Add(modelAttribute.IdentityType == IdentityType.AutoGenerated ? "HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)" : "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 // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) .OfType <UnidirectionalAssociation>() .Where(x => x.Persistent && !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) { segments.Add(association.TargetRole == EndpointRole.Dependent ? "WithRequiredDependent()" : "WithRequiredPrincipal()"); } else { segments.Add("WithRequired()"); } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne) { segments.Add(association.TargetRole == EndpointRole.Dependent ? "WithOptionalDependent()" : "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.Default && association.TargetRole == EndpointRole.Principal ? (association.TargetDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant() : (association.SourceDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant(); segments.Add($"WillCascadeOnDelete({willCascadeOnDelete})"); } if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (BidirectionalAssociation association in Association.GetLinksToSources(modelClass) .OfType <BidirectionalAssociation>() .Where(x => x.Persistent)) { 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) { segments.Add(association.SourceRole == EndpointRole.Dependent ? $"WithRequiredDependent(x => x.{association.TargetPropertyName})" : $"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) { segments.Add(association.SourceRole == EndpointRole.Dependent ? $"WithOptionalDependent(x => x.{association.TargetPropertyName})" : $"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.TargetDeleteAction != DeleteAction.Default && association.TargetRole == EndpointRole.Principal) || (association.SourceDeleteAction != DeleteAction.Default && association.SourceRole == EndpointRole.Principal)) { string willCascadeOnDelete = association.TargetDeleteAction != DeleteAction.Default && association.TargetRole == EndpointRole.Principal ? (association.TargetDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant() : (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); }
private void WriteModelCreateEF6(ModelRoot modelRoot, ModelClass[] classesWithTables) { Output("partial void OnModelCreatingImpl(System.Data.Entity.DbModelBuilder modelBuilder);"); Output("partial void OnModelCreatedImpl(System.Data.Entity.DbModelBuilder modelBuilder);"); NL(); Output("/// <inheritdoc />"); 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 <string> segments = new List <string>(); List <Association> visited = new List <Association>(); List <string> foreignKeyColumns = new List <string>(); List <string> declaredShadowProperties = new List <string>(); foreach (ModelClass modelClass in modelRoot.Classes.OrderBy(x => x.Name)) { segments.Clear(); foreignKeyColumns.Clear(); declaredShadowProperties.Clear(); NL(); // class level bool isDependent = modelClass.IsDependentType; segments.Add($"modelBuilder.{(isDependent ? "ComplexType" : "Entity")}<{modelClass.FullName}>()"); foreach (ModelAttribute transient in modelClass.Attributes.Where(x => !x.Persistent)) { segments.Add($"Ignore(t => t.{transient.Name})"); } // 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)) { segments.Add(modelClass.DatabaseSchema == modelClass.ModelRoot.DatabaseSchema ? $"ToTable(\"{modelClass.TableName}\")" : $"ToTable(\"{modelClass.TableName}\", \"{modelClass.DatabaseSchema}\")"); // primary key code segments must be output last, since HasKey returns a different type List <ModelAttribute> 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))} }})"); } } if (segments.Count > 1) { if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } if (modelClass.IsDependentType) { continue; } // attribute level foreach (ModelAttribute modelAttribute in modelClass.Attributes.Where(x => x.Persistent && !SpatialTypes.Contains(x.Type))) { segments.Clear(); if ((modelAttribute.MaxLength ?? 0) > 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) { segments.Add(modelAttribute.IdentityType == IdentityType.AutoGenerated ? "HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)" : "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 (modelAttribute.Indexed && !modelAttribute.IsIdentity) { segments.Clear(); segments.Add($"modelBuilder.Entity<{modelClass.FullName}>().HasIndex(t => t.{modelAttribute.Name})"); if (modelAttribute.IndexedUnique) { segments.Add("IsUnique()"); } if (segments.Count > 1) { 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 // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) .OfType <UnidirectionalAssociation>() .Where(x => x.Persistent && !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) { string tableMap = $"{association.Source.Name}_x_{association.TargetPropertyName}"; string sourceMap = string.Join(", ", association.Source.AllIdentityAttributeNames.Select(n => $@"""{association.Source.Name}_{n}""").ToList()); string targetMap = string.Join(", ", association.Target.AllIdentityAttributeNames.Select(n => $@"""{association.Target.Name}_{n}""").ToList()); segments.Add(modelClass == association.Source ? $@"Map(x => {{ x.ToTable(""{tableMap}""); x.MapLeftKey({sourceMap}); x.MapRightKey({targetMap}); }})" : $@"Map(x => {{ x.ToTable(""{tableMap}""); x.MapLeftKey({targetMap}); x.MapRightKey({sourceMap}); }})"); } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One) { segments.Add(association.TargetRole == EndpointRole.Dependent ? "WithRequiredDependent()" : "WithRequiredPrincipal()"); } else { segments.Add("WithRequired()"); } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne) { segments.Add(association.TargetRole == EndpointRole.Dependent ? "WithOptionalDependent()" : "WithOptionalPrincipal()"); } else { segments.Add("WithOptional()"); } break; //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: // segments.Add("HasMany()"); // break; } string foreignKeySegment = CreateForeignKeySegmentEF6(association, foreignKeyColumns); // can't shadow properties twice if (foreignKeySegment != null) { if (!foreignKeySegment.Contains("MapKey")) { segments.Add(foreignKeySegment); } else if (!declaredShadowProperties.Contains(foreignKeySegment)) { declaredShadowProperties.Add(foreignKeySegment); 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.Default && association.TargetRole == EndpointRole.Principal ? (association.TargetDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant() : (association.SourceDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant(); segments.Add($"WillCascadeOnDelete({willCascadeOnDelete})"); } if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (BidirectionalAssociation association in Association.GetLinksToSources(modelClass) .OfType <BidirectionalAssociation>() .Where(x => x.Persistent)) { 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) { string tableMap = $"{association.SourcePropertyName}_x_{association.TargetPropertyName}"; string sourceMap = string.Join(", ", association.Source.AllIdentityAttributeNames.Select(n => $@"""{association.Source.Name}_{n}""").ToList()); string targetMap = string.Join(", ", association.Target.AllIdentityAttributeNames.Select(n => $@"""{association.Target.Name}_{n}""").ToList()); segments.Add(modelClass == association.Source ? $@"Map(x => {{ x.ToTable(""{tableMap}""); x.MapLeftKey({sourceMap}); x.MapRightKey({targetMap}); }})" : $@"Map(x => {{ x.ToTable(""{tableMap}""); x.MapLeftKey({targetMap}); x.MapRightKey({sourceMap}); }})"); } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: if (association.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One) { segments.Add(association.SourceRole == EndpointRole.Dependent ? $"WithRequiredDependent(x => x.{association.TargetPropertyName})" : $"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) { segments.Add(association.SourceRole == EndpointRole.Dependent ? $"WithOptionalDependent(x => x.{association.TargetPropertyName})" : $"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 = CreateForeignKeySegmentEF6(association, foreignKeyColumns); // can't shadow properties twice if (foreignKeySegment != null) { if (!foreignKeySegment.Contains("MapKey")) { segments.Add(foreignKeySegment); } else if (!declaredShadowProperties.Contains(foreignKeySegment)) { declaredShadowProperties.Add(foreignKeySegment); segments.Add(foreignKeySegment); } } if ((association.TargetDeleteAction != DeleteAction.Default && association.TargetRole == EndpointRole.Principal) || (association.SourceDeleteAction != DeleteAction.Default && association.SourceRole == EndpointRole.Principal)) { string willCascadeOnDelete = association.TargetDeleteAction != DeleteAction.Default && association.TargetRole == EndpointRole.Principal ? (association.TargetDeleteAction == DeleteAction.Cascade).ToString().ToLowerInvariant() : (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("}"); }
// Revisit if/when supported in EFCore // void WriteDatabaseInitializerEFCore(ModelRoot modelRoot) // { // Output("using System.Data.Entity;"); // NL(); // // BeginNamespace(modelRoot.Namespace); // // 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 WriteMigrationConfigurationEFCore(ModelRoot modelRoot) // { // //if (modelRoot.DatabaseInitializerType != DatabaseInitializerKind.MigrateDatabaseToLatestVersion) // // return; // // Output("using System.Data.Entity.Migrations;"); // NL(); // // BeginNamespace(modelRoot.Namespace); // Output("public sealed partial class {0}DbMigrationConfiguration : DbMigrationsConfiguration<{0}>", modelRoot.EntityContainerName); // // Output("{"); // Output("partial void Init();"); // NL(); // // Output("public {0}DbMigrationConfiguration()", modelRoot.EntityContainerName); // Output("{"); // Output("AutomaticMigrationsEnabled = {0};", modelRoot.AutomaticMigrationsEnabled.ToString().ToLower()); // Output("AutomaticMigrationDataLossAllowed = false;"); // Output("Init();"); // Output("}"); // // Output("}"); // EndNamespace(modelRoot.Namespace); // } void WriteDbContextEFCore(ModelRoot modelRoot) { List <string> segments = new List <string>(); Output("using System;"); Output("using System.Collections.Generic;"); Output("using System.Linq;"); Output("using System.ComponentModel.DataAnnotations.Schema;"); Output("using Microsoft.EntityFrameworkCore;"); NL(); BeginNamespace(modelRoot.Namespace); if (!string.IsNullOrEmpty(modelRoot.Summary)) { Output("/// <summary>"); WriteCommentBody(modelRoot.Summary); Output("/// </summary>"); if (!string.IsNullOrEmpty(modelRoot.Description)) { Output("/// <remarks>"); WriteCommentBody(modelRoot.Description); Output("/// </remarks>"); } } else { Output("/// <inheritdoc/>"); } Output($"{modelRoot.EntityContainerAccess.ToString().ToLower()} partial class {modelRoot.EntityContainerName} : Microsoft.EntityFrameworkCore.DbContext"); Output("{"); PluralizationService pluralizationService = ModelRoot.PluralizationService; /***********************************************************************/ // generate DBSets /***********************************************************************/ ModelClass[] classesWithTables = null; switch (modelRoot.InheritanceStrategy) { case CodeStrategy.TablePerType: classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType).OrderBy(x => x.Name).ToArray(); break; case CodeStrategy.TablePerConcreteType: classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType && !mc.IsAbstract).OrderBy(x => x.Name).ToArray(); break; case CodeStrategy.TablePerHierarchy: classesWithTables = modelRoot.Classes.Where(mc => !mc.IsDependentType && mc.Superclass == null).OrderBy(x => x.Name).ToArray(); 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("/// <summary>"); WriteCommentBody($"Repository for {modelClass.FullName} - {modelClass.Summary}"); Output("/// </summary>"); } Output($"{modelRoot.DbSetAccess.ToString().ToLower()} virtual Microsoft.EntityFrameworkCore.DbSet<{modelClass.FullName}> {dbSetName} {{ get; set; }}"); } Output("#endregion DbSets"); NL(); } /***********************************************************************/ // constructors /***********************************************************************/ if (!string.IsNullOrEmpty(modelRoot.ConnectionString) || !string.IsNullOrEmpty(modelRoot.ConnectionStringName)) { string connectionString = string.IsNullOrEmpty(modelRoot.ConnectionString) ? $"Name={modelRoot.ConnectionStringName}" : modelRoot.ConnectionString; Output("/// <summary>"); Output("/// Default connection string"); Output("/// </summary>"); Output($"public static string ConnectionString {{ get; set; }} = @\"{connectionString}\";"); NL(); } //Output("/// <inheritdoc />"); //Output($"public {modelRoot.EntityContainerName}() : base()"); //Output("{"); //Output("}"); //NL(); Output("/// <inheritdoc />"); Output($"public {modelRoot.EntityContainerName}(DbContextOptions<{modelRoot.EntityContainerName}> options) : base(options)"); Output("{"); Output("}"); NL(); Output("partial void CustomInit(DbContextOptionsBuilder optionsBuilder);"); NL(); Output("/// <inheritdoc />"); Output("protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)"); Output("{"); segments.Clear(); if (modelRoot.GetEntityFrameworkPackageVersionNum() >= 2.1 && modelRoot.LazyLoadingEnabled) { segments.Add("UseLazyLoadingProxies()"); } if (segments.Any()) { segments.Insert(0, "optionsBuilder"); if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } NL(); } Output("CustomInit(optionsBuilder);"); Output("}"); NL(); Output("partial void OnModelCreatingImpl(ModelBuilder modelBuilder);"); Output("partial void OnModelCreatedImpl(ModelBuilder modelBuilder);"); NL(); /***********************************************************************/ // OnModelCreating /***********************************************************************/ Output("/// <inheritdoc />"); Output("protected override void OnModelCreating(ModelBuilder modelBuilder)"); Output("{"); Output("base.OnModelCreating(modelBuilder);"); Output("OnModelCreatingImpl(modelBuilder);"); NL(); Output($"modelBuilder.HasDefaultSchema(\"{modelRoot.DatabaseSchema}\");"); List <Association> visited = new List <Association>(); List <string> foreignKeyColumns = new List <string>(); foreach (ModelClass modelClass in modelRoot.Classes.OrderBy(x => x.Name)) { segments.Clear(); foreignKeyColumns.Clear(); NL(); // class level segments.Add($"modelBuilder.{(modelClass.IsDependentType ? "Owned" : "Entity")}<{modelClass.FullName}>()"); foreach (ModelAttribute transient in modelClass.Attributes.Where(x => !x.Persistent)) { segments.Add($"Ignore(t => t.{transient.Name})"); } if (!modelClass.IsDependentType) { // 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)) { segments.Add(modelClass.DatabaseSchema == modelClass.ModelRoot.DatabaseSchema ? $"ToTable(\"{modelClass.TableName}\")" : $"ToTable(\"{modelClass.TableName}\", \"{modelClass.DatabaseSchema}\")"); // primary key code segments must be output last, since HasKey returns a different type List <ModelAttribute> 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))} }})"); } } } if (segments.Count > 1 || modelClass.IsDependentType) { if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } if (modelClass.IsDependentType) { continue; } // indexed properties foreach (ModelAttribute indexed in modelClass.Attributes.Where(x => x.Indexed && !x.IsIdentity)) { segments.Clear(); segments.Add($"modelBuilder.Entity<{modelClass.FullName}>().HasIndex(t => t.{indexed.Name})"); if (indexed.IndexedUnique) { segments.Add("IsUnique()"); } 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({((int?)modelAttribute.MaxLength).Value})"); } if (modelAttribute.Required) { segments.Add("IsRequired()"); } if (modelAttribute.ColumnName != modelAttribute.Name && !string.IsNullOrEmpty(modelAttribute.ColumnName)) { segments.Add($"HasColumnName(\"{modelAttribute.ColumnName}\")"); } if (!modelAttribute.AutoProperty) { segments.Add($"HasField(\"_{modelAttribute.Name}\")"); segments.Add($"UsePropertyAccessMode(PropertyAccessMode.{(modelAttribute.PersistencePoint == PersistencePointType.Field ? "Field" : "Property")})"); } if (!string.IsNullOrEmpty(modelAttribute.ColumnType) && modelAttribute.ColumnType.ToLowerInvariant() != "default") { if (modelAttribute.ColumnType.ToLowerInvariant() == "varchar" || modelAttribute.ColumnType.ToLowerInvariant() == "nvarchar" || modelAttribute.ColumnType.ToLowerInvariant() == "char") { segments.Add($"HasColumnType(\"{modelAttribute.ColumnType}({(modelAttribute.MaxLength > 0 ? modelAttribute.MaxLength.ToString() : "max")})\")"); } else { segments.Add($"HasColumnType(\"{modelAttribute.ColumnType}\")"); } } if (modelAttribute.IsConcurrencyToken) { segments.Add("IsRowVersion()"); } if (modelAttribute.IsIdentity) { segments.Add(modelAttribute.IdentityType == IdentityType.AutoGenerated ? "ValueGeneratedOnAdd()" : "ValueGeneratedNever()"); } if (segments.Any()) { segments.Insert(0, $"modelBuilder.{(modelClass.IsDependentType ? "Owned" : "Entity")}<{modelClass.FullName}>()"); segments.Insert(1, $"Property(t => t.{modelAttribute.Name})"); if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } } bool hasDefinedConcurrencyToken = modelClass.AllAttributes.Any(x => x.IsConcurrencyToken); if (!hasDefinedConcurrencyToken && modelClass.EffectiveConcurrency == ConcurrencyOverride.Optimistic) { Output($@"modelBuilder.Entity<{modelClass.FullName}>().Property<byte[]>(""Timestamp"").IsConcurrencyToken();"); } // 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 // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) .OfType <UnidirectionalAssociation>() .Where(x => x.Persistent && !x.Target.IsDependentType)) { if (visited.Contains(association)) { continue; } visited.Add(association); segments.Clear(); segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); bool required = false; switch (association.TargetMultiplicity) // realized by property on source { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: // TODO: Implement many-to-many if (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); } else { continue; } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: segments.Add($"HasOne(x => x.{association.TargetPropertyName})"); break; case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"HasOne(x => x.{association.TargetPropertyName})"); break; //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: // segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); // break; } string columnPrefix = association.SourceRole == EndpointRole.Dependent ? "" : association.Target.Name + "_"; switch (association.SourceMultiplicity) // realized by shadow property on target { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: // TODO: Implement many-to-many if (association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { segments.Add("WithMany()"); segments.Add($"HasForeignKey(\"{columnPrefix}{association.TargetPropertyName}_Id\")"); } else { continue; } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: segments.Add("WithOne()"); segments.Add(association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany ? $"HasForeignKey<{association.Source.FullName}>(\"{columnPrefix}{association.TargetPropertyName}_Id\")" : $"HasForeignKey(\"{columnPrefix}{association.TargetPropertyName}_Id\")"); required = true; break; case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add("WithOne()"); segments.Add(association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany ? $"HasForeignKey<{association.Source.FullName}>(\"{columnPrefix}{association.TargetPropertyName}_Id\")" : $"HasForeignKey(\"{columnPrefix}{association.TargetPropertyName}_Id\")"); break; //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: // segments.Add("HasMany()"); // break; } if (required) { segments.Add("IsRequired()"); } if (association.TargetRole == EndpointRole.Principal || association.SourceRole == EndpointRole.Principal) { DeleteAction deleteAction = association.SourceRole == EndpointRole.Principal ? association.SourceDeleteAction : association.TargetDeleteAction; switch (deleteAction) { case DeleteAction.None: segments.Add("OnDelete(DeleteBehavior.Restrict)"); break; case DeleteAction.Cascade: segments.Add("OnDelete(DeleteBehavior.Cascade)"); break; } } if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(modelClass) .OfType <UnidirectionalAssociation>() .Where(x => x.Persistent && x.Target.IsDependentType)) { if (association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne || association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One) { Output($"modelBuilder.Entity<{modelClass.FullName}>().OwnsOne(x => x.{association.TargetPropertyName});"); } else { Output($"// Dependent 1-many association seen ({association.TargetPropertyName}). Code generation still unsupported in designer."); } } // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (BidirectionalAssociation association in Association.GetLinksToSources(modelClass) .OfType <BidirectionalAssociation>() .Where(x => x.Persistent)) { if (visited.Contains(association)) { continue; } visited.Add(association); // TODO: fix cascade delete bool required = false; segments.Clear(); segments.Add($"modelBuilder.Entity<{modelClass.FullName}>()"); switch (association.SourceMultiplicity) // realized by property on target { case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany: // TODO: Implement many-to-many if (association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { segments.Add($"HasMany(x => x.{association.SourcePropertyName})"); } else { continue; } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: segments.Add($"HasOne(x => x.{association.SourcePropertyName})"); break; case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"HasOne(x => x.{association.SourcePropertyName})"); break; //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: // TODO: Implement many-to-many if (association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany) { segments.Add($"WithMany(x => x.{association.TargetPropertyName})"); } else { continue; } break; case Sawczyn.EFDesigner.EFModel.Multiplicity.One: segments.Add($"WithOne(x => x.{association.TargetPropertyName})"); required = true; break; case Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroOne: segments.Add($"WithOne(x => x.{association.TargetPropertyName})"); break; //case Sawczyn.EFDesigner.EFModel.Multiplicity.OneMany: // segments.Add($"HasMany(x => x.{association.TargetPropertyName})"); // break; } string foreignKeySegment = CreateForeignKeyColumnSegmentEFCore(association , foreignKeyColumns , association.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany && association.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany); if (foreignKeySegment != null) { segments.Add(foreignKeySegment); } if (required) { segments.Add("IsRequired()"); } if (association.TargetRole == EndpointRole.Principal || association.SourceRole == EndpointRole.Principal) { DeleteAction deleteAction = association.SourceRole == EndpointRole.Principal ? association.SourceDeleteAction : association.TargetDeleteAction; switch (deleteAction) { case DeleteAction.None: segments.Add("OnDelete(DeleteBehavior.Restrict)"); break; case DeleteAction.Cascade: segments.Add("OnDelete(DeleteBehavior.Cascade)"); break; } } if (modelRoot.ChopMethodChains) { OutputChopped(segments); } else { Output(string.Join(".", segments) + ";"); } } } NL(); Output("OnModelCreatedImpl(modelBuilder);"); Output("}"); Output("}"); EndNamespace(modelRoot.Namespace); }