Пример #1
0
        private void WriteOnConfiguringEFCore(ModelRoot modelRoot, List <string> segments)
        {
            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();
        }
Пример #2
0
        public void GenerateEFCore(Manager manager, ModelRoot modelRoot)
        {
            if (modelRoot.EntityFrameworkVersion != EFVersion.EFCore)
            {
                throw new InvalidOperationException("Wrong generator selected");
            }

            EFModelGenerator generator;

            switch ((int)modelRoot.GetEntityFrameworkPackageVersionNum())
            {
            case 2:
                generator = new EFCore2ModelGenerator(this); break;

            case 3:
                generator = new EFCore3ModelGenerator(this); break;

            default:
                generator = new EFCore5ModelGenerator(this); break;
            }

            generator.Generate(manager);
        }
Пример #3
0
// 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);
        }