<# // Copyright (C) Simon Hughes 2012 // v3.9.0 // If you want to submit a pull request, please modify the Generator C# project as this file // is automatically constructed from the C# Generator project during the build process. #> <#@ template debug="true" hostspecific="true" language="C#" #> <#@ include file="EF6.Utility.CS.ttinclude"#><#@ assembly name="System.Configuration" #> <#@ assembly name="System.Windows.Forms" #> <#@ import namespace="System.Data.Entity.Infrastructure.Pluralization" #> <#@ import namespace="System" #> <#@ import namespace="System.Collections" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.ComponentModel" #> <#@ import namespace="System.Data" #> <#@ import namespace="System.Data.Common" #> <#@ import namespace="System.Data.SqlClient" #> <#@ import namespace="System.Diagnostics" #> <#@ import namespace="System.Globalization" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Reflection" #> <#@ import namespace="System.Security" #> <#@ import namespace="System.Security.Cryptography" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Text.RegularExpressions" #> <#@ import namespace="System.Threading" #> <#@ import namespace="EnvDTE" #> <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #> <#@ output extension=".cs" encoding="utf-8" #> <# // WriteLine("// T4 framework version = " + AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName); var DefaultNamespace = new CodeGenerationTools(this).VsNamespaceSuggestion() ?? "DebugMode"; Settings.Root = Host.ResolvePath(string.Empty); Settings.TemplateFile = Path.GetFileNameWithoutExtension(DynamicTextTransformation.Create(this).Host.TemplateFile); // System.Diagnostics.Debugger.Launch(); #><#+ public static class Settings { // Main settings ********************************************************************************************************************** // The following entries are the only required settings. public static DatabaseType DatabaseType = DatabaseType.SqlServer; // SqlServer, SqlCe, SQLite, PostgreSQL. Coming next: MySql, Oracle public static TemplateType TemplateType = TemplateType.EfCore8; // EfCore8, EfCore7, EfCore6, EfCore3, Ef6, FileBasedCore3-8. FileBased specify folder using Settings.TemplateFolder public static GeneratorType GeneratorType = GeneratorType.EfCore; // EfCore, Ef6, Custom. Custom edit GeneratorCustom class to provide your own implementation public static ForeignKeyNamingStrategy ForeignKeyNamingStrategy = ForeignKeyNamingStrategy.Legacy; // Please use Legacy for now, Latest (not yet ready) public static bool UseMappingTables = false; // Can only be set to true for EF6. If true, mapping will be used and no mapping tables will be generated. If false, all tables will be generated. public static FileManagerType FileManagerType = FileManagerType.EfCore; // .NET Core project = EfCore; .NET 4.x project = VisualStudio; No output (testing only) = Null public static string ConnectionString = ""; // This is used by the generator to reverse engineer your database public static string ConnectionStringName = "MyDbContext"; // ConnectionString key as specified in your app.config/web.config/appsettings.json public static string DbContextName = "MyDbContext"; // Class name for the DbContext to be generated. public static bool GenerateSeparateFiles = false; public static string Namespace = typeof(Settings).Namespace; // Override the default namespace here. Example: Namespace = "CustomNamespace"; public static string TemplateFolder = ""; // Only used if Settings.TemplateType = TemplateType.FileBased. Specify folder name where the mustache folders can be found. Please read https://github.com/sjh37/EntityFramework-Reverse-POCO-Code-First-Generator/wiki/Custom-file-based-templates public static bool AddUnitTestingDbContext = true; // Will add a FakeDbContext and FakeDbSet for easy unit testing // Elements to generate *************************************************************************************************************** // Add the elements that should be generated when the template is executed. // Multiple projects can now be used that separate the different concerns. public static Elements ElementsToGenerate = Elements.Poco | Elements.Context | Elements.Interface | Elements.PocoConfiguration | Elements.Enum; // Generate files in sub-folders ****************************************************************************************************** // Only activated if Settings.FileManagerType = FileManagerType.EfCore && Settings.GenerateSeparateFiles = true public static string ContextFolder = ""; // Sub-folder you would like your DbContext to be added to. e.g. @"Data" public static string InterfaceFolder = ""; // Sub-folder you would like your Interface to be added to. e.g. @"Data\Interface" public static string PocoFolder = ""; // Sub-folder you would like your Poco's to be added to. e.g. @"Data\Entities" public static string PocoConfigurationFolder = ""; // Sub-folder you would like your Configuration mappings to be added to. e.g. @"Data\Configuration" public static int CommandTimeout = 600; // SQL Command timeout in seconds. 600 is 10 minutes, 0 will wait indefinitely. Some databases can be slow retrieving schema information. public static string DbContextInterfaceBaseClasses = "IDisposable"; // Specify what the base classes are for your database context interface public static string DbContextBaseClass = "DbContext"; // Specify what the base class is for your DbContext. For ASP.NET Identity use "IdentityDbContext"; public static OnConfiguration OnConfiguration = OnConfiguration.ConnectionString; // EFCore only. Determines the code generated within DbContext.OnConfiguration(). Please read https://github.com/sjh37/EntityFramework-Reverse-POCO-Code-First-Generator/wiki/Settings.OnConfiguration public static bool AddParameterlessConstructorToDbContext = true; // If true, then DbContext will have a default (parameter-less) constructor which automatically passes in the connection string name, if false then no parameter-less constructor will be created. public static string ConfigurationClassName = "Configuration"; // Configuration, Mapping, Map, etc. This is appended to the Poco class name to configure the mappings. public static string DatabaseReaderPlugin = ""; // Eg, "c:\\Path\\YourDatabaseReader.dll,Full.Name.Of.Class.Including.Namespace". See #501. This will allow you to specify a pluggable provider for reading your database. public static string EntityClassesModifiers = "public"; // "public partial"; public static string ConfigurationClassesModifiers = "public"; // "public partial"; public static string DbContextClassModifiers = "public"; // "public partial"; public static string DbContextInterfaceModifiers = "public"; // "public partial"; public static string ResultClassModifiers = "public"; // "public partial"; public static bool UsePascalCase = true; // This will rename the generated C# tables & properties to use PascalCase. If false table & property names will be left alone. public static bool UsePascalCaseForEnumMembers = true; // This will rename the generated Enum Members to use PascalCase. If false Enum members will be left alone. public static bool UseDataAnnotations = false; // If true, will add data annotations to the poco classes. public static bool UsePropertyInitialisers = false; // Removes POCO constructor and instead uses C# 6 property initialisers to set defaults public static bool UseLazyLoading = true; // Marks all navigation properties as virtual or not, to support or disable EF Lazy Loading feature public static bool UseInheritedBaseInterfaceFunctions = false; // If true, the main DBContext interface functions will come from the DBContextInterfaceBaseClasses and not generated. If false, the functions will be generated. public static CommentsStyle IncludeComments = CommentsStyle.AtEndOfField; // Adds comments to the generated code public static CommentsStyle IncludeExtendedPropertyComments = CommentsStyle.InSummaryBlock; // Adds extended properties as comments to the generated code public static bool DisableGeographyTypes = false; // Turns off use of System.Data.Entity.Spatial.DbGeography and System.Data.Entity.Spatial.DbGeometry as OData doesn't support entities with geometry/geography types. public static string CollectionInterfaceType = "ICollection"; // = "System.Collections.Generic.List"; // Determines the declaration type of collections for the Navigation Properties. ICollection is used if not set. public static string CollectionType = "List"; // Determines the type of collection for the Navigation Properties. "ObservableCollection" for example. Add "System.Collections.ObjectModel" to AdditionalNamespaces if setting the CollectionType = "ObservableCollection". public static bool NullableShortHand = true; // true => T?, false => Nullable public static bool AddIDbContextFactory = true; // Will add a default IDbContextFactory implementation for easy dependency injection public static bool IncludeQueryTraceOn9481Flag = false; // If SqlServer 2014 appears frozen / take a long time when this file is saved, try setting this to true (you will also need elevated privileges). public static bool UsePrivateSetterForComputedColumns = true; // If the columns is computed, use a private setter. public static bool IncludeGeneratorVersionInCode = false; // If true, will include the version number of the generator in the generated code (Settings.ShowLicenseInfo must also be true). public static bool TrimCharFields = false; // EF Core option only. If true, will TrimEnd() 'char' fields when read from the database. public static bool IncludeFieldNameConstants = false; // Will include public const string {{NameHumanCase}}Field = "{{NameHumanCase}}"; in the generated POCO class. It allows you to use a constant instead of magic strings. public static bool UsePropertiesForStoredProcResultSets = false; // Stored procedure result set return models are rendered as fields (false) or properties (true). public static bool MergeMultipleStoredProcModelsIfAllSame = true; // Some stored procedures are reported as having multiple result sets when in fact there is only one. Set this to true to merge identical result sets. public static List AdditionalNamespaces = new List(); // To include extra namespaces, include them here. i.e. "Microsoft.AspNet.Identity.EntityFramework" public static List AdditionalContextInterfaceItems = new List(); // example: "void SetAutoDetectChangesEnabled(bool flag);" public static List AdditionalFileHeaderText = new List(); // This will put additional lines verbatim at the top of each file under the comments, 1 line per entry public static List AdditionalFileFooterText = new List(); // This will put additional lines verbatim at the end of each file above the // , 1 line per entry // Language choices public static GenerationLanguage GenerationLanguage = GenerationLanguage.CSharp; public static string FileExtension = ".cs"; // Code suppression ******************************************************************************* public static bool UseRegions = true; // If false, suppresses the use of #region public static bool UseNamespace = true; // If false, suppresses the writing of a namespace public static bool UsePragma = false; // If false, suppresses the writing of #pragma public static bool AllowNullStrings = false; // If true, will allow string? properties and will add '#nullable enable' to the top of each file public static bool UseResharper = false; // If true, will add a list of 'ReSharper disable' comments to the top of each file public static bool ShowLicenseInfo = false; // If true, will add the licence info comment to the top of each file public static bool IncludeConnectionSettingComments = false; // Add comments describing connection settings used to generate file public static bool IncludeCodeGeneratedAttribute = false; // If true, will include the [GeneratedCode] attribute before classes, false to remove it. public static bool IncludeColumnsWithDefaults = true; // If true, will set properties to the default value from the database.ro // Create enumerations from database tables // List the enumeration tables you want read and generated for // Also look at the AddEnum callback below to add your own during reverse generation of tables. public static List Enumerations = new List { // Example /*new EnumerationSettings { Name = "DaysOfWeek", // Enum to generate. e.g. "DaysOfWeek" would result in "public enum DaysOfWeek {...}" if the GroupField is set to a value then {GroupField} must be used in this name. e.g. "DaysOfWeek{GroupField}" Table = "EnumTest.DaysOfWeek", // Database table containing enum values. e.g. "DaysOfWeek" NameField = "TypeName", // Column containing the name for the enum. e.g. "TypeName" ValueField = "TypeId", // Column containing the values for the enum. e.g. "TypeId" GroupField = string.Empty // [optional] Column containing the group name for the enum. This is used if multiple Enums are in the same table. if this is populated, use {GroupField} in the Name property. e.g. "{GroupField}Enum" }, new EnumerationSettings { Name = "CarOptions", Table = "car_options", NameField = "enum_name", ValueField = "value" }*/ // Code will be generated as: // public enum Name // { // NameField = ValueField, // etc // } }; // Use the following list to add use of HiLo sequences for identity columns public static List HiLoSequences = new List { // Example // For column dbo.match_table_name.match_column_name this will use .UseHiLo("sequence_name", "sequence_schema") instead of .UseSqlServerIdentityColumn() /*new HiLoSequence { Schema = "dbo", Table = "match_table_name", Column = "match_column_name", SequenceName = "sequence_name", SequenceSchema = "sequence_schema" }, new HiLoSequence { Schema = "dbo", Table = "Employees", Column = "EmployeeID", SequenceName = "EmployeeSequence", SequenceSchema = "dbo" }*/ }; // If you need to serialise your entities with the JsonSerializer from Newtonsoft, this would serialise // all properties including the Reverse Navigation and Foreign Keys. The simplest way to exclude them is // to use the data annotation [JsonIgnore] on reverse navigation and foreign keys. // For more control, take a look at ForeignKeyAnnotationsProcessing() further down public static string[] AdditionalReverseNavigationsDataAnnotations = new string[] { // "JsonIgnore" // Also add "Newtonsoft.Json" to the AdditionalNamespaces array above }; public static string[] AdditionalForeignKeysDataAnnotations = new string[] { // "JsonIgnore" // Also add "Newtonsoft.Json" to the AdditionalNamespaces array above }; // Reference other namespaces ********************************************************************************************************* // Use these namespaces to specify where the different elements now live. These may even be in different assemblies. // NOTE: These are only used if ElementsToGenerate is not set to generate all the elements. // Please note the following does not create the files in these locations, it only adds a using statement to say where they are. // The way to generate files in other folders is to either specify sub-folders above, or add the "EntityFramework Reverse POCO Code First Generator" // into each of the folders you require, then set your .tt files to only generate the relevant section(s) you need by setting: // ElementsToGenerate = Elements.Poco; in your Entity folder, // ElementsToGenerate = Elements.Context | Elements.Interface; in your Context folder, // ElementsToGenerate = Elements.PocoConfiguration; in your Configuration folder. // You also need to set the following to the namespace where they now live: public static string ContextNamespace = ""; // "YourProject.Data"; public static string InterfaceNamespace = ""; // "YourProject.Data"; public static string PocoNamespace = ""; // "YourProject.Data.Entities"; public static string PocoConfigurationNamespace = ""; // "YourProject.Data.Configuration"; // Schema ***************************************************************************************************************************** // If there are multiple schemas, then the table name is prefixed with the schema, except for dbo. // Ie. dbo.hello will be Hello. // abc.hello will be Abc_Hello. public static bool PrependSchemaName = true; // Control if the schema name is prepended to the table name public static string DefaultSchema = null; // Set via DatabaseReader.DefaultSchema() // Table Suffix *********************************************************************************************************************** // Appends the suffix to the generated classes names // Ie. If TableSuffix is "Dto" then Order will be OrderDto // If TableSuffix is "Entity" then Order will be OrderEntity public static string TableSuffix = null; // AddRelationship is a helper function that creates ForeignKey objects and adds them to the foreignKeys list public static Action, Tables> AddExtraForeignKeys = delegate (IDbContextFilter filter, Generator gen, List foreignKeys, Tables tablesAndViews) { // Northwind example: // [Orders] (Table) to [Invoices] (View) is one-to-many using Orders.OrderID = Invoices.OrderID // gen.AddRelationship(filter, foreignKeys, tablesAndViews, "orders_to_invoices", Settings.DefaultSchema, "Orders", "OrderID", "dbo", "Invoices", "OrderID", null, null, true); // [Orders] (Table) to [Orders Qry] (View) is one-to-zeroOrOne ( [Orders].OrderID = [Orders Qry].OrderID ) // gen.AddRelationship(filter, foreignKeys, tablesAndViews, "orders_to_ordersQry", Settings.DefaultSchema, "Orders", "OrderID", "dbo", "Orders Qry", "OrderID", "ParentFkName", "ChildFkName", true); // [Order Details] (Table) to [Invoices] (View) is one-to-zeroOrOne - but uses a composite-key: ( [Order Details].OrderID,ProductID = [Invoices].OrderID,ProductID ) // gen.AddRelationship(filter, foreignKeys, tablesAndViews, "orderDetails_to_invoices", Settings.DefaultSchema, "Order Details", new[] { "OrderID", "ProductID" }, "dbo", "Invoices", new[] { "OrderID", "ProductID" }, null, null, true); }; // StoredProcedure return types ******************************************************************************************************* // Override generation of return models for stored procedures that return entities. // If a stored procedure returns an entity, add it to the list below. // This will suppress the generation of the return model, and instead return the entity. // Example: Proc name Return this entity type instead //StoredProcedureReturnTypes.Add("SalesByYear", "SummaryOfSalesByYear"); public static Dictionary StoredProcedureReturnTypes = new Dictionary(); // Renaming *********************************************************************************************************************** // Table renaming (single context generation only) ************************************************************************************ // Use the following function to rename tables such as tblOrders to Orders, Shipments_AB to Shipments, etc. // Example: public static Func TableRename = delegate (string name, string schema, bool isView) { // Example //if (name.StartsWith("tbl")) // name = name.Remove(0, 3); //name = name.Replace("_AB", ""); //if(isView) // name = name + "View"; // If you turn pascal casing off (UsePascalCase = false), and use the pluralisation service, and some of your // tables names are all UPPERCASE, some words ending in IES such as CATEGORIES get singularised as CATEGORy. // Therefore you can make them lowercase by using the following //return Inflector.MakeLowerIfAllCaps(name); // If you are using the pluralisation service and you want to rename a table, make sure you rename the table to the plural form. // For example, if the table is called Treez (with a z), and your pluralisation entry is // new CustomPluralizationEntry("Tree", "Trees") // Use this TableRename function to rename Treez to the plural (not singular) form, Trees: //if (name == "Treez") return "Trees"; return name; }; // Use the following function if you need to apply additional modifications to a table // Called just before UpdateColumn public static Action UpdateTable = delegate (Table table) { // Add an extra comment //if (table.NameHumanCase.Equals("SomeTable", StringComparison.InvariantCultureIgnoreCase)) // table.AdditionalComment = "Example comment"; // To add a base class to a table //if (table.NameHumanCase == "User") // table.BaseClasses = " : IdentityUser"; //if (table.NameHumanCase == "LogData" || table.NameHumanCase == "ReturnData" || table.NameHumanCase == "DataBlob") // table.BaseClasses = " : ReportData"; // To add attributes //table.Attributes.Add("[Serializable]"); }; // Use the following function if you need to apply additional modifications to a column // eg. normalise names etc. public static Action> UpdateColumn = delegate (Column column, Table table, List enumDefinitions) { // Rename column //if (column.IsPrimaryKey && column.NameHumanCase == "PkId") // column.NameHumanCase = "Id"; // .IsConcurrencyToken() must be manually configured. However .IsRowVersion() can be automatically detected. //if (table.NameHumanCase.Equals("SomeTable", StringComparison.InvariantCultureIgnoreCase) && column.NameHumanCase.Equals("SomeColumn", StringComparison.InvariantCultureIgnoreCase)) // column.IsConcurrencyToken = true; // Remove table name from primary key //if (column.IsPrimaryKey && column.NameHumanCase.Equals(table.NameHumanCase + "Id", StringComparison.InvariantCultureIgnoreCase)) // column.NameHumanCase = "Id"; // Remove column from poco class as it will be inherited from a base class //if (column.IsPrimaryKey && // ( // table.NameHumanCase.Equals("LogData", StringComparison.InvariantCultureIgnoreCase) || // table.NameHumanCase.Equals("ReturnData", StringComparison.InvariantCultureIgnoreCase) || // table.NameHumanCase.Equals("DataBlob", StringComparison.InvariantCultureIgnoreCase) // )) // column.ExistsInBaseClass = true; // If true, does not generate the property for this column as it will exist in a base class // Use the extended properties to perform tasks to column //if (column.ExtendedProperty == "HIDE") // column.Hidden = true; // Hidden means the generator does not generate any code for this column at all. // Apply the "override" access modifier to a specific column. //if (column.NameHumanCase == "id") // column.OverrideModifier = true; // This will create: public override long id { get; set; } // Perform Enum property type replacement var enumDefinition = enumDefinitions?.FirstOrDefault(e => (e.Schema.Equals(table.Schema.DbName, StringComparison.InvariantCultureIgnoreCase)) && (e.Table == "*" || e.Table.Equals(table.DbName, StringComparison.InvariantCultureIgnoreCase) || e.Table.Equals(table.NameHumanCase, StringComparison.InvariantCultureIgnoreCase)) && (e.Column.Equals(column.DbName, StringComparison.InvariantCultureIgnoreCase) || e.Column.Equals(column.NameHumanCase, StringComparison.InvariantCultureIgnoreCase))); if (enumDefinition != null) { column.PropertyType = enumDefinition.EnumType; if (!string.IsNullOrEmpty(column.Default)) column.Default = "(" + enumDefinition.EnumType + ") " + column.Default; } }; // Configures the key property to either use IDENTITY or HILO database feature to generate values for new entities. public static Func ColumnIdentity = delegate (Column c) { if(!IsEfCore3Plus()) return ".UseSqlServerIdentityColumn()"; // At this point we are using EFCore 3, 5+ which supports HiLo sequences. // Example of using a HiLo sequence using this function. /*if (c.ParentTable.NameHumanCase.Equals("SomeTable", StringComparison.InvariantCultureIgnoreCase) && c.NameHumanCase .Equals("SomeColumn", StringComparison.InvariantCultureIgnoreCase)) { return ".UseHiLo(\"your_sequence_name\",\"your_sequence_schema\")"; }*/ var hiLoSequence = HiLoSequences?.FirstOrDefault(x => x.Schema.Equals(c.ParentTable.Schema.DbName, StringComparison.InvariantCultureIgnoreCase) && (x.Table == "*" || x.Table.Equals(c.ParentTable.DbName, StringComparison.InvariantCultureIgnoreCase))); if (hiLoSequence != null) return string.Format(".UseHiLo(\"{0}\", \"{1}\")", hiLoSequence.SequenceName, hiLoSequence.SequenceSchema); return ".UseIdentityColumn()"; }; // In order to use this function, Settings.ElementsToGenerate must contain both Elements.Poco and Elements.Enum; public static Action
AddEnum = delegate (Table table) { /*if (table.HasPrimaryKey && table.PrimaryKeys.Count() == 1 && table.Columns.Any(x => x.PropertyType == "string")) { // Example: choosing tables with certain naming conventions for enums. Please use your own conventions. if (table.NameHumanCase.StartsWith("Enum", StringComparison.InvariantCultureIgnoreCase) || table.NameHumanCase.EndsWith ("Enum", StringComparison.InvariantCultureIgnoreCase)) { try { Enumerations.Add(new EnumerationSettings { Name = table.NameHumanCase.Replace("Enum","").Replace("Enum","") + "Enum", Table = table.Schema.DbName + "." + table.DbName, NameField = table.Columns.First(x => x.PropertyType == "string").DbName, // Or specify your own ValueField = table.PrimaryKeys.Single().DbName, // Or specify your own GroupField = string.Empty // Or specify your own }); // This will cause this table to not be reverse-engineered. // This means it was only required to generate an enum and can now be removed. table.RemoveTable = true; // Remove this line if you want to keep it in your dbContext. } catch { // Swallow exception } } }*/ }; // Use the following function if you need to apply additional modifications to a enum // Called just before UpdateEnumMember public static Action UpdateEnum = delegate (Enumeration enumeration) { //enumeration.EnumAttributes.Add("[DataContract]"); }; // Use the following function if you need to apply additional modifications to a enum member public static Action UpdateEnumMember = delegate (EnumerationMember enumerationMember) { //enumerationMember.Attributes.Add("[EnumMember]"); //enumerationMember.Attributes.Add("[SomeAttribute(\"" + enumerationMember.AllValues["SomeName"] + " \")]"); }; // Writes any boilerplate stuff inside the POCO class body public static Func WriteInsideClassBody = delegate (Table t) { // Example: //return " // " + t.NameHumanCase + Environment.NewLine; // Do nothing by default return string.Empty; }; // Using Views ***************************************************************************************************************** // SQL Server does not support the declaration of primary-keys in VIEWs. Entity Framework's EDMX designer (and this generator) // assume that all non-null columns in a VIEW are primary-key columns, this will be incorrect for most non-trivial applications. // This callback will be invoked for each VIEW found in the database. Use it to declare which columns participate in that VIEW's // primary-key by setting 'IsPrimaryKey = true'. // If no columns are marked with 'IsPrimaryKey = true' then this T4 template defaults to marking all non-NULL columns as primary key columns. // To set-up Foreign-Key relationships between VIEWs and Tables (or even other VIEWs) use the 'AddForeignKeys' callback below. public static Action
ViewProcessing = delegate (Table view) { // Below is example code for the Northwind database that configures the 'VIEW [Orders Qry]' and 'VIEW [Invoices]' /*switch (view.DbName) { case "Orders Qry": // VIEW [Orders Qry] uniquely identifies rows with the 'OrderID' column: view.Columns.Single(col => col.DbName == "OrderID").IsPrimaryKey = true; break; case "Invoices": // VIEW [Invoices] has a composite primary key (OrderID+ProductID), so both columns must be marked as a Primary Key: foreach (var col in view.Columns.Where(c => c.DbName == "OrderID" || c.DbName == "ProductID")) col.IsPrimaryKey = true; break; }*/ }; // StoredProcedure renaming ************************************************************************************************************ // Use the following function to rename stored procs such as sp_CreateOrderHistory to CreateOrderHistory, my_sp_shipments to Shipments, etc. public static Func StoredProcedureRename = delegate (StoredProcedure sp) { // Example: //if (sp.NameHumanCase.StartsWith("sp_")) // return sp.NameHumanCase.Remove(0, 3); //return sp.NameHumanCase.Replace("my_sp_", ""); return sp.NameHumanCase; // Do nothing by default }; // Use the following function to rename the return model automatically generated for stored procedure. // By default it's ReturnModel. public static Func StoredProcedureReturnModelRename = delegate (string name, StoredProcedure sp) { // Example: //if (sp.NameHumanCase.Equals("ComputeValuesForDate", StringComparison.InvariantCultureIgnoreCase)) // return "ValueSet"; //if (sp.NameHumanCase.Equals("SalesByYear", StringComparison.InvariantCultureIgnoreCase)) // return "SalesSet"; return name; // Do nothing by default }; // Mapping Table renaming ********************************************************************************************************************* // By default, name of the properties created relate to the table the foreign key points to and not the mapping table. // Use the following function to rename the properties created by ManyToMany relationship tables especially if you have 2 relationships between the same tables. // Example: public static Func MappingTableRename = delegate (string mappingTable, string tableName, string entityName) { // Example: If you have two mapping tables such as one being UserRequiredSkills snd one being UserOptionalSkills, this would change the name of one property //if (mappingTable == "UserRequiredSkills" && tableName == "User") // return "RequiredSkills"; // or if you want to give the same property name on both classes //if (mappingTable == "UserRequiredSkills") // return "UserRequiredSkills"; return entityName; }; public static Func ForeignKeyName = delegate (string tableName, ForeignKey foreignKey, string foreignKeyName, Relationship relationship, short attempt) { string fkName; // 5 Attempts to correctly name the foreign key switch (attempt) { case 1: // Try without appending foreign key name fkName = tableName; break; case 2: // Only called if foreign key name ends with "id" // Use foreign key name without "id" at end of string fkName = foreignKeyName.Remove(foreignKeyName.Length - 2, 2); break; case 3: // Use foreign key name only fkName = foreignKeyName; break; case 4: // Use table name and foreign key name fkName = tableName + "_" + foreignKeyName; break; case 5: // Used in for loop 1 to 99 to append a number to the end fkName = tableName; break; default: // Give up fkName = tableName; break; } // Apply custom foreign key renaming rules. Can be useful in applying pluralization. // For example: /*if (tableName == "Employee" && foreignKey.FkColumn == "ReportsTo") return "Manager"; if (tableName == "Territories" && foreignKey.FkTableName == "EmployeeTerritories") return "Locations"; if (tableName == "Employee" && foreignKey.FkTableName == "Orders" && foreignKey.FkColumn == "EmployeeID") return "ContactPerson"; */ // FK_TableName_FromThisToParentRelationshipName_FromParentToThisChildsRelationshipName // (e.g. FK_CustomerAddress_Customer_Addresses will extract navigation properties "address.Customer" and "customer.Addresses") // Feel free to use and change the following /*if (foreignKey.ConstraintName.StartsWith("FK_") && foreignKey.ConstraintName.Count(x => x == '_') == 3) { var parts = foreignKey.ConstraintName.Split('_'); if (!string.IsNullOrWhiteSpace(parts[2]) && !string.IsNullOrWhiteSpace(parts[3]) && parts[1] == foreignKey.FkTableName) { if (relationship == Relationship.OneToMany) fkName = parts[3]; else if (relationship == Relationship.ManyToOne) fkName = parts[2]; } }*/ return fkName; }; // This foreign key filter used in addition to SingleContextFilter.ForeignKeyFilter() // Return null to exclude this foreign key public static Func ForeignKeyFilterFunc = delegate (ForeignKey fk) { // Return null to exclude this foreign key, or set IncludeReverseNavigation = false // to include the foreign key but not generate reverse navigation properties. // Example, to exclude all foreign keys for the Categories table, use: //if (fk.PkTableName == "Categories") // return null; // Example, to exclude reverse navigation properties for tables ending with Type, use: //if (fk.PkTableName.EndsWith("Type")) // fk.IncludeReverseNavigation = false; // You can also change the access modifier of the foreign-key's navigation property: //if(fk.PkTableName == "Categories") // fk.AccessModifier = "internal"; return fk; }; public static Func ForeignKeyAnnotationsProcessing = delegate (Table fkTable, Table pkTable, string propName, string fkPropName) { // Example: // Each navigation property that is a reference to User are left intact //if(pkTable.NameHumanCase.Equals("User") && propName.Equals("User")) // return null; // all the others are marked with this attribute //return new[] { "System.Runtime.Serialization.IgnoreDataMember" }; return null; }; // Column modification **************************************************************************************************************** // Use the following list to replace column byte types with Enums. // As long as the type can be mapped to your new type, all is well. public static Action> AddEnumDefinitions = delegate (List enumDefinitions) { // Examples: //enumDefinitions.Add(new EnumDefinition { Schema = Settings.DefaultSchema, Table = "match_table_name", Column = "match_column_name", EnumType = "name_of_enum" }); // This will replace OrderHeader.OrderStatus type to be an OrderStatusType enum //enumDefinitions.Add(new EnumDefinition { Schema = Settings.DefaultSchema, Table = "OrderHeader", Column = "OrderStatus", EnumType = "OrderStatusType" }); // This will replace any table *.OrderStatus type to be an OrderStatusType enum //enumDefinitions.Add(new EnumDefinition { Schema = Settings.DefaultSchema, Table = "*", Column = "OrderStatus", EnumType = "OrderStatusType" }); }; // Generate multiple db contexts in a single go *************************************************************************************** // Generating multiple contexts at a time requires you specifying which tables, and columns to generate for each context. // As this generator can now generate multiple db contexts in a single go, filtering is done a per db context, and no longer global. // If GenerateSingleDbContext = true (default), please modify SingleContextFilter, this is where your previous global settings should go. // If GenerateSingleDbContext = false, this will generate multiple db contexts. Please read https://github.com/sjh37/EntityFramework-Reverse-POCO-Code-First-Generator/wiki/Generating-multiple-database-contexts-in-a-single-go public static bool GenerateSingleDbContext = true; public static string MultiContextSettingsConnectionString = ""; // Leave empty to read data from same database in ConnectionString above. If settings are in another database, specify the connection string here. public static string MultiContextSettingsPlugin = ""; // Only used for unit testing Generator project as you can't (yet) inherit from IMultiContextSettingsPlugin. "c:\\Path\\YourMultiDbSettingsReader.dll,Full.Name.Of.Class.Including.Namespace". This will allow you to specify a pluggable provider for reading your MultiContext settings. public static char MultiContextAttributeDelimiter = '~'; // The delimiter used for splitting MultiContext attributes public static Action> MultiContextAllFieldsColumnProcessing = delegate (Column column, Table table, Dictionary allFields) { // Examples of how to use additional custom fields from the MultiContext.[Column] table // INT example /*if (allFields.ContainsKey("DummyInt")) { var o = allFields["DummyInt"]; column.ExtendedProperty += string.Format(" DummyInt = {0}", (int) o); }*/ // VARCHAR example /*if (allFields.ContainsKey("Test")) { var o = allFields["Test"]; column.ExtendedProperty += string.Format(" Test = {0}", o.ToString()); }*/ // DATETIME example /*if (allFields.ContainsKey("date_of_birth")) { var o = allFields["date_of_birth"]; var date = Convert.ToDateTime(o); column.ExtendedProperty += string.Format(" date_of_birth = {0}", date.ToLongDateString()); }*/ }; public static Action> MultiContextAllFieldsTableProcessing = delegate (Table table, Dictionary allFields) { // Examples of how to use additional custom fields from the MultiContext.[Table] table // VARCHAR example /*if (allFields.ContainsKey("Notes")) { var o = allFields["Notes"]; if (string.IsNullOrEmpty(table.AdditionalComment)) table.AdditionalComment = string.Empty; table.AdditionalComment += string.Format(" Test = {0}", o.ToString()); }*/ }; public static Action> MultiContextAllFieldsStoredProcedureProcessing = delegate (StoredProcedure sp, Dictionary allFields) { // Examples of how to use additional custom fields from the MultiContext.[Table] table // VARCHAR example /*if (allFields.ContainsKey("CustomRename")) { var o = allFields["CustomRename"]; sp.NameHumanCase = o.ToString(); }*/ }; public static Action> MultiContextAllFieldsFunctionProcessing = delegate (StoredProcedure sp, Dictionary allFields) { // Examples of how to use additional custom fields from the MultiContext.[Table] table // VARCHAR example /*if (allFields.ContainsKey("CustomRename")) { var o = allFields["CustomRename"]; sp.NameHumanCase = o.ToString(); }*/ }; // Helper functions *************************************************************************************************************** public static bool DbContextClassIsPartial() { return DbContextClassModifiers != null && DbContextClassModifiers.Contains("partial"); } public static bool EntityClassesArePartial() { return EntityClassesModifiers != null && EntityClassesModifiers.Contains("partial"); } public static bool ConfigurationClassesArePartial() { return ConfigurationClassesModifiers != null && ConfigurationClassesModifiers.Contains("partial"); } private static string _dbContextInterfaceName; public static string DbContextInterfaceName { get { return _dbContextInterfaceName ?? ("I" + DbContextName); } set { _dbContextInterfaceName = value; } } private static bool _explicitDefaultConstructorArgument; private static string _defaultConstructorArgument; public static string DefaultConstructorArgument { // = null; // Defaults to "Name=" + ConnectionStringName, use null in order not to call the base constructor get { return _explicitDefaultConstructorArgument ? _defaultConstructorArgument : string.Format('"' + "Name={0}" + '"', ConnectionStringName); } set { _explicitDefaultConstructorArgument = true; _defaultConstructorArgument = value; } } // Don't forget to take a look at SingleContextFilter and FilterSettings classes! // That's it, nothing else to configure *********************************************************************************************** public static bool IsEf6() => TemplateType == TemplateType.Ef6; public static bool IsEfCore3Plus() => EfCoreVersion() >= 3; public static bool IsEfCore6Plus() => EfCoreVersion() >= 6; public static bool IsEfCore7Plus() => EfCoreVersion() >= 7; public static bool IsEfCore8Plus() => EfCoreVersion() >= 8; public static int EfCoreVersion() { switch (TemplateType) { case TemplateType.EfCore3: case TemplateType.FileBasedCore3: return 3; case TemplateType.EfCore6: case TemplateType.FileBasedCore6: return 6; case TemplateType.EfCore7: case TemplateType.FileBasedCore7: return 7; case TemplateType.EfCore8: case TemplateType.FileBasedCore8: return 8; case TemplateType.Ef6: case TemplateType.FileBasedEf6: default: return 0; } } public static string DatabaseProvider() { switch (DatabaseType) { case DatabaseType.PostgreSQL: return "UseNpgsql"; case DatabaseType.MySql: return "UseMySql"; case DatabaseType.Oracle: return "UseOracle"; case DatabaseType.SQLite: return "UseSqlite"; default: return "UseSqlServer"; } } public static string SqlParameter() { switch (DatabaseType) { case DatabaseType.PostgreSQL: return "NpgsqlParameter"; case DatabaseType.MySql: return "MySqlParameter"; case DatabaseType.Oracle: return "OracleParameter"; case DatabaseType.SQLite: return "SqliteParameter"; default: return "SqlParameter"; } } public static string SqlParameterValue() { switch (DatabaseType) { case DatabaseType.PostgreSQL: return "NpgsqlValue"; case DatabaseType.MySql: return "Value"; case DatabaseType.Oracle: return "Value"; case DatabaseType.SQLite: return "Value"; default: return "SqlValue"; } } public static string Root; public static string TemplateFile; public static int FilterCount; } // Filtering ************************************************************************************************************************** // These settings are only used by the single context filter SingleContextFilter (Settings.GenerateSingleDbContext = true) // Please read https://github.com/sjh37/EntityFramework-Reverse-POCO-Code-First-Generator/wiki/Filtering // For multi-context filtering (Settings.GenerateSingleDbContext = false), please read https://github.com/sjh37/EntityFramework-Reverse-POCO-Code-First-Generator/wiki/Generating-multiple-database-contexts-in-a-single-go // Use the following filters to exclude or include schemas/tables/views/columns/stored procedures. // You can have as many as you like, and mix and match them. // They run in the order defined below. For help with Regex's try https://regexr.com // Feel free to add more filter types and include them below. public static class FilterSettings { public static bool IncludeViews; public static bool IncludeSynonyms; public static bool IncludeStoredProcedures; public static bool IncludeTableValuedFunctions; public static bool IncludeScalarValuedFunctions; public static readonly List> SchemaFilters; public static readonly List> TableFilters; public static readonly List> ColumnFilters; public static readonly List> StoredProcedureFilters; static FilterSettings() { SchemaFilters = new List>(); TableFilters = new List>(); ColumnFilters = new List>(); StoredProcedureFilters = new List>(); } public static void Reset() { SchemaFilters .RemoveAll(x => true); TableFilters .RemoveAll(x => true); ColumnFilters .RemoveAll(x => true); StoredProcedureFilters.RemoveAll(x => true); } public static void AddDefaults() { IncludeViews = true; IncludeSynonyms = false; IncludeStoredProcedures = true; IncludeTableValuedFunctions = false; // If true, for EF6 install the "EntityFramework.CodeFirstStoreFunctions" NuGet Package. IncludeScalarValuedFunctions = false; AddDefaultSchemaFilters(); AddDefaultTableFilters(); AddDefaultColumnFilters(); AddDefaultStoredProcedureFilters(); } public static void CheckSettings() { if (IncludeTableValuedFunctions || IncludeScalarValuedFunctions) IncludeStoredProcedures = true; // Must be set if table/scalar functions are wanted } public static void AddDefaultSchemaFilters() { SchemaFilters.AddRange(new List> { new PeriodFilter(), // Keep this first as EF does not allow schemas to contain a period character // To include the only the schemas 'dbo' and 'events' //new RegexIncludeFilter("^dbo$|^events$"), // Add your own code to these custom filter classes new SchemaFilter(), new HasNameFilter(FilterType.Schema) }); } public static void AddDefaultTableFilters() { TableFilters.AddRange(new List> { //new PeriodFilter(), // Keep this first as EF does not allow tables to contain a period character // To include all the customer tables, but not the customer billing tables //new RegexExcludeFilter(".*[Bb]illing.*"), // This excludes all tables with 'billing' anywhere in the name //new RegexIncludeFilter("^[Cc]ustomer.*"), // This includes any remaining tables with names beginning with 'customer' // To exclude all tables that contain '_FR_' or begin with 'data_' //new RegexExcludeFilter("(.*_FR_.*)|(^data_.*)"), // Pass in your own custom Regex //new RegexIncludeFilter(new Regex("^tableName1$|^tableName2$", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(200))), // Add your own code to these custom filter classes new TableFilter(), new HasNameFilter(FilterType.Table), }); } public static void AddDefaultColumnFilters() { ColumnFilters.AddRange(new List> { // Exclude any columns that begin with 'FK_' //new RegexExcludeFilter("^FK_.*$"), // Add your own code to these custom filter classes new ColumnFilter(), new HasNameFilter(FilterType.Column), }); } public static void AddDefaultStoredProcedureFilters() { StoredProcedureFilters.AddRange(new List> { new PeriodFilter(), // Keep this first as EF does not allow stored procedures to contain a period character // Add your own code to these custom filter classes new StoredProcedureFilter(), new HasNameFilter(FilterType.StoredProcedure) }); } } /// /// Filtering can now be done via one or more Regex's and one or more functions. /// Gone are the days of a single do-it-all regex, you can now split them up into many smaller Regex's. /// It's now up to you how to want to mix and match them. /// public class SingleContextFilter : DbContextFilter { public List EnumDefinitions; protected readonly List> SchemaFilters; protected readonly List> TableFilters; protected readonly List> ColumnFilters; protected readonly List> StoredProcedureFilters; private bool _hasMergedIncludeFilters; public SingleContextFilter() { IncludeViews = FilterSettings.IncludeViews; IncludeSynonyms = FilterSettings.IncludeSynonyms; IncludeTableValuedFunctions = FilterSettings.IncludeTableValuedFunctions; IncludeScalarValuedFunctions = FilterSettings.IncludeScalarValuedFunctions; IncludeStoredProcedures = IncludeScalarValuedFunctions || IncludeTableValuedFunctions || FilterSettings.IncludeStoredProcedures; SchemaFilters = FilterSettings.SchemaFilters; TableFilters = FilterSettings.TableFilters; ColumnFilters = FilterSettings.ColumnFilters; StoredProcedureFilters = FilterSettings.StoredProcedureFilters; _hasMergedIncludeFilters = false; EnumDefinitions = new List(); Settings.AddEnumDefinitions?.Invoke(EnumDefinitions); } public override bool IsExcluded(EntityName item) { if(!_hasMergedIncludeFilters) { MergeIncludeFilters(); _hasMergedIncludeFilters = true; } var schema = item as Schema; if (schema != null) return SchemaFilters.Any(filter => filter.IsExcluded(schema)); var table = item as Table; if (table != null) return TableFilters.Any(filter => filter.IsExcluded(table)) || SchemaFilters.Any(filter => filter.IsExcluded(table.Schema)); var column = item as Column; if (column != null) return ColumnFilters.Any(filter => filter.IsExcluded(column)); var sp = item as StoredProcedure; if (sp != null) return StoredProcedureFilters.Any(filter => filter.IsExcluded(sp)) || SchemaFilters.Any(filter => filter.IsExcluded(sp.Schema)); return false; } public override string TableRename(string name, string schema, bool isView) { // Callback to Settings, which can be set within .tt if (Settings.TableRename != null) return Settings.TableRename(name, schema, isView); return name; } public override string MappingTableRename(string mappingTable, string tableName, string entityName) { // Callback to Settings, which can be set within .tt if (Settings.MappingTableRename != null) return Settings.MappingTableRename(mappingTable, tableName, entityName); return entityName; } public override void UpdateTable(Table table) { // Callback to Settings, which can be set within .tt Settings.UpdateTable?.Invoke(table); } public override void UpdateColumn(Column column, Table table) { // Callback to Settings, which can be set within .tt Settings.UpdateColumn?.Invoke(column, table, EnumDefinitions); } public override void AddEnum(Table table) { Settings.AddEnum?.Invoke(table); } public override void UpdateEnum(Enumeration enumeration) { Settings.UpdateEnum?.Invoke(enumeration); } public override void UpdateEnumMember(EnumerationMember enumerationMember) { Settings.UpdateEnumMember?.Invoke(enumerationMember); } public override void ViewProcessing(Table view) { // Callback to Settings, which can be set within .tt Settings.ViewProcessing?.Invoke(view); } public override string StoredProcedureRename(StoredProcedure sp) { // Callback to Settings, which can be set within .tt if (Settings.StoredProcedureRename != null) return Settings.StoredProcedureRename(sp); return sp.NameHumanCase; // Do nothing by default } public override string StoredProcedureReturnModelRename(string name, StoredProcedure sp) { // Callback to Settings, which can be set within .tt if (Settings.StoredProcedureReturnModelRename != null) return Settings.StoredProcedureReturnModelRename(name, sp); return name; // Do nothing by default } public override ForeignKey ForeignKeyFilter(ForeignKey fk) { // Return null to exclude this foreign key, or set IncludeReverseNavigation = false // to include the foreign key but not generate reverse navigation properties. // Example, to exclude all foreign keys for the Categories table, use: //if (fk.PkTableName == "Categories") // return null; // Example, to exclude reverse navigation properties for tables ending with Type, use: //if (fk.PkTableName.EndsWith("Type")) // fk.IncludeReverseNavigation = false; // You can also change the access modifier of the foreign-key's navigation property: //if(fk.PkTableName == "Categories") // fk.AccessModifier = "internal"; return fk; } public override string[] ForeignKeyAnnotationsProcessing(Table fkTable, Table pkTable, string propName, string fkPropName) { // Callback to Settings, which can be set within .tt if (Settings.ForeignKeyAnnotationsProcessing != null) return Settings.ForeignKeyAnnotationsProcessing(fkTable, pkTable, propName, fkPropName); return null; } private void MergeIncludeFilters() { MergeIncludeFilters(SchemaFilters); MergeIncludeFilters(TableFilters); MergeIncludeFilters(ColumnFilters); MergeIncludeFilters(StoredProcedureFilters); } private static void MergeIncludeFilters(List> filters) { var list = filters .Where(x => x.GetType() == typeof(RegexIncludeFilter)) .Select(x => (RegexIncludeFilter) x) .ToList(); if (list.Count < 2) return; // Nothing to merge var singleRegex = string.Join("|", list.Select(x => x.Pattern())); filters.RemoveAll(filter => filter.GetType() == typeof(RegexIncludeFilter)); var singleIncludeFilter = (IFilterType) new RegexIncludeFilter(singleRegex); filters.Add(singleIncludeFilter); } } // **************************************************************************************************************************************** // **************************** No more settings to adjust. Rest of the generator code below. ********************************************* // **************************************************************************************************************************************** public static class AssemblyHelper { public static object LoadPlugin(string assemblyAndType) { var assemblyInfo = assemblyAndType.Split(','); return LoadPlugin(assemblyInfo[0], assemblyInfo[1]); } /// /// Load a plugin /// /// Full path including DLL name. i.e. "C:\\S\\Source (open source)\\EntityFramework-Reverse-POCO-Code-Generator\\Generator.Tests.Unit\\\bin\\Debug\\Generator.Tests.Unit.dll" /// Fully qualified class name, including any namespaces. i.e. "Generator.Tests.Unit.MultiContextSettingsPlugin" /// public static object LoadPlugin(string assemblyFile, string typeName) { var asm = Assembly.LoadFrom(assemblyFile.Trim()); var dynType = asm.GetType(typeName.Trim(), true, false); return Activator.CreateInstance(dynType); } } public class Column : EntityName { public string DisplayName; // Name used in the data annotation [Display(Name = " goes here")] public bool OverrideModifier = false; // Adds 'override' to the property declaration public List Attributes = new List(); // List of attributes to add to this columns poco property public bool Hidden; // If true, does not generate any code for this column. public bool ExistsInBaseClass; // If true, does not generate the property for this column as it will exist in a base class public int Scale; public string PropertyType; public string SqlPropertyType; public int DateTimePrecision; public string Default; public string HasDefaultValueSql; public int MaxLength; public int Precision; public int Ordinal; public int PrimaryKeyOrdinal; public string ExtendedProperty; public string SummaryComments; public string InlineComments; public string UniqueIndexName; public bool AllowEmptyStrings = true; public bool IsIdentity; public bool IsRowGuid; public bool IsComputed; public ColumnGeneratedAlwaysType GeneratedAlwaysType; public bool IsNullable; public bool IsPrimaryKey; public bool IsUniqueConstraint; public bool IsUnique; public bool IsStoreGenerated; public bool IsRowVersion; public bool IsConcurrencyToken; // Manually set via callback public bool IsFixedLength; public bool IsUnicode; public bool IsMaxLength; public bool IsForeignKey; public bool IsSpatial; public string Config; public List ConfigFk = new List(); public List EntityFk = new List(); public List Indexes = new List(); public Table ParentTable; public static readonly List NotNullable = new List { Settings.AllowNullStrings ? "" : "string", "byte[]", "datatable", "system.data.datatable", "object", "microsoft.sqlserver.types.sqlgeography", "microsoft.sqlserver.types.sqlgeometry", "sqlgeography", "sqlgeometry", "system.data.entity.spatial.dbgeography", "system.data.entity.spatial.dbgeometry", "dbgeography", "dbgeometry", "system.data.entity.hierarchy.hierarchyid", "hierarchyid", "nettopologysuite.geometries.point", "nettopologysuite.geometries.geometry" }; public static readonly List StoredProcedureNotNullable = new List { "string", "byte[]", "datatable", "system.data.datatable", "object", "microsoft.sqlserver.types.sqlgeography", "microsoft.sqlserver.types.sqlgeometry", "sqlgeography", "sqlgeometry", "system.data.entity.spatial.dbgeography", "system.data.entity.spatial.dbgeometry", "dbgeography", "dbgeometry", "system.data.entity.hierarchy.hierarchyid", "hierarchyid", "nettopologysuite.geometries.point", "nettopologysuite.geometries.geometry" }; public static readonly List CanUseSqlServerIdentityColumn = new List { "sbyte", "short", "smallint", "int", "long" }; public static readonly List ExcludedHasColumnType = new List { "user-defined" }; public void ResetNavigationProperties() { ConfigFk = new List(); EntityFk = new List(); } public bool IsColumnNullable() { return IsNullable && !NotNullable.Contains(PropertyType.ToLower()); } public void CleanUpDefault() { if (string.IsNullOrWhiteSpace(Default) || IsSpatial) { Default = string.Empty; return; } // Remove outer brackets while (Default.First() == '(' && Default.Last() == ')' && Default.Length > 2) { Default = Default.Substring(1, Default.Length - 2); } // Check for sequence var lower = Default.ToLower(); if (lower.Contains("next value for")) { HasDefaultValueSql = Default.Trim(); Default = string.Empty; return; } // Remove unicode prefix if (IsUnicode && Default.StartsWith("N") && !Default.Equals("NULL", StringComparison.InvariantCultureIgnoreCase)) Default = Default.Substring(1, Default.Length - 1); if (Default.First() == '\'' && Default.Last() == '\'' && Default.Length >= 2) Default = string.Format("\"{0}\"", Default.Substring(1, Default.Length - 2)); lower = Default.ToLower(); var lowerPropertyType = PropertyType.ToLower(); // Cleanup default switch (lowerPropertyType) { case "bool": Default = (Default == "0" || lower == "\"false\"" || lower == "false") ? "false" : "true"; break; case "string": case "datetime": case "datetime2": case "system.datetime": case "timespan": case "system.timespan": case "datetimeoffset": case "system.datetimeoffset": if (Default.First() != '"') Default = string.Format("\"{0}\"", Default); if (Default.Contains('\\') || Default.Contains('\r') || Default.Contains('\n')) Default = "@" + Default; else Default = string.Format("\"{0}\"", Default.Substring(1, Default.Length - 2) .Replace("\"", "\\\"")); // #281 Default values must be escaped if contain double quotes break; case "long": case "short": case "int": case "double": case "float": case "decimal": case "byte": case "guid": case "system.guid": if (Default.First() == '\"' && Default.Last() == '\"' && Default.Length > 2) Default = Default.Substring(1, Default.Length - 2); break; case "byte[]": case "system.data.entity.spatial.dbgeography": case "system.data.entity.spatial.dbgeometry": case "nettopologysuite.geometries.point": case "nettopologysuite.geometries.geometry": Default = string.Empty; break; } // Ignore defaults we cannot interpret (we would need SQL to C# compiler) if (lower.StartsWith("create default")) { Default = string.Empty; return; } if (string.IsNullOrWhiteSpace(Default)) { Default = string.Empty; return; } // Validate default switch (lowerPropertyType) { case "long": long l; if (!long.TryParse(Default, out l)) Default = string.Empty; break; case "short": short s; if (!short.TryParse(Default, out s)) Default = string.Empty; break; case "int": int i; if (!int.TryParse(Default, out i)) Default = string.Empty; break; case "datetime": case "datetime2": case "system.datetime": DateTime dt; if (!DateTime.TryParse(Default, out dt)) Default = (lower.Contains("getdate()") || lower.Contains("sysdatetime")) ? "DateTime.Now" : (lower.Contains("getutcdate()") || lower.Contains("sysutcdatetime")) ? "DateTime.UtcNow" : string.Empty; else Default = string.Format("DateTime.Parse({0})", Default); break; case "datetimeoffset": case "system.datetimeoffset": DateTimeOffset dto; if (!DateTimeOffset.TryParse(Default, out dto)) Default = (lower.Contains("getdate()") || lower.Contains("sysdatetimeoffset")) ? "DateTimeOffset.Now" : (lower.Contains("getutcdate()") || lower.Contains("sysutcdatetime")) ? "DateTimeOffset.UtcNow" : string.Empty; else Default = string.Format("DateTimeOffset.Parse({0})", Default); break; case "timespan": case "system.timespan": TimeSpan ts; Default = TimeSpan.TryParse(Default, out ts) ? string.Format("TimeSpan.Parse({0})", Default) : string.Empty; break; case "double": double d; if (!double.TryParse(Default, out d)) Default = string.Empty; if (Default.ToLowerInvariant().EndsWith(".")) Default += "0"; break; case "float": float f; if (!float.TryParse(Default, out f)) Default = string.Empty; if (!Default.ToLowerInvariant().EndsWith("f")) Default += "f"; break; case "decimal": decimal dec; if (!decimal.TryParse(Default, out dec)) Default = string.Empty; else Default += "m"; break; case "byte": byte b; if (!byte.TryParse(Default, out b)) Default = string.Empty; break; case "bool": bool x; if (!bool.TryParse(Default, out x)) Default = string.Empty; break; case "string": if (lower.Contains("newid()") || lower.Contains("newsequentialid()")) Default = "Guid.NewGuid().ToString()"; if (lower.StartsWith("space(")) Default = "\"\""; if (lower == "null") Default = string.Empty; break; case "guid": case "system.guid": if (lower.Contains("newid()") || lower.Contains("newsequentialid()")) Default = "Guid.NewGuid()"; else if (lower.Contains("null")) Default = "null"; else Default = string.Format("Guid.Parse(\"{0}\")", Default); break; } } public static string ToDisplayName(string str) { if (string.IsNullOrEmpty(str)) return string.Empty; var sb = new StringBuilder(30); str = Regex.Replace(str, @"[^a-zA-Z0-9]", " "); // Anything that is not a letter or digit, convert to a space str = Regex.Replace(str, @"([A-Z])([A-Z])([a-z])|([a-z])([A-Z])", "$1$4 $2$3$5"); // Add space between case changes var hasUpperCased = false; var lastChar = '\0'; foreach (var original in str.Trim()) { var c = original; if (lastChar == '\0') { c = char.ToUpperInvariant(original); } else { var isLetter = char.IsLetter(original); var isDigit = char.IsDigit(original); var isWhiteSpace = !isLetter && !isDigit; // Is this char is different to last time var isDifferent = false; if (isLetter && !char.IsLetter(lastChar)) isDifferent = true; else if (isDigit && !char.IsDigit(lastChar)) isDifferent = true; else if (char.IsUpper(original) && !char.IsUpper(lastChar)) isDifferent = true; if (isDifferent || isWhiteSpace) sb.Append(' '); // Add a space if (hasUpperCased && isLetter) c = char.ToLowerInvariant(original); } lastChar = original; if (!hasUpperCased && char.IsUpper(c)) hasUpperCased = true; sb.Append(c); } str = sb.ToString(); str = Regex.Replace(str, @"\s+", " ").Trim(); // Multiple white space to one space str = Regex.Replace(str, @"\bid\b", "ID"); // Make ID word uppercase return str; } public string WrapIfNullable() { if (!IsColumnNullable()) return PropertyType; return string.Format(Settings.NullableShortHand ? "{0}?" : "System.Nullable<{0}>", PropertyType); } } public enum ColumnGeneratedAlwaysType { NotApplicable = 0, AsRowStart = 1, AsRowEnd = 2 } [Flags] public enum CommentsStyle { None, InSummaryBlock, AtEndOfField }; public enum DatabaseType { SqlServer, SqlCe, SQLite, Plugin, // See Settings.DatabaseReaderPlugin PostgreSQL, MySql, // Not yet implemented Oracle // Not yet implemented } public static class EfrpgVersion { public static string Version() { return "v3.9.0"; } } // Settings to allow selective code generation [Flags] public enum Elements { None = 0, Poco = 1, Context = 2, Interface = 4, PocoConfiguration = 8, Enum = 16 }; public abstract class EntityName { public string DbName; // Raw name as obtained from the database public string NameHumanCase; // Name adjusted for C# generator output } public class EnumDefinition { public string Schema; public string Table; public string Column; public string EnumType; } public class Enumeration { public readonly string EnumName; public readonly List Items; public List EnumAttributes = new List(); public Enumeration(string enumName, List items) { EnumName = enumName; Items = items; } } public class EnumerationMember { public readonly string Key; public readonly string Value; public readonly Dictionary AllValues; public List Attributes = new List(); public EnumerationMember(string key, string value, Dictionary allValues) { Key = key; Value = value; AllValues = allValues; } } public class EfCoreFileManager : IFileManager { private readonly List _blocks; private string _filename; private StringBuilder _sb; private const int StringBuilderSize = 2048; public EfCoreFileManager() { _blocks = new List(); _filename = null; _sb = new StringBuilder(StringBuilderSize); } public void Init(GeneratedTextTransformation textTransformation) { } public void StartHeader() { } public void StartFooter() { } public void EndBlock() { AddCurrentBlock(); } public void Process(bool split) { AddCurrentBlock(); foreach (var fileBlock in _blocks) { var filename = Path.Combine(Settings.Root, fileBlock.Filename); using (var file = new StreamWriter(filename)) { file.Write(fileBlock.Text); } } } public void ProcessToAnotherFileManager(IFileManager fileManager, GeneratedTextTransformation outer) { AddCurrentBlock(); foreach (var fileBlock in _blocks) { fileManager.StartNewFile(fileBlock.Filename); outer.WriteLine(fileBlock.Text); fileManager.EndBlock(); } } public void StartNewFile(string name) { _filename = name; } public void WriteLine(string text) { _sb.AppendLine(text); } private void AddCurrentBlock() { if (_sb.Length == 0) return; if (string.IsNullOrEmpty(_filename)) { _sb = new StringBuilder(StringBuilderSize); return; } _blocks.Add(new FileBlock { Text = _sb.ToString(), Filename = _filename }); _filename = null; _sb = new StringBuilder(StringBuilderSize); } private class FileBlock { public string Text { get; set; } public string Filename { get; set; } } } /// /// This class is responsible for: /// 1. Recording what files were created. /// 2. Write this log to into a text file, same name as the database.tt filename, but with .txt file extension. /// 3. Read this file back in at the start, and to delete the previous runs files prior to re-generating. /// public class FileAuditService { // List of file, including paths, relative to Settings.Root private readonly List _files; private readonly DateTime _start; public FileAuditService() { _start = DateTime.Now; _files = new List { "# This file contains a list of the files generated by the " + Settings.TemplateFile + ".tt file.", "# Please do not edit this file. It is used to delete files that may get filtered out during the next run.", "# Time start = " + _start.ToLocalTime() }; } public void AddFile(string file) { _files.Add(file); } public void WriteAuditFile() { var end = DateTime.Now; _files.Add($"# Time end = {end.ToLocalTime()}, duration = {(end - _start).TotalSeconds:F} seconds."); var filename = GetFilename(); DeleteFilesNotRecreated(filename); File.WriteAllLines(filename, _files); } private void DeleteFilesNotRecreated(string filename) { if (!File.Exists(filename)) return; var filesToDelete = File.ReadAllLines(filename) .Where(x => !x.StartsWith("#") && !string.IsNullOrWhiteSpace(x) && !_files.Contains(x)) .ToList(); foreach (var file in filesToDelete) { try { var path = Path.Combine(Settings.Root, file); if (File.Exists(path)) File.Delete(path); } catch (Exception) { // Ignore if cannot delete, or file is missing } } } private static string GetFilename() { return Path.Combine(Settings.Root, Settings.TemplateFile + "Audit.txt"); } } public class FileManagementService : IFileManager { private readonly GeneratedTextTransformation _outer; private readonly Dictionary _fileManagers; private IFileManager _fileManager; private VisualStudioFileManager _visualStudioFileManager; private readonly FileAuditService _auditService; private bool _writeToOuter; public bool ForceWriteToOuter; public FileManagementService(GeneratedTextTransformation outer) { if (outer == null) throw new ArgumentNullException(nameof(outer)); _outer = outer; _fileManagers = new Dictionary(); _fileManager = null; _visualStudioFileManager = null; _auditService = new FileAuditService(); } public static void DeleteFile(string filename) { try { var path = Path.Combine(Settings.Root, filename); File.Delete(path); } catch { // ignored } } public void Init(Dictionary filters, Type fileManagerType) { Settings.FilterCount = filters.Count; _writeToOuter = Settings.GenerateSingleDbContext && !Settings.GenerateSeparateFiles; // For debug /*var a = _writeToOuter; var b = Settings.FilterCount; var c = Settings.GenerateSeparateFiles; var d = Settings.TemplateType; var e = Settings.GenerateSingleDbContext; var f = filters.First().Key;*/ if (fileManagerType == typeof(VisualStudioFileManager)) { _visualStudioFileManager = (VisualStudioFileManager)Activator.CreateInstance(fileManagerType); _visualStudioFileManager.Init(_outer); // Switch to the EfCoreFileManager for the rest fileManagerType = typeof(EfCoreFileManager); } foreach (var filter in filters) { var fileManager = (IFileManager)Activator.CreateInstance(fileManagerType); fileManager.Init(_outer); if (!string.IsNullOrWhiteSpace(filter.Key)) fileManager.StartNewFile(filter.Key + Settings.FileExtension); _fileManagers.Add(filter.Key, fileManager); } } public void UseFileManager(string key) { _fileManager = _fileManagers[key]; } public void Error(string error) { // Write any errors to the primary output cs file _outer.WriteLine(error); } public void WriteLine(string text) { if (_writeToOuter || ForceWriteToOuter) _outer.WriteLine(text); else _fileManager.WriteLine(text); } public void Init(GeneratedTextTransformation textTransformation) { throw new NotImplementedException(); } public void StartHeader() { _fileManager.StartHeader(); } public void StartFooter() { _fileManager.StartFooter(); } public void EndBlock() { _fileManager.EndBlock(); } public void Process(bool split) { _auditService.WriteAuditFile(); foreach (var fileManager in _fileManagers) { if (_visualStudioFileManager == null) { fileManager.Value.Process(split); continue; } if (fileManager.Value.GetType() == typeof(EfCoreFileManager)) ((EfCoreFileManager)fileManager.Value).ProcessToAnotherFileManager(_visualStudioFileManager, _outer); else fileManager.Value.Process(split); } _visualStudioFileManager?.Process(split); } public void StartNewFile(string name) { _fileManager.StartNewFile(name); _auditService.AddFile(name); } } public static class FileManagerFactory { public static Type GetFileManagerType() { switch (Settings.FileManagerType) { case FileManagerType.VisualStudio: return typeof(VisualStudioFileManager); #pragma warning disable CS0618 // Type or member is obsolete case FileManagerType.Custom: #pragma warning restore CS0618 // Type or member is obsolete case FileManagerType.EfCore: return typeof(EfCoreFileManager); case FileManagerType.Null: return typeof(NullFileManager); default: throw new ArgumentOutOfRangeException(); } } } public interface IFileManager { void Init(GeneratedTextTransformation textTransformation); void StartHeader(); void StartFooter(); void EndBlock(); void Process(bool split); void StartNewFile(string name); void WriteLine(string text); } public class NullFileManager : IFileManager { public void Init(GeneratedTextTransformation textTransformation) { } public void StartHeader() { } public void StartFooter() { } public void EndBlock() { } public void Process(bool split) { } public void StartNewFile(string name) { } public void WriteLine(string text) { } } // This will make use of the real file manager within EF6.Utility.CS.ttinclude public class VisualStudioFileManager : IFileManager { private EntityFrameworkTemplateFileManager _wrapped; public void Init(GeneratedTextTransformation textTransformation) { _wrapped = EntityFrameworkTemplateFileManager.Create(textTransformation); } public void StartHeader() { _wrapped.StartHeader(); } public void StartFooter() { _wrapped.StartFooter(); } public void EndBlock() { _wrapped.EndBlock(); } public void Process(bool split) { _wrapped.Process(split); } public void StartNewFile(string name) { _wrapped.StartNewFile(name); } public void WriteLine(string text) { } } public enum FileManagerType { // Use this for .NET 4.x projects. // It will make use of the `EF6.Utility.CS.ttinclude` to add/remove files to the Visual Studio project. VisualStudio, // Use this for .NET Core projects. // It will write the files directly. // Visual Studio will automatically detect new files and include them in the project. EfCore, [Obsolete("Please use FileManagerType.EfCore instead")] Custom, Null // For testing only. Does nothing. } public class ColumnFilter : IFilterType { // Filtering of columns using a function. // Return true to exclude the column, return false to include it. public bool IsExcluded(Column c) { // Example: Exclude any columns whose table is in 'dbo' schema and column name starts with 'bank' //if(c.ParentTable.Schema.DbName.Equals(Settings.DefaultSchema, StringComparison.InvariantCultureIgnoreCase) && // c.NameHumanCase.ToLowerInvariant().StartsWith("bank")) // return true; return false; } } public abstract class DbContextFilter : IDbContextFilter { public string SubNamespace { get; set; } public Tables Tables { get; set; } public List StoredProcs { get; set; } public List Enums { get; set; } public List Sequences { get; set; } public bool IncludeViews { get; set; } public bool IncludeSynonyms { get; set; } public bool IncludeStoredProcedures { get; set; } public bool IncludeTableValuedFunctions { get; set; } public bool IncludeScalarValuedFunctions { get; set; } protected DbContextFilter() { Tables = new Tables(); StoredProcs = new List(); Enums = new List(); Sequences = new List(); SubNamespace = string.Empty; } public abstract bool IsExcluded(EntityName item); public abstract string TableRename(string name, string schema, bool isView); public abstract string MappingTableRename(string mappingTable, string tableName, string entityName); public abstract void UpdateTable(Table table); public abstract void UpdateColumn(Column column, Table table); public abstract void AddEnum(Table table); public abstract void UpdateEnum(Enumeration enumeration); public abstract void UpdateEnumMember(EnumerationMember enumerationMember); public abstract void ViewProcessing(Table view); public abstract string StoredProcedureRename(StoredProcedure sp); public abstract string StoredProcedureReturnModelRename(string name, StoredProcedure sp); public abstract ForeignKey ForeignKeyFilter(ForeignKey fk); public abstract string[] ForeignKeyAnnotationsProcessing(Table fkTable, Table pkTable, string propName, string fkPropName); } public class DbContextFilterList : IDbContextFilterList { private List _multiContextSettings; private Dictionary _filters; // Key = database context name, which also becomes the sub-namespace used to encapsulate the many db contexts public bool ReadDbContextSettings(DatabaseReader reader, string singleDbContextSubNamespace = "") { _filters = new Dictionary(); if (Settings.GenerateSingleDbContext) { // No need to read the database for settings, as they are provided by the user customisable class SingleContextFilter var filter = new SingleContextFilter { SubNamespace = singleDbContextSubNamespace }; _filters.Add(string.IsNullOrWhiteSpace(singleDbContextSubNamespace) ? string.Empty : singleDbContextSubNamespace, filter); return true; } // Read in multi database context settings if (!string.IsNullOrWhiteSpace(Settings.MultiContextSettingsPlugin)) { // Use plugin // We know the plugin implements (IMultiContextSettingsPlugin) interface but we can't direct cast it so we copy the object var plugin = AssemblyHelper.LoadPlugin(Settings.MultiContextSettingsPlugin); var pluginType = plugin.GetType(); // remoteSettingsListObject is a List object var remoteSettingsListObject = (IList) pluginType.InvokeMember("ReadSettings", System.Reflection.BindingFlags.InvokeMethod, null, plugin, null); _multiContextSettings = new List(); var remoteSettingsList = remoteSettingsListObject.Cast(); foreach (var remoteSettings in remoteSettingsList) { var multiContextSettings = new MultiContextSettings(); MultiContextSettingsCopy.Copy(remoteSettings, multiContextSettings); _multiContextSettings.Add(multiContextSettings); } } else { if (reader == null) return false; // Read from database _multiContextSettings = reader.ReadMultiContextSettings(); } if (_multiContextSettings == null || _multiContextSettings.Count == 0) return false; foreach (var setting in _multiContextSettings) { var filter = new MultiContextFilter(setting); if(!string.IsNullOrWhiteSpace(setting.Filename) && !_filters.ContainsKey(setting.Filename)) _filters.Add(setting.Filename, filter); else _filters.Add(setting.Name, filter); } return true; } public Dictionary GetFilters() { return _filters; } public List GetMultiContextSettings() { return _multiContextSettings; } public bool IncludeViews() { return _filters.Any(x => x.Value.IncludeViews); } public bool IncludeSynonyms() { return _filters.Any(x => x.Value.IncludeSynonyms); } public bool IncludeStoredProcedures() { return _filters.Any(x => x.Value.IncludeStoredProcedures); } public bool IncludeTableValuedFunctions() { return _filters.Any(x => x.Value.IncludeTableValuedFunctions); } public bool IncludeScalarValuedFunctions() { return _filters.Any(x => x.Value.IncludeScalarValuedFunctions); } } public enum FilterType { Schema, // Can only be used on Schema Table, // Can only used on Tables Column, // Can only used on Columns StoredProcedure // Can only used on Stored Procedures } public class HasNameFilter : IFilterType { private readonly FilterType _filterType; public HasNameFilter(FilterType filterType) { _filterType = filterType; } public bool IsExcluded(EntityName item) { // Example: Exclude a schema with a name of 'audit' //if (_filterType == FilterType.Schema && item.Name.Equals("audit", StringComparison.InvariantCultureIgnoreCase)) // return false; // Example: Exclude any item with 'audit' anywhere in its name. //if (item.Name.ToLowerInvariant().Contains("audit")) // return true; // Example: Exclude any table which starts with 'audit' //if (_filterType == FilterType.Table && item.Name.ToLowerInvariant().StartsWith("audit")) // return true; // TODO: Add your code here return false; } } public interface IDbContextFilter { string SubNamespace { get; set; } Tables Tables { get; set; } List StoredProcs { get; set; } List Enums { get; set; } List Sequences { get; set; } bool IncludeViews { get; set; } bool IncludeSynonyms { get; set; } bool IncludeStoredProcedures { get; set; } bool IncludeTableValuedFunctions { get; set; } // If true, you must set IncludeStoredProcedures = true, and install the "EntityFramework.CodeFirstStoreFunctions" Nuget Package. bool IncludeScalarValuedFunctions { get; set; } // If true, you must set IncludeStoredProcedures = true. bool IsExcluded(EntityName item); string TableRename(string name, string schema, bool isView); string MappingTableRename(string mappingTable, string tableName, string entityName); void UpdateTable(Table table); void UpdateColumn(Column column, Table table); void AddEnum(Table table); void UpdateEnum(Enumeration enumeration); void UpdateEnumMember(EnumerationMember enumerationMember); void ViewProcessing(Table view); string StoredProcedureRename(StoredProcedure sp); string StoredProcedureReturnModelRename(string name, StoredProcedure sp); ForeignKey ForeignKeyFilter(ForeignKey fk); string[] ForeignKeyAnnotationsProcessing(Table fkTable, Table pkTable, string propName, string fkPropName); } public interface IDbContextFilterList { bool ReadDbContextSettings(DatabaseReader reader, string singleDbContextSubNamespace = null); Dictionary GetFilters(); List GetMultiContextSettings(); bool IncludeViews(); bool IncludeSynonyms(); bool IncludeStoredProcedures(); bool IncludeTableValuedFunctions(); bool IncludeScalarValuedFunctions(); } public interface IFilter { } public interface IFilterType : IFilter { bool IsExcluded(T item); } public class MultiContextFilter : DbContextFilter { private readonly MultiContextSettings _settings; private readonly MultiContextNameNormalisation _normalisation; private readonly List _allowedSchemas; private readonly List _allowedTables; private readonly Dictionary> _allowedColumns; // Key = table name, value = list of columns private readonly List _allowedStoredProcs; // Stored procedures private readonly List _allowedFunctions; // Table/Scalar valued functions public MultiContextFilter(MultiContextSettings settings) { _settings = settings; IncludeViews = settings.IncludeViews(); IncludeSynonyms = false; IncludeTableValuedFunctions = settings.IncludeFunctions(); // If true, for EF6 install the "EntityFramework.CodeFirstStoreFunctions" Nuget Package. IncludeScalarValuedFunctions = IncludeTableValuedFunctions; // Scalar/Table function filters are not separate in this filter. IncludeStoredProcedures = IncludeScalarValuedFunctions || IncludeTableValuedFunctions || settings.IncludeStoredProcedures(); SubNamespace = settings.GetNamespace(); _allowedSchemas = new List(); _allowedTables = new List(); _allowedColumns = new Dictionary>(); _allowedStoredProcs = new List(); _allowedFunctions = new List(); // Pre-process the settings _normalisation = new MultiContextNameNormalisation(settings.BaseSchema); _allowedSchemas.Add(_normalisation.DefaultSchema); // Tables foreach (var t in settings.Tables) { var tableName = _normalisation.Normalise(t.Name); SchemaAndName tableDbName = null; if (!string.IsNullOrEmpty(t.DbName)) { t.DbName = t.DbName.Replace("[", "").Replace("]", ""); tableDbName = _normalisation.Normalise(t.DbName); _allowedTables.Add(tableDbName.ToString()); // Override schema with the one defined in DbName tableName.Schema = tableDbName.Schema; } _allowedTables.Add(tableName.ToString()); SchemaAndName tablePluralName = null; if (!string.IsNullOrEmpty(t.PluralName)) { tablePluralName = _normalisation.Normalise(t.PluralName); tablePluralName.Schema = tableName.Schema; _allowedTables.Add(tablePluralName.ToString()); } var cols = new List(); foreach (var c in t.Columns) { var columnName = _normalisation.Normalise(c.Name); cols.Add(columnName.Name.ToLowerInvariant()); if (!string.IsNullOrEmpty(c.DbName)) { c.DbName = c.DbName.Replace("[", "").Replace("]", ""); var columnDbName = _normalisation.Normalise(c.DbName); cols.Add(columnDbName.Name.ToLowerInvariant()); } } if (cols.Any()) { cols = cols.Distinct().ToList(); _allowedColumns.Add(tableName.ToString(), cols); if (tableDbName != null && !_allowedColumns.ContainsKey(tableDbName.ToString())) _allowedColumns.Add(tableDbName.ToString(), cols); if (tablePluralName != null && !_allowedColumns.ContainsKey(tablePluralName.ToString())) _allowedColumns.Add(tablePluralName.ToString(), cols); } } _allowedTables = _allowedTables.Distinct().ToList(); // Find schemas used in tables var tableSchemas = _allowedTables .Where(x => x.Contains('.')) .Select(t => t.Split('.').First().ToLowerInvariant()) .Distinct() .ToList(); _allowedSchemas.AddRange(tableSchemas); _allowedSchemas = _allowedSchemas.Distinct().ToList(); // Stored procedures foreach (var sp in settings.StoredProcedures) { var spName = _normalisation.Normalise(sp.Name); if (!string.IsNullOrEmpty(sp.DbName)) { var spDbName = _normalisation.Normalise(sp.DbName); _allowedStoredProcs.Add(spDbName.ToString()); // Override schema with the one defined in DbName spName.Schema = spDbName.Schema; } _allowedStoredProcs.Add(spName.ToString()); } _allowedStoredProcs = _allowedStoredProcs.Distinct().ToList(); // Functions foreach (var f in settings.Functions) { var funcName = _normalisation.Normalise(f.Name); if (!string.IsNullOrEmpty(f.DbName)) { var funcDbName = _normalisation.Normalise(f.DbName); _allowedFunctions.Add(funcDbName.ToString()); // Override schema with the one defined in DbName funcName.Schema = funcDbName.Schema; } _allowedFunctions.Add(funcName.ToString()); } _allowedFunctions = _allowedFunctions.Distinct().ToList(); } public MultiContextSettings GetSettings() { return _settings; } public override bool IsExcluded(EntityName item) { if (string.IsNullOrEmpty(item.DbName)) return true; // Schema var schema = item as Schema; if (schema != null) return !_allowedSchemas.Contains(item.DbName.ToLowerInvariant()); // Table var table = item as Table; if (table != null) { var search = $"{table.Schema.DbName}.{table.DbName}".ToLowerInvariant(); if (_allowedTables.Contains(search)) return false; // Allowed if (!string.IsNullOrEmpty(table.NameHumanCase)) { search = $"{table.Schema.DbName}.{table.NameHumanCase}".ToLowerInvariant(); if (_allowedTables.Contains(search)) return false; // Allowed } return true; // Excluded } // Column var column = item as Column; if (column != null) { var key = $"{column.ParentTable.Schema.DbName}.{column.ParentTable.DbName}".ToLowerInvariant(); if (!_allowedColumns.ContainsKey(key) && !string.IsNullOrEmpty(column.ParentTable.NameHumanCase)) { key = $"{column.ParentTable.Schema.DbName}.{column.ParentTable.NameHumanCase}".ToLowerInvariant(); if (!_allowedColumns.ContainsKey(key)) return true; // Excluded as could not find table } var cols = _allowedColumns[key]; if (cols.Contains(item.DbName.ToLowerInvariant())) return false; // Allowed if (!string.IsNullOrEmpty(item.NameHumanCase) && cols.Contains(item.NameHumanCase.ToLowerInvariant())) return false; // Allowed return true; // Excluded } // Stored procedure var sp = item as StoredProcedure; if (sp != null) { var search = $"{sp.Schema.DbName}.{sp.DbName}".ToLowerInvariant(); if (sp.IsStoredProcedure && _allowedStoredProcs.Contains(search)) return false; // Allowed if ((sp.IsTableValuedFunction || sp.IsScalarValuedFunction) && _allowedFunctions.Contains(search)) return false; // Allowed if (!string.IsNullOrEmpty(sp.NameHumanCase)) { search = $"{sp.Schema.DbName}.{sp.NameHumanCase}".ToLowerInvariant(); if (sp.IsStoredProcedure && _allowedStoredProcs.Contains(search)) return false; // Allowed if ((sp.IsTableValuedFunction || sp.IsScalarValuedFunction) && _allowedFunctions.Contains(search)) return false; // Allowed } return true; // Excluded } return true; // Always exclude unless found } public override string TableRename(string name, string schema, bool isView) { var tableSettings = FindTableSetting(name, schema); if (tableSettings != null && !string.IsNullOrWhiteSpace(tableSettings.Name)) return tableSettings.Name; // Callback to Settings, which can be set within .tt if (Settings.TableRename != null) return Settings.TableRename(name, schema, isView); return name; } public override string MappingTableRename(string mappingTable, string tableName, string entityName) { // Callback to Settings, which can be set within .tt if (Settings.MappingTableRename != null) return Settings.MappingTableRename(mappingTable, tableName, entityName); return entityName; } public override void UpdateTable(Table table) { var t = FindTableSetting(table.DbName, table.Schema.DbName); if (t != null) { if (!string.IsNullOrEmpty(t.Description)) table.AdditionalComment = t.Description; if (!string.IsNullOrEmpty(t.PluralName)) table.PluralNameOverride = t.PluralName; if (!string.IsNullOrEmpty(t.DbSetModifier)) table.DbSetModifier = t.DbSetModifier; if (!string.IsNullOrEmpty(t.Attributes)) table.Attributes.AddRange(t.Attributes.Split(Settings.MultiContextAttributeDelimiter)); } // Callback to Settings, which can be set within .tt Settings.UpdateTable?.Invoke(table); if (t.AllFields != null) { Settings.MultiContextAllFieldsTableProcessing?.Invoke(table, t.AllFields); // Examples of how to use additional custom fields from the MultiContext.[Table] table // VARCHAR example /*if (t.AllFields.ContainsKey("Notes")) { var o = t.AllFields["Notes"]; if (string.IsNullOrEmpty(table.AdditionalComment)) table.AdditionalComment = string.Empty; table.AdditionalComment += string.Format(" Test = {0}", o.ToString()); }*/ } } public override void UpdateColumn(Column column, Table table) { var t = FindTableSetting(table.DbName, table.Schema.DbName); if (t != null) { var name = column.DbName.ToLowerInvariant(); var nameHumanCase = column.NameHumanCase.ToLowerInvariant(); var col = FindColumnSetting(t, name, nameHumanCase); if (col != null) { column.NameHumanCase = col.Name; if (!string.IsNullOrEmpty(col.Attributes)) column.Attributes.AddRange(col.Attributes.Split(Settings.MultiContextAttributeDelimiter)); if (col.OverrideModifier != null) column.OverrideModifier = col.OverrideModifier.Value; if (col.IsPrimaryKey != null) column.IsPrimaryKey = col.IsPrimaryKey.Value; if (!string.IsNullOrEmpty(col.EnumType)) { column.PropertyType = col.EnumType; if (!string.IsNullOrEmpty(column.Default)) column.Default = "(" + col.EnumType + ") " + column.Default; } if (col.IsNullable != null) column.IsNullable = col.IsNullable.Value; if (!string.IsNullOrEmpty(col.PropertyType)) column.PropertyType = col.PropertyType; if (col.AllFields != null) { Settings.MultiContextAllFieldsColumnProcessing?.Invoke(column, table, col.AllFields); // Examples of how to use additional custom fields from the MultiContext.[Column] table // INT example /*if (col.AllFields.ContainsKey("DummyInt")) { var o = col.AllFields["DummyInt"]; column.ExtendedProperty += string.Format(" DummyInt = {0}", (int) o); }*/ // VARCHAR example /*if (col.AllFields.ContainsKey("Test")) { var o = col.AllFields["Test"]; column.ExtendedProperty += string.Format(" Test = {0}", o.ToString()); }*/ // DATETIME example /*if (col.AllFields.ContainsKey("date_of_birth")) { var o = col.AllFields["date_of_birth"]; var date = Convert.ToDateTime(o); column.ExtendedProperty += string.Format(" date_of_birth = {0}", date.ToLongDateString()); }*/ } } } // Callback to Settings, which can be set within .tt Settings.UpdateColumn?.Invoke(column, table, null); } public override void AddEnum(Table table) { } public override void UpdateEnum(Enumeration enumeration) { } public override void UpdateEnumMember(EnumerationMember enumerationMember) { } public override void ViewProcessing(Table view) { // Find the multi-context settings for this view var t = FindTableSetting(view.DbName, view.Schema.DbName); if (t != null) { // Find the multi-context columns which have a setting in IsPrimaryKey var requiredPrimaryKeys = t.Columns .Where(c => c.IsPrimaryKey.HasValue) .Select(c => new { Name = _normalisation.Normalise(c.Name, c.DbName).Name.ToLowerInvariant(), DbName = _normalisation.Normalise(c.DbName)?.Name.ToLowerInvariant(), IsPrimaryKey = c.IsPrimaryKey.Value }) .ToList(); if (!requiredPrimaryKeys.Any()) return; // Find the column settings which have an IsPrimaryKey set to true/false; var requiredFalse = requiredPrimaryKeys.Where(x => !x.IsPrimaryKey).ToList(); var requiredTrue = requiredPrimaryKeys.Where(x => x.IsPrimaryKey).ToList(); var dbNamesFalse = requiredFalse.Where(x => !string.IsNullOrEmpty(x.DbName)).Select(x => x.DbName).ToList(); var dbNamesTrue = requiredTrue .Where(x => !string.IsNullOrEmpty(x.DbName)).Select(x => x.DbName).ToList(); var colFalse = requiredFalse.Select(x => x.Name).ToList(); var colTrue = requiredTrue .Select(x => x.Name).ToList(); foreach (var col in view.Columns.Where(c => dbNamesFalse.Contains(c.DbName.ToLowerInvariant()) || colFalse.Contains(c.NameHumanCase.ToLowerInvariant()))) col.IsPrimaryKey = false; foreach (var col in view.Columns.Where(c => dbNamesTrue.Contains(c.DbName.ToLowerInvariant()) || colTrue.Contains(c.NameHumanCase.ToLowerInvariant()))) { col.IsPrimaryKey = true; col.IsNullable = false; } } // Callback to Settings, which can be set within .tt Settings.ViewProcessing?.Invoke(view); } public override string StoredProcedureRename(StoredProcedure sp) { var storedProcSetting = FindStoredProcSetting(sp); if (storedProcSetting != null) { if (storedProcSetting.AllFields != null) { Settings.MultiContextAllFieldsStoredProcedureProcessing?.Invoke(sp, storedProcSetting.AllFields); // Examples of how to use additional custom fields from the MultiContext.[Table] table // VARCHAR example /*if (storedProcSetting.AllFields.ContainsKey("CustomRename")) { var o = storedProcSetting.AllFields["CustomRename"]; sp.NameHumanCase = o.ToString(); }*/ } return storedProcSetting.Name; } var functionSetting = FindFunctionSetting(sp); if (functionSetting != null) { if (functionSetting.AllFields != null) { Settings.MultiContextAllFieldsFunctionProcessing?.Invoke(sp, functionSetting.AllFields); // Examples of how to use additional custom fields from the MultiContext.[Table] table // VARCHAR example /*if (functionSetting.AllFields.ContainsKey("CustomRename")) { var o = functionSetting.AllFields["CustomRename"]; sp.NameHumanCase = o.ToString(); }*/ } return functionSetting.Name; } // Callback to Settings, which can be set within .tt if (Settings.StoredProcedureRename != null) return Settings.StoredProcedureRename(sp); return sp.NameHumanCase; } public override string StoredProcedureReturnModelRename(string name, StoredProcedure sp) { var procSetting = FindStoredProcSetting(sp); if (procSetting != null && !string.IsNullOrWhiteSpace(procSetting.ReturnModel)) return procSetting.ReturnModel; // Callback to Settings, which can be set within .tt if (Settings.StoredProcedureReturnModelRename != null) return Settings.StoredProcedureReturnModelRename(name, sp); return name; } public override ForeignKey ForeignKeyFilter(ForeignKey fk) { // Return null to exclude this foreign key, or set IncludeReverseNavigation = false // to include the foreign key but not generate reverse navigation properties. // Example, to exclude all foreign keys for the Categories table, use: //if (fk.PkTableName == "Categories") // return null; // Example, to exclude reverse navigation properties for tables ending with Type, use: //if (fk.PkTableName.EndsWith("Type")) // fk.IncludeReverseNavigation = false; // You can also change the access modifier of the foreign-key's navigation property: //if(fk.PkTableName == "Categories") // fk.AccessModifier = "internal"; return fk; } public override string[] ForeignKeyAnnotationsProcessing(Table fkTable, Table pkTable, string propName, string fkPropName) { // Callback to Settings, which can be set within .tt if (Settings.ForeignKeyAnnotationsProcessing != null) return Settings.ForeignKeyAnnotationsProcessing(fkTable, pkTable, propName, fkPropName); return null; } private MultiContextTableSettings FindTableSetting(string name, string schema) { var search = $"{schema}.{name}".ToLowerInvariant(); return _settings.Tables .FirstOrDefault(t => search == _normalisation.Normalise(t.Name, t.DbName).ToString() || search == _normalisation.Normalise(t.DbName)?.ToString()); } private MultiContextColumnSettings FindColumnSetting(MultiContextTableSettings table, string dbName, string nameHumanCase) { return table.Columns .FirstOrDefault(t => nameHumanCase == _normalisation.Normalise(t.Name, t.DbName).Name.ToLowerInvariant() || dbName == _normalisation.Normalise(t.DbName)?.Name.ToLowerInvariant()); } private MultiContextStoredProcedureSettings FindStoredProcSetting(StoredProcedure sp) { var search = $"{sp.Schema.DbName}.{sp.DbName}".ToLowerInvariant(); return _settings.StoredProcedures .FirstOrDefault(t => search == _normalisation.Normalise(t.Name, t.DbName).ToString() || search == _normalisation.Normalise(t.DbName)?.ToString()); } private MultiContextFunctionSettings FindFunctionSetting(StoredProcedure sp) { var search = $"{sp.Schema.DbName}.{sp.DbName}".ToLowerInvariant(); return _settings.Functions .FirstOrDefault(t => search == _normalisation.Normalise(t.Name, t.DbName).ToString() || search == _normalisation.Normalise(t.DbName)?.ToString()); } } public class MultiContextNameNormalisation { public readonly string DefaultSchema; public MultiContextNameNormalisation(string defaultSchema) { DefaultSchema = string.IsNullOrWhiteSpace(defaultSchema) ? Settings.DefaultSchema?.Trim().ToLowerInvariant() : defaultSchema.Trim().ToLowerInvariant(); } public SchemaAndName Normalise(string name) { if (string.IsNullOrWhiteSpace(name)) return null; var parts = name.Split('.'); var numParts = parts.Length; var schema = numParts >= 2 ? parts[numParts - 2].Trim() : DefaultSchema; var final = parts[numParts - 1].Trim(); return new SchemaAndName(schema, final); } public SchemaAndName Normalise(string name, string dbName) { if (string.IsNullOrWhiteSpace(name)) return null; var db = Normalise(dbName); var parts = name.Split('.'); var numParts = parts.Length; var schema = db?.Schema ?? DefaultSchema; if (numParts >= 2) schema = parts[numParts - 2].Trim(); var final = parts[numParts - 1].Trim(); return new SchemaAndName(schema, final); } } // Unless table/column/stored proc/etc is explicitly listed here, it will be excluded. public class MultiContextSettings { public string Name { get; set; } public string Description { get; set; } public string Namespace { get; set; } // Optional public string TemplatePath { get; set; } public string Filename { get; set; } // Defaults to use if not specified for an object public string BaseSchema { get; set; } public Dictionary AllFields { get; set; } // Here you will find all fields, including any extra custom fields not listed above public List Tables { get; set; } public List StoredProcedures { get; set; } public List Functions { get; set; } public List Enumerations { get; set; } public List ForeignKeys { get; set; } public MultiContextSettings() { Tables = new List(); StoredProcedures = new List(); Functions = new List(); Enumerations = new List(); ForeignKeys = new List(); } public bool IncludeViews() { return Tables.Any(); } public bool IncludeStoredProcedures() { return StoredProcedures.Any(); } public bool IncludeFunctions() { return Functions.Any(); } public string GetNamespace() { if (!string.IsNullOrEmpty(Namespace)) return Namespace; return !string.IsNullOrEmpty(Name) ? Name : string.Empty; } } public class MultiContextTableSettings { public string Name { get; set; } public string Description { get; set; } // [optional] Comment added to table class public string PluralName { get; set; } // [optional] Override auto-plural name public string DbName { get; set; } // [optional] Name of table in database. Specify only if the db table name is different from the "Name" property. public string Attributes { get; set; } // [optional] Use a tilda ~ delimited list of attributes to add to this table property. e.g. [CustomSecurity(Security.ReadOnly)]~[AnotherAttribute]~[Etc] // The tilda ~ delimiter used in Attributes can be changed if you set Settings.MultiContextAttributeDelimiter = '~'; to something else. public string DbSetModifier { get; set; } // [optional] Will override setting of table.DbSetModifier. Default is "public". public Dictionary AllFields { get; set; } // Here you will find all fields, including any extra custom fields not listed above public List Columns { get; set; } } public class MultiContextColumnSettings { public string Name { get; set; } public string DbName { get; set; } // [optional] Name of column in database. Specify only if the db column name is different from the "Name" property. public bool? IsPrimaryKey { get; set; } // [optional] Useful for views as views don't have primary keys. public bool? OverrideModifier { get; set; } // [optional] Adds "override" modifier. public string EnumType { get; set; } // [optional] Use enum type instead of data type public string Attributes { get; set; } // [optional] Use a tilda ~ delimited list of attributes to add to a poco property. e.g. [CustomSecurity(Security.ReadOnly)]~[Required] public string PropertyType { get; set; } // [optional] Will override setting of column.PropertyType public bool? IsNullable { get; set; } // [optional] Will override setting of column.IsNullable public Dictionary AllFields { get; set; } // Here you will find all fields, including any extra custom fields not listed above } public class MultiContextStoredProcedureSettings { public string Name { get; set; } public string DbName { get; set; } // [optional] Name of stored proc in database. Specify only if the db stored proc name is different from the "Name" property. public string ReturnModel { get; set; } // [optional] Specify a return model for stored proc public Dictionary AllFields { get; set; } // Here you will find all fields, including any extra custom fields not listed above } public class MultiContextFunctionSettings { public string Name { get; set; } public string DbName { get; set; } // [optional] Name of function in database. Specify only if the db function name is different from the "Name" property. public Dictionary AllFields { get; set; } // Here you will find all fields, including any extra custom fields not listed above } /// /// Create enumeration from database table /// public enum Name /// { /// NameField = ValueField, /// etc /// } /// public class EnumerationSettings { public string Name { get; set; } // Enum to generate. e.g. "DaysOfWeek" would result in "public enum DaysOfWeek {...}" if the GroupField is set to a value then {GroupField} must be used in this name. e.g. "DaysOfWeek{GroupField}" public string Table { get; set; } // Database table containing enum values. e.g. "DaysOfWeek" public string NameField { get; set; } // Column containing the name for the enum. e.g. "TypeName" public string ValueField { get; set; } // Column containing the values for the enum. e.g. "TypeId" public string GroupField { get; set; } // [optional] Column containing the group name for the enum. This is used if multiple Enums are in the same table. if this is populated, use {GroupField} in the Name property. e.g. "{GroupField}Enum" public Dictionary AllFields { get; set; } // Here you will find all fields, including any extra custom fields not listed above } /// /// Existing foreign keys will be read and used as normal from the source database, however you can specify extra foreign keys here. /// Define extra navigation relationships, such as views, since views don’t have relationships. /// Specify names as defined in the database, not how they will be named in C# /// public class MultiContextForeignKeySettings { public string ConstraintName { get; set; } // Name of the foreign key public string ParentName { get; set; } // [optional] Name of the parent foreign key property. If NULL it will be generated. public string ChildName { get; set; } // [optional] Name of the child foreign key property. If NULL it will be generated. public string PkSchema { get; set; } // [optional] Will default to MultiContext.Context.BaseSchema public string PkTableName { get; set; } public string PkColumn { get; set; } public string FkSchema { get; set; } // [optional] Will default to MultiContext.Context.BaseSchema public string FkTableName { get; set; } public string FkColumn { get; set; } public int Ordinal { get; set; } // Order of this item public bool CascadeOnDelete { get; set; } // If false will add .WillCascadeOnDelete(false) public bool IsNotEnforced { get; set; } // If not enforced, it means foreign key is optional. .HasOptional(...) or .HasRequired(...) public bool HasUniqueConstraint { get; set; } // True if this FK points to columns that have a unique constraint against them } public class PeriodFilter : IFilterType { public bool IsExcluded(EntityName item) { return item.DbName.Contains('.'); } } /// /// Items matching the regex are excluded. /// public class RegexExcludeFilter : IFilterType { private readonly Regex _filter; /// /// A standard Regex will be created for the exclude filter. /// public RegexExcludeFilter(string filterExclude) { _filter = new Regex(filterExclude); } /// /// Allow you to provide your own custom defined Regex /// public RegexExcludeFilter(Regex filter) { _filter = filter; } public bool IsExcluded(EntityName item) { return _filter.IsMatch(item.DbName); } } /// /// Items matching the regex are included. /// public class RegexIncludeFilter : IFilterType { private readonly Regex _filter; /// /// A standard Regex will be created for the include filter. /// public RegexIncludeFilter(string filterInclude) { _filter = new Regex(filterInclude); } /// /// Allow you to provide your own custom defined Regex /// public RegexIncludeFilter(Regex filter) { _filter = filter; } public bool IsExcluded(EntityName item) { return !_filter.IsMatch(item.DbName); } public string Pattern() { return _filter.ToString(); } } public class SchemaAndName { public string Schema; public string Name; public SchemaAndName(string schema, string name) { Schema = schema; Name = name; } public override string ToString() { return string.IsNullOrEmpty(Name) ? string.Empty : $"{Schema}.{Name}".ToLowerInvariant(); } } public class SchemaFilter : IFilterType { // Filtering of schema using a function. // Return true to exclude the schema, return false to include it public bool IsExcluded(EntityName schema) { // Exclude schema with a name of 'MultiContext' as this is reserved by the generator for multi-context generation. // See https://github.com/sjh37/EntityFramework-Reverse-POCO-Code-First-Generator/wiki/Generating-multiple-database-contexts-in-a-single-go if (schema.DbName.Equals("MultiContext", StringComparison.InvariantCultureIgnoreCase)) return true; // Example: Exclude any schema with 'audit' anywhere in its name. //if (schema.Name.ToLowerInvariant().Contains("audit")) // return true; return false; } } public class StoredProcedureFilter : IFilterType { // Filtering of stored procedures using a function. // Return true to exclude the stored procedure, return false to include it. public bool IsExcluded(StoredProcedure sp) { // Example: Exclude any stored procedure in dbo schema with "order" in its name. //if(sp.Schema.DbName.Equals("dbo", StringComparison.InvariantCultureIgnoreCase) && sp.NameHumanCase.ToLowerInvariant().Contains("order")) // return false; return false; } } public class TableFilter : IFilterType
{ // Filtering of tables using a function. // Return true to exclude the table, return false to include it. public bool IsExcluded(Table t) { // Example: Exclude any table in 'dbo' schema and with 'order' anywhere in its name. //if(t.Schema.DbName.Equals("dbo", StringComparison.InvariantCultureIgnoreCase) && t.DbName.ToLowerInvariant().Contains("order")) // return true; return false; } } public class ForeignKey { public readonly string FkTableName; public readonly string FkSchema; public readonly string PkTableName; public readonly string FkTableNameFiltered; public readonly string PkTableNameFiltered; public readonly string PkSchema; public readonly string FkColumn; public readonly string PkColumn; public readonly string ConstraintName; public readonly int Ordinal; public readonly bool CascadeOnDelete; public readonly string ParentName; public readonly string ChildName; public readonly bool HasUniqueConstraint; // Record unique name for this foreign key public string UniqueName; // User settable via ForeignKeyFilter callback public string AccessModifier { get; set; } public bool IncludeReverseNavigation { get; set; } public bool IsNotEnforced { get; set; } public ForeignKey(string fkTableName, string fkSchema, string pkTableName, string pkSchema, string fkColumn, string pkColumn, string constraintName, string fkTableNameFiltered, string pkTableNameFiltered, int ordinal, bool cascadeOnDelete, bool isNotEnforced, string parentName, string childName, bool hasUniqueConstraint) { ConstraintName = constraintName; ParentName = parentName; ChildName = childName; PkColumn = pkColumn; FkColumn = fkColumn; PkSchema = pkSchema; PkTableName = pkTableName; FkSchema = fkSchema; FkTableName = fkTableName; FkTableNameFiltered = fkTableNameFiltered; PkTableNameFiltered = pkTableNameFiltered; Ordinal = ordinal; CascadeOnDelete = cascadeOnDelete; IsNotEnforced = isNotEnforced; HasUniqueConstraint = hasUniqueConstraint; UniqueName = string.Empty; IncludeReverseNavigation = true; } public ForeignKey(RawForeignKey rfk, string fkTableNameFiltered, string pkTableNameFiltered) { ConstraintName = rfk.ConstraintName; ParentName = rfk.ParentName; ChildName = rfk.ChildName; PkColumn = rfk.PkColumn; FkColumn = rfk.FkColumn; PkSchema = rfk.PkSchema; PkTableName = rfk.PkTableName; FkSchema = rfk.FkSchema; FkTableName = rfk.FkTableName; FkTableNameFiltered = fkTableNameFiltered; PkTableNameFiltered = pkTableNameFiltered; Ordinal = rfk.Ordinal; CascadeOnDelete = rfk.CascadeOnDelete; IsNotEnforced = rfk.IsNotEnforced; HasUniqueConstraint = rfk.HasUniqueConstraint; UniqueName = string.Empty; IncludeReverseNavigation = true; } public string PkTableHumanCase(string suffix) { var singular = Inflector.MakeSingular(DatabaseReader.CleanUp(PkTableNameFiltered)); var pkTableHumanCase = (Settings.UsePascalCase ? Inflector.ToTitleCase(singular) : singular) .Replace(" ", string.Empty) .Replace("$", string.Empty); if (string.Compare(PkSchema, Settings.DefaultSchema, StringComparison.OrdinalIgnoreCase) != 0 && Settings.PrependSchemaName) pkTableHumanCase = PkSchema + "_" + pkTableHumanCase; pkTableHumanCase += suffix; return pkTableHumanCase; } } public enum ForeignKeyNamingStrategy { Legacy, // Same as versions <= v3.9.0 Latest } public class BaseForeignKeyNamingStrategy { protected readonly Table _table; protected readonly IDbContextFilter _filter; public BaseForeignKeyNamingStrategy(IDbContextFilter filter, Table table) { _filter = filter; _table = table; } protected static string CheckForUserSpecifiedName(bool isParent, ForeignKey foreignKey) { // User specified name via AddRelationship if (isParent && !string.IsNullOrEmpty(foreignKey.ParentName)) return foreignKey.ParentName; // User specified name via AddRelationship if (!isParent && !string.IsNullOrEmpty(foreignKey.ChildName)) return foreignKey.ChildName; return null; } } public static class ForeignKeyNamingStrategyFactory { public static IForeignKeyNamingStrategy Create(IDbContextFilter filter, Table table) { switch (Settings.ForeignKeyNamingStrategy) { case ForeignKeyNamingStrategy.Legacy: return new LegacyForeignKeyNamingStrategy(filter, table); default: return new LatestForeignKeyNamingStrategy(filter, table); } } } public interface IForeignKeyNamingStrategy { string GetUniqueForeignKeyName(bool isParent, string tableNameHumanCase, ForeignKey foreignKey, bool checkForFkNameClashes, bool makeSingular, Relationship relationship); void ResetNavigationProperties(); } // Not complete. Please use LegacyForeignKeyNamingStrategy public class LatestForeignKeyNamingStrategy : BaseForeignKeyNamingStrategy, IForeignKeyNamingStrategy { public LatestForeignKeyNamingStrategy(IDbContextFilter filter, Table table) : base(filter, table) { } public string GetUniqueForeignKeyName(bool isParent, string tableNameHumanCase, ForeignKey foreignKey, bool checkForFkNameClashes, bool makeSingular, Relationship relationship) { var userSpecifiedName = CheckForUserSpecifiedName(isParent, foreignKey); if (!string.IsNullOrEmpty(userSpecifiedName)) return userSpecifiedName; var addReverseNavigationUniquePropName = checkForFkNameClashes && (_table.DbName == foreignKey.FkTableName || (_table.DbName == foreignKey.PkTableName && foreignKey.IncludeReverseNavigation)); // Attempt 1 var fkName = (Settings.UsePascalCase ? Inflector.ToTitleCase(foreignKey.FkColumn) : foreignKey.FkColumn).Replace(" ", string.Empty).Replace("$", string.Empty); var name = Settings.ForeignKeyName(tableNameHumanCase, foreignKey, fkName, relationship, 1); if (addReverseNavigationUniquePropName || !checkForFkNameClashes) { foreignKey.UniqueName = name; } // todo return name; } public void ResetNavigationProperties() { // todo } } // v0.0.0 <= v3.9.0 public class LegacyForeignKeyNamingStrategy : BaseForeignKeyNamingStrategy, IForeignKeyNamingStrategy { public List ReverseNavigationUniquePropName; public List ReverseNavigationUniquePropNameClashes; public LegacyForeignKeyNamingStrategy(IDbContextFilter filter, Table table) : base(filter, table) { ReverseNavigationUniquePropNameClashes = new List(); } public string GetUniqueForeignKeyName(bool isParent, string tableNameHumanCase, ForeignKey foreignKey, bool checkForFkNameClashes, bool makeSingular, Relationship relationship) { var userSpecifiedName = CheckForUserSpecifiedName(isParent, foreignKey); if (!string.IsNullOrEmpty(userSpecifiedName)) return userSpecifiedName; var addReverseNavigationUniquePropName = checkForFkNameClashes && (_table.DbName == foreignKey.FkTableName || (_table.DbName == foreignKey.PkTableName && foreignKey.IncludeReverseNavigation)); // Generate name if (ReverseNavigationUniquePropName.Count == 0) { // Reserve table name and all column names ReverseNavigationUniquePropName.Add(_table.NameHumanCase); ReverseNavigationUniquePropName.AddRange(_table.Columns.Select(c => c.NameHumanCase)); } if (!makeSingular) tableNameHumanCase = Inflector.MakePlural(tableNameHumanCase); if (checkForFkNameClashes && ReverseNavigationUniquePropName.Contains(tableNameHumanCase) && !ReverseNavigationUniquePropNameClashes.Contains(tableNameHumanCase)) { ReverseNavigationUniquePropNameClashes.Add(tableNameHumanCase); // Name clash } // Attempt 1 var fkName = (Settings.UsePascalCase ? Inflector.ToTitleCase(foreignKey.FkColumn) : foreignKey.FkColumn).Replace(" ", string.Empty).Replace("$", string.Empty); var name = Settings.ForeignKeyName(tableNameHumanCase, foreignKey, fkName, relationship, 1); string col; if (!ReverseNavigationUniquePropName.Contains(name) && !ReverseNavigationUniquePropNameClashes.Contains(name)) { if (addReverseNavigationUniquePropName || !checkForFkNameClashes) { ReverseNavigationUniquePropName.Add(name); foreignKey.UniqueName = name; } return name; } if (_table.DbName == foreignKey.FkTableName) { // Attempt 2 if (fkName.Length > 2 && fkName.ToLowerInvariant().EndsWith("id")) { col = Settings.ForeignKeyName(tableNameHumanCase, foreignKey, fkName, relationship, 2); if (checkForFkNameClashes && ReverseNavigationUniquePropName.Contains(col) && !ReverseNavigationUniquePropNameClashes.Contains(col)) { ReverseNavigationUniquePropNameClashes.Add(col); // Name clash } if (!ReverseNavigationUniquePropName.Contains(col) && !ReverseNavigationUniquePropNameClashes.Contains(col)) { if (addReverseNavigationUniquePropName || !checkForFkNameClashes) { ReverseNavigationUniquePropName.Add(col); } return col; } } // Attempt 3 col = Settings.ForeignKeyName(tableNameHumanCase, foreignKey, fkName, relationship, 3); if (checkForFkNameClashes && ReverseNavigationUniquePropName.Contains(col) && !ReverseNavigationUniquePropNameClashes.Contains(col)) { ReverseNavigationUniquePropNameClashes.Add(col); // Name clash } if (!ReverseNavigationUniquePropName.Contains(col) && !ReverseNavigationUniquePropNameClashes.Contains(col)) { if (addReverseNavigationUniquePropName || !checkForFkNameClashes) { ReverseNavigationUniquePropName.Add(col); } return col; } } // Attempt 4 col = Settings.ForeignKeyName(tableNameHumanCase, foreignKey, fkName, relationship, 4); if (checkForFkNameClashes && ReverseNavigationUniquePropName.Contains(col) && !ReverseNavigationUniquePropNameClashes.Contains(col)) { ReverseNavigationUniquePropNameClashes.Add(col); // Name clash } if (!ReverseNavigationUniquePropName.Contains(col) && !ReverseNavigationUniquePropNameClashes.Contains(col)) { if (addReverseNavigationUniquePropName || !checkForFkNameClashes) { ReverseNavigationUniquePropName.Add(col); } return col; } // Attempt 5 for (var n = 1; n < 99; ++n) { col = Settings.ForeignKeyName(tableNameHumanCase, foreignKey, fkName, relationship, 5) + n; if (ReverseNavigationUniquePropName.Contains(col)) continue; if (addReverseNavigationUniquePropName || !checkForFkNameClashes) { ReverseNavigationUniquePropName.Add(col); } return col; } // Give up return Settings.ForeignKeyName(tableNameHumanCase, foreignKey, fkName, relationship, 6); } public void ResetNavigationProperties() { ReverseNavigationUniquePropName = new List(); } } public class CodeGenerator { private readonly Generator _generator; private readonly IDbContextFilter _filter; private readonly List _tables; private readonly List _storedProcs; private readonly List _globalUsings; private readonly Template _template; private readonly List _tableValuedFunctions; private readonly List _scalarValuedFunctions; private readonly List _tableValuedFunctionComplexTypes; private readonly bool _hasTables, _hasStoredProcs, _hasTableValuedFunctions, _hasScalarValuedFunctions, _hasTableValuedFunctionComplexTypes, _hasEnums; public CodeGenerator(Generator generator, IDbContextFilter filter) { #pragma warning disable IDE0016 // Use 'throw' expression if (generator == null) throw new ArgumentNullException(nameof(generator)); if (filter == null) throw new ArgumentNullException(nameof(filter)); #pragma warning restore IDE0016 // Use 'throw' expression var isEfCore = Settings.GeneratorType == GeneratorType.EfCore; var isEfCore3Plus = Settings.IsEfCore3Plus(); _generator = generator; _filter = filter; _tables = filter.Tables .Where(t => !t.IsMapping && (t.HasPrimaryKey || (t.IsView && isEfCore3Plus))) .OrderBy(x => x.NameHumanCase) .Select(tbl => new TableTemplateData(tbl)) .ToList(); if (filter.IncludeStoredProcedures) { _storedProcs = filter.StoredProcs .Where(s => s.IsStoredProcedure) .OrderBy(x => x.NameHumanCase) .Select(sp => new StoredProcTemplateData( sp.ReturnModels.Count == 0, sp.ReturnModels.Count > 0, sp.ReturnModels.Count == 1, sp.ReturnModels.Count > 1, sp.WriteStoredProcReturnType(_filter), sp.WriteStoredProcReturnModelName(filter), sp.WriteStoredProcFunctionName(filter), sp.WriteStoredProcFunctionParams(false, false, false), sp.WriteStoredProcFunctionParams(false, true, false), sp.WriteStoredProcFunctionParams(true, false, false), sp.WriteStoredProcFunctionParams(true, true, false), sp.WriteStoredProcFunctionParams(false, false, true), sp.WriteStoredProcFunctionParams(false, true, true), sp.WriteStoredProcFunctionParams(true, false, true), sp.WriteStoredProcFunctionParams(true, true, true), !sp.StoredProcCanExecuteAsync(), sp.WriteStoredProcFunctionOverloadCall(), sp.WriteStoredProcFunctionSetSqlParameters(false), sp.WriteStoredProcFunctionSetSqlParameters(true), sp.ReturnModels.Count == 1 ? // exec string.Format("EXEC @procResult = [{0}].[{1}] {2}", sp.Schema.DbName, sp.DbName, sp.WriteStoredProcFunctionSqlAtParams()).Trim() : string.Format("[{0}].[{1}]", sp.Schema.DbName, sp.DbName), sp.ReturnModels.Count == 0 ? // Async exec string.Format("EXEC @procResult = [{0}].[{1}] {2}", sp.Schema.DbName, sp.DbName, sp.WriteStoredProcFunctionSqlAtParams()).Trim() : sp.ReturnModels.Count == 1 ? string.Format("EXEC [{0}].[{1}] {2}", sp.Schema.DbName, sp.DbName, sp.WriteStoredProcFunctionSqlAtParams()).Trim() : string.Format("[{0}].[{1}]", sp.Schema.DbName, sp.DbName), sp.WriteStoredProcReturnModelName(_filter), sp.WriteStoredProcFunctionSqlParameterAnonymousArray(true, true, false), sp.WriteStoredProcFunctionSqlParameterAnonymousArray(false, true, false), sp.WriteStoredProcFunctionSqlParameterAnonymousArray(true, true, true, isEfCore3Plus), sp.WriteStoredProcFunctionSqlParameterAnonymousArray(false, true, true, isEfCore3Plus), sp.WriteStoredProcFunctionDeclareSqlParameter(true), sp.WriteStoredProcFunctionDeclareSqlParameter(false), sp.Parameters.OrderBy(x => x.Ordinal).Select(sp.WriteStoredProcSqlParameterName).ToList(), sp.ReturnModels.Count, string.Format("EXEC @procResult = [{0}].[{1}] {2}", sp.Schema.DbName, sp.DbName, sp.WriteStoredProcFunctionSqlAtParams()) )) .ToList(); } else _storedProcs = new List(); if (filter.IncludeTableValuedFunctions) { _tableValuedFunctions = filter.StoredProcs .Where(s => s.IsTableValuedFunction) .OrderBy(x => x.NameHumanCase) .Select(tvf => new TableValuedFunctionsTemplateData( tvf.ReturnModels.Count == 1 && tvf.ReturnModels[0].Count == 1, tvf.ReturnModels.Count == 1 && tvf.ReturnModels[0].Count == 1 ? tvf.ReturnModels[0][0].ColumnName : null, tvf.WriteStoredProcFunctionName(_filter), tvf.WriteStoredProcReturnModelName(_filter), tvf.WriteStoredProcFunctionParams(false, true), tvf.WriteStoredProcFunctionParams(false, false), tvf.DbName, tvf.Schema.DbName, isEfCore ? tvf.WriteStoredProcFunctionDeclareSqlParameter(false) : tvf.WriteTableValuedFunctionDeclareSqlParameter(), isEfCore ? tvf.WriteStoredProcFunctionSqlParameterAnonymousArray(false, false) : tvf.WriteTableValuedFunctionSqlParameterAnonymousArray(), isEfCore ? tvf.WriteNetCoreTableValuedFunctionsSqlAtParams() : tvf.WriteStoredProcFunctionSqlAtParams(), isEfCore3Plus ? "FromSqlRaw" : "FromSql", isEfCore3Plus ? "Set" : "Query", isEfCore3Plus ? "Entity" : "Query", isEfCore3Plus ? ".HasNoKey()" : string.Empty, !Settings.StoredProcedureReturnTypes.ContainsKey(tvf.NameHumanCase) && !Settings.StoredProcedureReturnTypes.ContainsKey(tvf.DbName) )) .ToList(); _tableValuedFunctionComplexTypes = filter.StoredProcs .Where(s => s.IsTableValuedFunction && !Settings.StoredProcedureReturnTypes.ContainsKey(s.NameHumanCase) && !Settings.StoredProcedureReturnTypes.ContainsKey(s.DbName)) .OrderBy(x => x.NameHumanCase) .Select(x => x.WriteStoredProcReturnModelName(_filter)) .ToList(); } else { _tableValuedFunctions = new List(); _tableValuedFunctionComplexTypes = new List(); } if (filter.IncludeScalarValuedFunctions) { _scalarValuedFunctions = filter.StoredProcs .Where(s => s.IsScalarValuedFunction && s.Parameters.Any(x => x.Mode == StoredProcedureParameterMode.Out)) .OrderBy(x => x.NameHumanCase) .Select(svf => new ScalarValuedFunctionsTemplateData( svf.WriteStoredProcFunctionName(_filter), svf.Parameters.Where(x => x.Mode == StoredProcedureParameterMode.Out).OrderBy(x => x.Ordinal).FirstOrDefault()?.PropertyType, svf.WriteStoredProcFunctionParams(false, true), svf.WriteStoredProcFunctionParams(false, false), svf.DbName, svf.Schema.DbName )) .ToList(); } else _scalarValuedFunctions = new List(); var returnModelsUsed = new List(); foreach (var sp in _storedProcs) { if (returnModelsUsed.Contains(sp.ReturnModelName)) sp.CreateDbSetForReturnModel = false; else returnModelsUsed.Add(sp.ReturnModelName); } _hasTables = _tables.Any(); _hasStoredProcs = _storedProcs.Any(); _hasTableValuedFunctions = _tableValuedFunctions.Any(); _hasScalarValuedFunctions = _scalarValuedFunctions.Any(); _hasTableValuedFunctionComplexTypes = _tableValuedFunctionComplexTypes.Any(); _hasEnums = filter.Enums.Any(); _globalUsings = new List(); _template = TemplateFactory.Create(); CalcGlobalUsings(); } private void CalcGlobalUsings() { _globalUsings.AddRange(Settings.AdditionalNamespaces.Where(x => !string.IsNullOrEmpty(x)).Distinct()); if ((Settings.ElementsToGenerate.HasFlag(Elements.PocoConfiguration) || Settings.ElementsToGenerate.HasFlag(Elements.Context) || Settings.ElementsToGenerate.HasFlag(Elements.Interface)) && (!Settings.ElementsToGenerate.HasFlag(Elements.Poco) && !string.IsNullOrWhiteSpace(Settings.PocoNamespace))) _globalUsings.Add(Settings.PocoNamespace); if (Settings.ElementsToGenerate.HasFlag(Elements.PocoConfiguration) && (!Settings.ElementsToGenerate.HasFlag(Elements.Context) && !string.IsNullOrWhiteSpace(Settings.ContextNamespace))) _globalUsings.Add(Settings.ContextNamespace); if (Settings.ElementsToGenerate.HasFlag(Elements.Context) && (!Settings.ElementsToGenerate.HasFlag(Elements.Interface) && !string.IsNullOrWhiteSpace(Settings.InterfaceNamespace))) _globalUsings.Add(Settings.InterfaceNamespace); if (Settings.ElementsToGenerate.HasFlag(Elements.Context) && (!Settings.ElementsToGenerate.HasFlag(Elements.PocoConfiguration) && !string.IsNullOrWhiteSpace(Settings.PocoConfigurationNamespace))) _globalUsings.Add(Settings.PocoConfigurationNamespace); } private bool CanWriteInterface() { return Settings.ElementsToGenerate.HasFlag(Elements.Interface) && !string.IsNullOrWhiteSpace(Settings.DbContextInterfaceName) && (_hasTables || _hasStoredProcs || _hasTableValuedFunctions || _hasScalarValuedFunctions); } private bool CanWriteFactory() { return Settings.ElementsToGenerate.HasFlag(Elements.Context) && Settings.AddIDbContextFactory && (_hasTables || _hasStoredProcs || _hasTableValuedFunctions || _hasScalarValuedFunctions); } private bool CanWriteContext() { return Settings.ElementsToGenerate.HasFlag(Elements.Context) && (_hasTables || _hasStoredProcs || _hasTableValuedFunctions || _hasScalarValuedFunctions); } private bool CanWriteFakeContext() { return Settings.AddUnitTestingDbContext && Settings.ElementsToGenerate.HasFlag(Elements.Context) && (_hasTables || _hasStoredProcs || _hasTableValuedFunctions || _hasScalarValuedFunctions); } private bool CanWritePoco() { return Settings.ElementsToGenerate.HasFlag(Elements.Poco) && _hasTables; } private bool CanWritePocoConfiguration() { return Settings.ElementsToGenerate.HasFlag(Elements.PocoConfiguration) && _hasTables; } private bool CanWriteStoredProcReturnModel() { return Settings.ElementsToGenerate.HasFlag(Elements.Poco) && (_hasStoredProcs || _hasTableValuedFunctions); } private bool CanWriteEnums() { return Settings.ElementsToGenerate.HasFlag(Elements.Enum) && _hasEnums; } public string GenerateUsings(List usings) { return !usings.Any() ? null : Template.Transform(_template.Usings(), usings).Trim(); } public CodeOutput GenerateInterface() { var filename = Settings.DbContextInterfaceName + Settings.FileExtension; if (!CanWriteInterface()) { FileManagementService.DeleteFile(filename); return null; } var data = new InterfaceModel { interfaceModifier = Settings.DbContextInterfaceModifiers ?? "public partial", DbContextInterfaceName = Settings.DbContextInterfaceName, DbContextInterfaceBaseClasses = Settings.DbContextInterfaceBaseClasses, DbContextName = Settings.DbContextName, tables = _tables.Where(x => x.DbSetModifier == "public").ToList(), AdditionalContextInterfaceItems = Settings.AdditionalContextInterfaceItems.Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList(), addSaveChanges = !Settings.UseInheritedBaseInterfaceFunctions, storedProcs = _storedProcs, hasStoredProcs = _hasStoredProcs, tableValuedFunctions = _tableValuedFunctions, scalarValuedFunctions = _scalarValuedFunctions, hasTableValuedFunctions = _hasTableValuedFunctions && _filter.IncludeTableValuedFunctions, hasScalarValuedFunctions = _hasScalarValuedFunctions && _filter.IncludeScalarValuedFunctions, }; var co = new CodeOutput(string.Empty, filename, "Database context interface", Settings.InterfaceFolder, _globalUsings); co.AddUsings(_template.DatabaseContextInterfaceUsings(data)); co.AddCode(Template.Transform(_template.DatabaseContextInterface(), data)); return co; } public CodeOutput GenerateContext() { var filename = Settings.DbContextName + Settings.FileExtension; if (!CanWriteContext()) { FileManagementService.DeleteFile(filename); return null; } var indexes = new List(); var hasSpatialTypes = false; var hasHierarchyIdType = false; foreach (var table in _tables) { var columns = table.Table.Columns .Where(x => !x.Hidden && !string.IsNullOrEmpty(x.Config)) .OrderBy(x => x.Ordinal) .ToList(); if (!Settings.DisableGeographyTypes && !hasSpatialTypes) hasSpatialTypes = columns.Any(x => x.IsSpatial); if (!hasHierarchyIdType && columns.Any(x => x.SqlPropertyType.Equals("hierarchyid", StringComparison.InvariantCultureIgnoreCase))) hasHierarchyIdType = true; indexes.AddRange(columns .Select(_generator.IndexModelBuilder) .Where(x => !string.IsNullOrWhiteSpace(x))); } var isEfCore3Plus = Settings.IsEfCore3Plus(); var data = new ContextModel { DbContextClassModifiers = Settings.DbContextClassModifiers, DbContextName = Settings.DbContextName, DbContextBaseClass = Settings.DbContextBaseClass, AddParameterlessConstructorToDbContext = Settings.AddParameterlessConstructorToDbContext, HasDefaultConstructorArgument = !string.IsNullOrEmpty(Settings.DefaultConstructorArgument), DefaultConstructorArgument = Settings.DefaultConstructorArgument, ConfigurationClassName = Settings.ConfigurationClassName, ConnectionString = Settings.ConnectionString, ConnectionStringName = Settings.ConnectionStringName, ConnectionStringActions = GetConnectionStringActions(hasSpatialTypes, hasHierarchyIdType), contextInterface = string.IsNullOrWhiteSpace(Settings.DbContextInterfaceName) ? "" : ", " + Settings.DbContextInterfaceName, setInitializer = string.Format("<{0}>(null);", Settings.DbContextName), DbContextClassIsPartial = Settings.DbContextClassIsPartial(), SqlCe = Settings.DatabaseType == DatabaseType.SqlCe, tables = _tables, hasTables = _hasTables, indexes = indexes, storedProcs = _storedProcs, hasStoredProcs = _hasStoredProcs, tableValuedFunctionComplexTypes = _tableValuedFunctionComplexTypes, hasTableValuedFunctionComplexTypes = _hasTableValuedFunctionComplexTypes, AdditionalContextInterfaceItems = Settings.AdditionalContextInterfaceItems.Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList(), addSaveChanges = !Settings.UseInheritedBaseInterfaceFunctions, tableValuedFunctions = _tableValuedFunctions, scalarValuedFunctions = _scalarValuedFunctions, Sequences = _filter.Sequences, hasTableValuedFunctions = _hasTableValuedFunctions && _filter.IncludeTableValuedFunctions, hasScalarValuedFunctions = _hasScalarValuedFunctions && _filter.IncludeScalarValuedFunctions, IncludeObjectContextConstructor = !Settings.DbContextBaseClass.Contains("IdentityDbContext"), QueryString = isEfCore3Plus ? "Set" : "Query", FromSql = isEfCore3Plus ? "FromSqlRaw" : "FromSql", ExecuteSqlCommand = isEfCore3Plus ? "ExecuteSqlRaw" : "ExecuteSqlCommand", StoredProcModelBuilderCommand = isEfCore3Plus ? "Entity" : "Query", StoredProcModelBuilderPostCommand = isEfCore3Plus ? ".HasNoKey()" : string.Empty, OnConfigurationUsesConfiguration = Settings.OnConfiguration == OnConfiguration.Configuration, OnConfigurationUsesConnectionString = Settings.OnConfiguration == OnConfiguration.ConnectionString, DefaultSchema = Settings.DefaultSchema, UseDatabaseProvider = Settings.DatabaseProvider(), UseLazyLoadingProxies = Settings.UseLazyLoading && Settings.IsEfCore3Plus(), SqlParameter = Settings.SqlParameter(), SqlParameterValue = Settings.SqlParameterValue(), Triggers = _tables.Where(x => !string.IsNullOrEmpty(x.Table.TriggerName) || x.Table.Columns.Any(c => c.IsComputed)) .Select(x => new Trigger { TableName = x.Table.NameHumanCase, TriggerName = x.Table.TriggerName ?? "HasComputedColumn" }).ToList(), MemoryOptimisedTables = _tables.Where(x => x.Table.IsMemoryOptimised).Select(x => x.Table.NameHumanCase).ToList() }; data.hasIndexes = data.indexes.Any(); data.hasTriggers = data.Triggers.Any(); data.hasSequences = data.Sequences.Any(); data.hasMemoryOptimisedTables = data.MemoryOptimisedTables.Any(); var co = new CodeOutput(string.Empty, filename, "Database context", Settings.ContextFolder, _globalUsings); co.AddUsings(_template.DatabaseContextUsings(data)); co.AddCode(Template.Transform(_template.DatabaseContext(), data)); return co; } private static string GetConnectionStringActions(bool hasSpatialTypes, bool hasHierarchyIdType) { switch (Settings.TemplateType) { case TemplateType.Ef6: return string.Empty; case TemplateType.EfCore3: hasHierarchyIdType = false; break; } if (!hasSpatialTypes && !hasHierarchyIdType) return string.Empty; if (hasSpatialTypes && hasHierarchyIdType) return ", x => x.UseNetTopologySuite().UseHierarchyId()"; return hasSpatialTypes ? ", x => x.UseNetTopologySuite()" : ", x => x.UseHierarchyId()"; } public CodeOutput GenerateFakeContext() { var filename = "Fake" + Settings.DbContextName + Settings.FileExtension; if (!CanWriteFakeContext()) { FileManagementService.DeleteFile(filename); return null; } var data = new FakeContextModel { DbContextClassModifiers = Settings.DbContextClassModifiers, DbContextName = Settings.DbContextName, DbContextBaseClass = Settings.DbContextBaseClass, contextInterface = string.IsNullOrWhiteSpace(Settings.DbContextInterfaceName) ? "" : " : " + Settings.DbContextInterfaceName, DbContextClassIsPartial = Settings.DbContextClassIsPartial(), tables = _tables, storedProcs = _storedProcs, hasStoredProcs = _hasStoredProcs, tableValuedFunctions = _tableValuedFunctions, scalarValuedFunctions = _scalarValuedFunctions, hasTableValuedFunctions = _hasTableValuedFunctions && _filter.IncludeTableValuedFunctions, hasScalarValuedFunctions = _hasScalarValuedFunctions && _filter.IncludeScalarValuedFunctions, }; var co = new CodeOutput(string.Empty, filename, "Fake Database context", Settings.ContextFolder, _globalUsings); co.AddUsings(_template.FakeDatabaseContextUsings(data, _filter)); co.AddCode(Template.Transform(_template.FakeDatabaseContext(), data)); return co; } public CodeOutput GenerateFakeDbSet() { var filename = "FakeDbSet" + Settings.FileExtension; if (!CanWriteFakeContext()) { FileManagementService.DeleteFile(filename); return null; } var data = new FakeDbSetModel { DbContextClassModifiers = Settings.DbContextClassModifiers, DbContextClassIsPartial = Settings.DbContextClassIsPartial(), }; var co = new CodeOutput(string.Empty, filename, "Fake DbSet", Settings.ContextFolder, _globalUsings); co.AddUsings(_template.FakeDbSetUsings(data)); co.AddCode(Template.Transform(_template.FakeDbSet(), data)); return co; } public CodeOutput GenerateFactory() { var filename = Settings.DbContextName + "Factory" + Settings.FileExtension; if (!CanWriteFactory()) { FileManagementService.DeleteFile(filename); return null; } var data = new FactoryModel { classModifier = Settings.DbContextClassModifiers, contextName = Settings.DbContextName }; var co = new CodeOutput(string.Empty, filename, "Database context factory", Settings.ContextFolder, _globalUsings); co.AddUsings(_template.DatabaseContextFactoryUsings(data)); co.AddCode(Template.Transform(_template.DatabaseContextFactory(), data)); return co; } public CodeOutput GeneratePoco(Table table) { var filename = table.NameHumanCaseWithSuffix() + Settings.FileExtension; if (!CanWritePoco()) { FileManagementService.DeleteFile(filename); return null; } var isEfCore3Plus = Settings.IsEfCore3Plus(); var data = new PocoModel { UseHasNoKey = isEfCore3Plus && table.IsView && !table.HasPrimaryKey, HasNoPrimaryKey = !table.HasPrimaryKey, Name = table.DbName, NameHumanCaseWithSuffix = table.NameHumanCaseWithSuffix(), ClassModifier = Settings.EntityClassesModifiers, ClassComment = table.WriteComments(), ExtendedComments = table.WriteExtendedComments(), ClassAttributes = table.WriteClassAttributes(), BaseClasses = table.BaseClasses, InsideClassBody = Settings.WriteInsideClassBody(table), HasHierarchyId = table.Columns.Any(x => x.PropertyType.EndsWith("hierarchyid", StringComparison.InvariantCultureIgnoreCase)), Columns = table.Columns .Where(x => !x.Hidden && !x.ExistsInBaseClass) .OrderBy(x => x.Ordinal) .Select((col, index) => new PocoColumnModel { AddNewLineBefore = index > 0 && (((Settings.IncludeExtendedPropertyComments == CommentsStyle.InSummaryBlock || Settings.IncludeComments == CommentsStyle.InSummaryBlock) && !string.IsNullOrEmpty(col.SummaryComments)) || (col.Attributes != null && col.Attributes.Any())), HasSummaryComments = (Settings.IncludeExtendedPropertyComments == CommentsStyle.InSummaryBlock || Settings.IncludeComments == CommentsStyle.InSummaryBlock) && !string.IsNullOrEmpty(col.SummaryComments), SummaryComments = !string.IsNullOrEmpty(col.SummaryComments) ? SecurityElement.Escape(col.SummaryComments) : null, Attributes = col.Attributes, OverrideModifier = col.OverrideModifier, IncludeFieldNameConstants = Settings.IncludeFieldNameConstants, WrapIfNullable = col.WrapIfNullable(), NameHumanCase = col.NameHumanCase, PrivateSetterForComputedColumns = Settings.UsePrivateSetterForComputedColumns && col.IsComputed ? "private " : string.Empty, PropertyInitialisers = Settings.UsePropertyInitialisers ? (string.IsNullOrWhiteSpace(col.Default) ? string.Empty : string.Format(" = {0};", col.Default)) : string.Empty, InlineComments = col.InlineComments }) .ToList(), HasReverseNavigation = table.ReverseNavigationProperty.Count > 0, ReverseNavigationProperty = table.ReverseNavigationProperty .OrderBy(x => x.Definition) .Select(x => new PocoReverseNavigationPropertyModel { ReverseNavHasComment = Settings.IncludeComments != CommentsStyle.None && !string.IsNullOrEmpty(x.Comments), ReverseNavComment = Settings.IncludeComments != CommentsStyle.None ? x.Comments : string.Empty, AdditionalReverseNavigationsDataAnnotations = Settings.AdditionalReverseNavigationsDataAnnotations, AdditionalDataAnnotations = x.AdditionalDataAnnotations, Definition = x.Definition }) .ToList(), HasForeignKey = table.HasForeignKey, ForeignKeyTitleComment = Settings.IncludeComments != CommentsStyle.None && table.Columns.SelectMany(x => x.EntityFk).Any() ? " // Foreign keys" + Environment.NewLine : string.Empty, ForeignKeys = table.Columns .SelectMany(x => x.EntityFk) .OrderBy(o => o.Definition) .Select(x => new PocoForeignKeyModel { HasFkComment = Settings.IncludeComments != CommentsStyle.None && !string.IsNullOrEmpty(x.Comments), FkComment = x.Comments, AdditionalForeignKeysDataAnnotations = Settings.AdditionalForeignKeysDataAnnotations, AdditionalDataAnnotations = x.AdditionalDataAnnotations, Definition = x.Definition }) .ToList(), CreateConstructor = !Settings.UsePropertyInitialisers && ( table.Columns.Any(c => c.Default != string.Empty && !c.Hidden) || table.ReverseNavigationCtor.Any() || Settings.EntityClassesArePartial() ), ColumnsWithDefaults = table.Columns .Where(c => c.Default != string.Empty && !c.Hidden && Settings.IncludeColumnsWithDefaults) .OrderBy(x => x.Ordinal) .Select(x => new PocoColumnsWithDefaultsModel { NameHumanCase = x.NameHumanCase, Default = x.Default }) .ToList(), ReverseNavigationCtor = table.ReverseNavigationCtor, EntityClassesArePartial = Settings.EntityClassesArePartial(), HasSpatial = table.Columns.Any(x => x.IsSpatial) }; var co = new CodeOutput(table.DbName, filename, null, Settings.PocoFolder, _globalUsings); co.AddUsings(_template.PocoUsings(data)); co.AddCode(Template.Transform(_template.Poco(), data)); return co; } public CodeOutput GeneratePocoConfiguration(Table table) { var filename = table.NameHumanCaseWithSuffix() + Settings.ConfigurationClassName + Settings.FileExtension; if (!CanWritePocoConfiguration()) { FileManagementService.DeleteFile(filename); return null; } var columns = table.Columns .Where(x => !x.Hidden && !string.IsNullOrEmpty(x.Config)) .OrderBy(x => x.Ordinal) .ToList(); var isEfCore3Plus = Settings.IsEfCore3Plus(); var foreignKeys = columns.SelectMany(x => x.ConfigFk).OrderBy(o => o).ToList(); var primaryKey = _generator.PrimaryKeyModelBuilder(table); var indexes = _generator.IndexModelBuilder(table); var hasIndexes = indexes != null && indexes.Any(); var data = new PocoConfigurationModel { UseHasNoKey = isEfCore3Plus && table.IsView && !table.HasPrimaryKey, Name = table.DbName, ToTableOrView = (isEfCore3Plus && table.IsView && !table.HasPrimaryKey) ? "ToView" : "ToTable", ConfigurationClassName = table.NameHumanCaseWithSuffix() + Settings.ConfigurationClassName, NameHumanCaseWithSuffix = table.NameHumanCaseWithSuffix(), Schema = table.Schema.DbName, PrimaryKeyNameHumanCase = primaryKey ?? table.PrimaryKeyNameHumanCase(), NotUsingDataAnnotations = !Settings.UseDataAnnotations, HasSchema = !string.IsNullOrEmpty(table.Schema.DbName), ClassModifier = Settings.ConfigurationClassesModifiers, ClassComment = table.WriteComments(), Columns = columns.Select(x => x.Config).ToList(), HasReverseNavigation = table.ReverseNavigationProperty.Count > 0, UsesDictionary = table.UsesDictionary, HasSpatial = table.Columns.Any(x => x.IsSpatial), ReverseNavigationProperty = table.ReverseNavigationProperty .OrderBy(x => x.Definition) .Select(x => new PocoReverseNavigationPropertyModel { ReverseNavHasComment = Settings.IncludeComments != CommentsStyle.None && !string.IsNullOrEmpty(x.Comments), ReverseNavComment = Settings.IncludeComments != CommentsStyle.None ? x.Comments : string.Empty, AdditionalReverseNavigationsDataAnnotations = Settings.AdditionalReverseNavigationsDataAnnotations, AdditionalDataAnnotations = x.AdditionalDataAnnotations, Definition = x.Definition }) .ToList(), HasForeignKey = foreignKeys.Any(), ForeignKeys = foreignKeys, MappingConfiguration = table.MappingConfiguration, ConfigurationClassesArePartial = Settings.ConfigurationClassesArePartial(), Indexes = indexes, HasIndexes = hasIndexes }; var co = new CodeOutput(table.DbName, filename, null, Settings.PocoConfigurationFolder, _globalUsings); co.AddUsings(_template.PocoConfigurationUsings(data)); co.AddCode(Template.Transform(_template.PocoConfiguration(), data)); return co; } public CodeOutput GenerateStoredProcReturnModel(StoredProcedure sp) { var filename = sp.WriteStoredProcReturnModelName(_filter) + Settings.FileExtension; if (!CanWriteStoredProcReturnModel()) { FileManagementService.DeleteFile(filename); return null; } var multipleModelReturnColumns = new List(); var model = 0; foreach (var returnModel in sp.ReturnModels) { multipleModelReturnColumns.Add(new MultipleModelReturnColumns(++model, returnModel.Select(sp.WriteStoredProcReturnColumn).ToList())); } var data = new StoredProcReturnModel { ResultClassModifiers = Settings.ResultClassModifiers, WriteStoredProcReturnModelName = sp.WriteStoredProcReturnModelName(_filter), PropertyGetSet = Settings.UsePropertiesForStoredProcResultSets ? " { get; set; }" : ";", SingleModel = sp.ReturnModels.Count == 1, SingleModelReturnColumns = sp.ReturnModels .First() .Select(sp.WriteStoredProcReturnColumn) .ToList(), MultipleModelReturnColumns = multipleModelReturnColumns }; var co = new CodeOutput(sp.DbName, filename, null, Settings.PocoFolder, _globalUsings); co.AddUsings(_template.StoredProcReturnModelUsings()); co.AddCode(Template.Transform(_template.StoredProcReturnModels(), data)); return co; } public CodeOutput GenerateEnum(Enumeration enumeration) { var filename = enumeration.EnumName + Settings.FileExtension; if (!CanWriteEnums()) { FileManagementService.DeleteFile(filename); return null; } var co = new CodeOutput(enumeration.EnumName, filename, null, Settings.PocoFolder, null); co.AddUsings(_template.EnumUsings()); co.AddCode(Template.Transform(_template.Enums(), enumeration)); return co; } } public class CodeOutput { public string DbName { get; private set; } public string Filename { get; private set; } public string Region { get; private set; } public List Usings { get; private set; } public List Code { get; private set; } public CodeOutput(string dbName, string filename, string region, string folder, List usings) { DbName = dbName; Filename = filename; Region = region; Usings = new List(); Code = new List(); if (!string.IsNullOrWhiteSpace(folder)) Filename = Path.Combine(folder, filename); AddUsings(usings); } public void AddCode(string code) { if (code != null) Code.AddRange(code.Split(new[] { Environment.NewLine }, StringSplitOptions.None)); } public void AddUsings(List usings) { if (usings == null || !usings.Any()) return; Usings.AddRange(usings); Usings = Usings.Where(x => x != null).Distinct().ToList(); } public List GetUsings() { return Usings .Distinct() .OrderBy(x => x) .ToList(); } } public class CodeOutputList { public readonly Dictionary Files; // List of code files public CodeOutputList() { Files = new Dictionary(); } public void Add(string key, CodeOutput code) { if (code != null) { if (Files.ContainsKey(key)) { var error = string.Format("{0} already exists in the code output list. {1} and {2} both resolve to the same C# name. Filter one of them out.", key, Files[key].DbName, code.DbName); throw new Exception(error); } Files.Add(key, code); } } public List GetUsings() { var usings = new List(); foreach (var codeOutput in Files) { usings.AddRange(codeOutput.Value.Usings); } return usings .Distinct() .OrderBy(x => x) .ToList(); } } public class ColumnAndForeignKey { public ForeignKey ForeignKey; public Column Column; } public class FileHeaderFooter { public readonly string Header; public readonly string Namespace; public readonly string Footer; public FileHeaderFooter(string subNamespace) { var header = new StringBuilder(1024); header.AppendLine("// "); if (Settings.UseResharper) header.AppendLine("// ReSharper disable All"); if (Settings.AllowNullStrings) header.AppendLine("#nullable enable"); if (Settings.UsePragma) header.AppendLine("#pragma warning disable 1591 // Ignore \"Missing XML Comment\" warning"); foreach (var additionalHeader in Settings.AdditionalFileHeaderText) { header.AppendLine(additionalHeader); } Header = header.ToString(); header = new StringBuilder(500); if (Settings.UseNamespace) { var name = Settings.Namespace + subNamespace; header.AppendLine("namespace " + name.Trim().Replace(' ', '_')); header.Append("{"); } Namespace = header.ToString(); var footer = new StringBuilder(30); if (Settings.UseNamespace) footer.AppendLine("}"); foreach (var additionalHeader in Settings.AdditionalFileFooterText) { footer.AppendLine(additionalHeader); } footer.Append("// "); Footer = footer.ToString(); } } // ReSharper disable UseStringInterpolation public abstract class Generator { public bool InitialisationOk { get; private set; } protected DatabaseReader DatabaseReader; public readonly IDbContextFilterList FilterList; protected abstract bool AllowFkToNonPrimaryKey(); protected abstract bool FkMustHaveSameNumberOfColumnsAsPrimaryKey(); protected abstract void SetupEntity(Column c); protected abstract void SetupConfig(Column c); public abstract string PrimaryKeyModelBuilder(Table table); public abstract List IndexModelBuilder(Table t); public abstract string IndexModelBuilder(Column c); protected abstract string GetHasMethod(Relationship relationship, IList fkCols, IList pkCols, bool isNotEnforced, bool fkHasUniqueConstraint); protected abstract string GetWithMethod(Relationship relationship, IList fkCols, string fkPropName, string manyToManyMapping, string mapKey, bool includeReverseNavigation, string hasMethod, string pkTableNameHumanCase, string fkTableNameHumanCase, string primaryKeyColumns, bool fkHasUniqueConstraint); protected abstract string GetCascadeOnDelete(bool cascadeOnDelete); protected abstract string GetForeignKeyConstraintName(string foreignKeyConstraintName); private bool _hasAcademicLicence; private bool _hasTrialLicence; private FileHeaderFooter _fileHeaderFooter; private readonly StringBuilder _preHeaderInfo; private readonly string _codeGeneratedAttribute; private readonly FileManagementService _fileManagementService; private readonly Type _fileManagerType; private const int Indent = 4; protected Generator(FileManagementService fileManagementService, Type fileManagerType) { _fileManagementService = fileManagementService; _fileManagerType = fileManagerType; InitialisationOk = false; DatabaseReader = null; _fileHeaderFooter = null; _preHeaderInfo = new StringBuilder(1024); _codeGeneratedAttribute = string.Format("[GeneratedCode(\"EF.Reverse.POCO.Generator\", \"{0}\")]", EfrpgVersion.Version()); FilterList = new DbContextFilterList(); } public void Init(DatabaseReader databaseReader, string singleDbContextSubNamespace) { if (Settings.ConnectionString.Contains("**TODO**")) { _fileManagementService.Error("// Please update your .tt file's `Settings.ConnectionString` string to the database you want to reverse engineer and save your .tt file."); return; } var licence = ReadAndValidateLicence(); if(licence == null) return; BuildPreHeaderInfo(licence); DatabaseReader = databaseReader; if (DatabaseReader == null) { _fileManagementService.Error("// Cannot create a database reader due to unknown database type."); return; } DatabaseReader.Init(); if (Settings.IncludeConnectionSettingComments) _preHeaderInfo.Append(DatabaseDetails()); if (Settings.UseDataAnnotations) { Settings.AdditionalNamespaces.Add("System.ComponentModel.DataAnnotations"); Settings.AdditionalNamespaces.Add("System.ComponentModel.DataAnnotations.Schema"); } _hasAcademicLicence = licence.LicenceType == LicenceType.Academic; _hasTrialLicence = licence.LicenceType == LicenceType.Trial; InitialisationOk = FilterList.ReadDbContextSettings(DatabaseReader, singleDbContextSubNamespace); _fileManagementService.Init(FilterList.GetFilters(), _fileManagerType); } public string GetPreHeaderInfo() { return _preHeaderInfo.ToString(); } private Licence ReadAndValidateLicence() { var path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); var file = Path.Combine(path, "ReversePOCO.txt"); const string obtainAt = "// Please obtain your licence file at www.ReversePOCO.co.uk, and place it in your documents folder shown above."; if (!File.Exists(file)) { _fileManagementService.Error(string.Format("// Licence file {0} not found.", file)); _fileManagementService.Error(obtainAt); return TrialLicenceFallback(); } var validator = new LicenceValidator(); if(!validator.Validate(File.ReadAllText(file))) { _fileManagementService.Error(validator.Expired ? string.Format("// Your licence file {0} has expired.", file) : string.Format("// Your licence file {0} is not valid.", file)); _fileManagementService.Error(obtainAt); return TrialLicenceFallback(); } return validator.Licence; // Thank you for having a valid licence and supporting this product :-) } private Licence TrialLicenceFallback() { _fileManagementService.Error("// Defaulting to Trial version."); return new Licence(string.Empty, string.Empty, LicenceType.Trial, "1", DateTime.MaxValue); } public string DatabaseDetails() { return DatabaseReader.GetDatabaseDetails(); } public void LoadEnums() { if (DatabaseReader == null || !Settings.ElementsToGenerate.HasFlag(Elements.Enum)) return; try { foreach (var filterKeyValuePair in FilterList.GetFilters()) { var filter = filterKeyValuePair.Value; foreach (var table in filter.Tables) { filter.AddEnum(table); } } if (Settings.GenerateSingleDbContext) { // Single-context if (Settings.Enumerations == null) return; // No enums required var enumerations = DatabaseReader.ReadEnums(Settings.Enumerations); if (enumerations.Count <= 0) return; // No enums in database foreach (var filterKeyValuePair in FilterList.GetFilters()) { filterKeyValuePair.Value.Enums.AddRange(enumerations); } } else { // Multi-context foreach (var filterKeyValuePair in FilterList.GetFilters()) { var multiContextSetting = ((MultiContextFilter) filterKeyValuePair.Value).GetSettings(); if (multiContextSetting?.Enumerations == null || multiContextSetting.Enumerations.Count == 0) continue; var enumerations = DatabaseReader.ReadEnums(multiContextSetting.Enumerations); if (enumerations.Count > 0) filterKeyValuePair.Value.Enums.AddRange(enumerations); } } foreach (var filterKeyValuePair in FilterList.GetFilters()) { var filter = filterKeyValuePair.Value; foreach (var enumeration in filter.Enums) { filter.UpdateEnum(enumeration); foreach (var enumerationMember in enumeration.Items) { filter.UpdateEnumMember(enumerationMember); } } } // Remove tables marked as RemoveTable // This means it was only required to generate an enum and can now be removed foreach (var filter in FilterList.GetFilters().Select(filterKeyValuePair => filterKeyValuePair.Value)) { filter.Tables.RemoveAll(x => x.RemoveTable); } } catch (Exception x) { var error = FormatError(x); _fileManagementService.Error(string.Empty); _fileManagementService.Error("// -----------------------------------------------------------------------------------------"); _fileManagementService.Error(string.Format("// Failed to read enumeration tables in LoadEnums() - {0}", error)); _fileManagementService.Error("/*" + x.StackTrace + "*/"); _fileManagementService.Error("// -----------------------------------------------------------------------------------------"); _fileManagementService.Error(string.Empty); } } public void LoadSequences() { if (DatabaseReader == null || !Settings.ElementsToGenerate.HasFlag(Elements.Context)) return; try { var sequences = DatabaseReader.ReadSequences(); if (sequences.Count <= 0) return; // No sequences in database foreach (var filterKeyValuePair in FilterList.GetFilters()) { // Only add sequences where the table is used by this filter foreach (var seq in sequences .Where(seq => seq.TableMapping .Any(tableMapping => filterKeyValuePair.Value.Tables .Any(x => x.Schema.DbName.Equals(tableMapping.TableSchema, StringComparison.InvariantCultureIgnoreCase) && x.DbName.Equals(tableMapping.TableName, StringComparison.InvariantCultureIgnoreCase))))) { filterKeyValuePair.Value.Sequences.Add(seq); } } } catch (Exception x) { var error = FormatError(x); _fileManagementService.Error(string.Empty); _fileManagementService.Error("// -----------------------------------------------------------------------------------------"); _fileManagementService.Error(string.Format("// Failed to read sequences in LoadSequences() - {0}", error)); _fileManagementService.Error("/*" + x.StackTrace + "*/"); _fileManagementService.Error("// -----------------------------------------------------------------------------------------"); _fileManagementService.Error(string.Empty); } } public void ReadDatabase() { LoadTables(); LoadStoredProcs(); LoadEnums(); LoadSequences(); } public void LoadTables() { if (DatabaseReader == null || !(Settings.ElementsToGenerate.HasFlag(Elements.Poco) || Settings.ElementsToGenerate.HasFlag(Elements.Context) || Settings.ElementsToGenerate.HasFlag(Elements.Interface) || Settings.ElementsToGenerate.HasFlag(Elements.PocoConfiguration))) return; try { var includeSynonyms = FilterList.IncludeSynonyms(); var rawTables = DatabaseReader.ReadTables(includeSynonyms); var rawIndexes = DatabaseReader.ReadIndexes(); var rawForeignKeys = DatabaseReader.ReadForeignKeys(includeSynonyms); // For unit testing //foreach (var ri in rawIndexes.OrderBy(x => x.TableName).ThenBy(x => x.IndexName)) Console.WriteLine(ri.Dump()); //foreach (var rfk in rawForeignKeys) Console.WriteLine(rfk.Dump()); AddTablesToFilters(rawTables); IdentifyUniqueForeignKeys(rawIndexes, rawForeignKeys); AddIndexesToFilters(rawIndexes); SetPrimaryKeys(); AddForeignKeysToFilters(rawForeignKeys); if (Settings.IsEfCore6Plus()) { var rawMemoryOptimisedTables = DatabaseReader.ReadMemoryOptimisedTables(); AddMemoryOptimisedTablesToFilters(rawMemoryOptimisedTables); } if (Settings.IsEfCore7Plus()) { var rawTriggers = DatabaseReader.ReadTriggers(); AddTriggersToFilters(rawTriggers); } if (Settings.IncludeExtendedPropertyComments != CommentsStyle.None) AddExtendedPropertiesToFilters(DatabaseReader.ReadExtendedProperties()); SetupEntityAndConfig(); // Must be done last } catch (Exception x) { var error = FormatError(x); _fileManagementService.Error(string.Empty); _fileManagementService.Error("// -----------------------------------------------------------------------------------------"); _fileManagementService.Error(string.Format("// Failed to read database schema in LoadTables() - {0}", error)); _fileManagementService.Error("/*" + x.StackTrace + "*/"); _fileManagementService.Error("// -----------------------------------------------------------------------------------------"); _fileManagementService.Error(string.Empty); } } public static void IdentifyUniqueForeignKeys(List rawIndexes, List rawForeignKeys) { if (rawIndexes == null || rawForeignKeys == null || !rawIndexes.Any() || !rawForeignKeys.Any()) return; var uniqueForeignKeys = from i in rawIndexes.Where(x => x.IsUniqueConstraint) join fk1 in rawForeignKeys on new { X1 = i.Schema, X2 = i.TableName } equals new { X1 = fk1.PkSchema, X2 = fk1.PkTableName } select fk1; foreach (var fk in uniqueForeignKeys) fk.HasUniqueConstraint = true; } // Create tables from raw data for each of the DbContextFilters private void AddTablesToFilters(List rawTables) { if (rawTables == null || !rawTables.Any()) return; var tablesNames = rawTables .Select(x => new { x.SchemaName, x.TableName, x.IsView }) .Distinct() .OrderBy(x => x.SchemaName) .ThenBy(x => x.TableName) .ToList(); #pragma warning disable CS0618 // Type or member is obsolete var deleteFilteredOutFiles = (Settings.FileManagerType == FileManagerType.Custom || Settings.FileManagerType == FileManagerType.EfCore) && Settings.GenerateSeparateFiles; #pragma warning restore CS0618 // Type or member is obsolete foreach (var filterKeyValuePair in FilterList.GetFilters()) { var filter = filterKeyValuePair.Value; foreach (var tn in tablesNames) { var exclude = tn.IsView && !filter.IncludeViews; if(exclude && !deleteFilteredOutFiles) continue; // Check if schema is excluded var schema = new Schema(tn.SchemaName); if (filter.IsExcluded(schema)) { exclude = true; if (!deleteFilteredOutFiles) continue; } // Check if table is excluded var table = new Table(filter, schema, tn.TableName, tn.IsView); if (filter.IsExcluded(table)) { exclude = true; if (!deleteFilteredOutFiles) continue; } // Handle table names with underscores - singularise just the last word var tableName = DatabaseReader.CleanUp(filter.TableRename(tn.TableName, tn.SchemaName, tn.IsView)); var singularCleanTableName = Inflector.MakeSingular(tableName); table.NameHumanCase = (Settings.UsePascalCase ? Inflector.ToTitleCase(singularCleanTableName) : singularCleanTableName).Replace(" ", "").Replace("$", "").Replace(".", ""); if (Settings.PrependSchemaName && string.Compare(table.Schema.DbName, Settings.DefaultSchema, StringComparison.OrdinalIgnoreCase) != 0) table.NameHumanCase = table.Schema.DbName + "_" + table.NameHumanCase; if (filter.IsExcluded(table)) // Retest exclusion after table rename { exclude = true; if (!deleteFilteredOutFiles) continue; } if(exclude) { FileManagementService.DeleteFile(table.NameHumanCaseWithSuffix() + Settings.FileExtension); // Poco FileManagementService.DeleteFile(table.NameHumanCaseWithSuffix() + Settings.ConfigurationClassName + Settings.FileExtension); // Poco config continue; } // Check for table or C# name clashes if (DatabaseReader.ReservedKeywords.Contains(table.NameHumanCase) || (Settings.UsePascalCase && filter.Tables.Find(x => x.NameHumanCase == table.NameHumanCase) != null)) { table.NameHumanCase += "1"; } // Create columns for table foreach (var rawTable in rawTables .Where(x => x.SchemaName == tn.SchemaName && x.TableName == tn.TableName) .OrderBy(x => x.Ordinal)) { table.IsSynonym = table.IsSynonym || rawTable.IsSynonym; var column = DatabaseReader.CreateColumn(rawTable, table, filter); if (column != null) table.Columns.Add(column); } // Check for property name clashes in columns foreach (var c in table.Columns.Where(c => table.Columns.FindAll(x => x.NameHumanCase == c.NameHumanCase).Count > 1)) { var n = 1; var original = c.NameHumanCase; c.NameHumanCase = original + n++; // Check if the above resolved the name clash, if not, use next value while (c.ParentTable.Columns.Count(c2 => c2.NameHumanCase == c.NameHumanCase) > 1) c.NameHumanCase = original + n++; } filter.Tables.Add(table); } foreach (var table in filter.Tables) { if (table.IsView) filter.ViewProcessing(table); filter.UpdateTable(table); foreach (var column in table.Columns) filter.UpdateColumn(column, table); table.Suffix = Settings.TableSuffix; } } } private void AddIndexesToFilters(List rawIndexes) { if (rawIndexes == null || !rawIndexes.Any()) return; var indexTables = rawIndexes .Select(x => new { x.Schema, x.TableName }) .Distinct() .OrderBy(x => x.Schema) .ThenBy(x => x.TableName) .ToList(); foreach (var filterKeyValuePair in FilterList.GetFilters()) { var filter = filterKeyValuePair.Value; Table t = null; foreach (var indexTable in indexTables) { // Lookup table if (t == null || t.DbName != indexTable.TableName || t.Schema.DbName != indexTable.Schema) t = filter.Tables.GetTable(indexTable.TableName, indexTable.Schema); if (t == null) continue; // Find indexes for table t.Indexes = rawIndexes.Where(x => x.Schema == indexTable.Schema && x.TableName == indexTable.TableName) .OrderBy(o => o.ColumnCount) .ThenBy(o => o.KeyOrdinal) .ToList(); // Set index on column foreach (var index in t.Indexes) { var col = t.Columns.Find(x => x.DbName == index.ColumnName); if (col == null) continue; col.Indexes.Add(index); col.IsPrimaryKey = col.IsPrimaryKey || index.IsPrimaryKey; col.IsUniqueConstraint = col.IsUniqueConstraint || (index.IsUniqueConstraint && index.ColumnCount == 1); col.IsUnique = col.IsUnique || (index.IsUnique && index.ColumnCount == 1); } // Check if table has any primary keys if (t.PrimaryKeys.Any()) continue; // Already has a primary key, ignore this unique index / constraint // Find unique indexes for table var uniqueIndexKeys = t.Indexes .Where(x => x.IsUnique || x.IsPrimaryKey || x.IsUniqueConstraint) .OrderBy(o => o.ColumnCount) .ThenBy(o => o.KeyOrdinal); // Process only the first index with the lowest unique column count string indexName = null; foreach (var key in uniqueIndexKeys) { if (indexName == null) indexName = key.IndexName; if (indexName != key.IndexName) break; // First unique index with lowest column count has been processed, exit. var col = t.Columns.Find(x => x.DbName == key.ColumnName); if (col != null && !col.IsNullable && !col.Hidden && !col.IsPrimaryKey) { col.IsPrimaryKey = true; col.IsUniqueConstraint = true; col.IsUnique = true; col.UniqueIndexName = indexName; } } } } } private void AddForeignKeysToFilters(List rawForeignKeys) { if (Settings.GenerateSingleDbContext && (rawForeignKeys == null || !rawForeignKeys.Any())) return; if (rawForeignKeys == null) rawForeignKeys = new List(); //else //SortForeignKeys(rawForeignKeys); foreach (var filterKeyValuePair in FilterList.GetFilters()) { var filter = filterKeyValuePair.Value; var fks = new List(); fks.AddRange(rawForeignKeys/*.OrderBy(x => x.SortOrder).ThenBy(x => x.FkTableName).ThenBy(x => x.PkTableName)*/); if (!Settings.GenerateSingleDbContext) { var multiContextSetting = ((MultiContextFilter) filter).GetSettings(); if (multiContextSetting?.ForeignKeys != null) { fks.AddRange(multiContextSetting.ForeignKeys.Select(x => new RawForeignKey(x.ConstraintName, x.ParentName, x.ChildName, x.PkColumn, x.FkColumn, x.PkSchema, x.PkTableName, x.FkSchema, x.FkTableName, x.Ordinal, x.CascadeOnDelete, x.IsNotEnforced, x.HasUniqueConstraint))); } } if (!fks.Any()) continue; var foreignKeys = new List(); foreach (var rawForeignKey in fks) { var fkTableNameFiltered = filter.TableRename(rawForeignKey.FkTableName, rawForeignKey.FkSchema, false); var pkTableNameFiltered = filter.TableRename(rawForeignKey.PkTableName, rawForeignKey.PkSchema, false); var fk = new ForeignKey(rawForeignKey, fkTableNameFiltered, pkTableNameFiltered); var filteredFk = filter.ForeignKeyFilter(fk); if (filteredFk != null) { if (Settings.ForeignKeyFilterFunc != null) filteredFk = Settings.ForeignKeyFilterFunc(filteredFk); if (filteredFk != null) foreignKeys.Add(filteredFk); } } IdentifyForeignKeys(foreignKeys, filter.Tables); Settings.AddExtraForeignKeys?.Invoke(filter, this, foreignKeys, filter.Tables); // Work out if there are any foreign key relationship naming clashes ProcessForeignKeys(foreignKeys, true, filter); // Mappings tables can only be true for Ef6 and EFCore 5 onwards if (Settings.UseMappingTables && !(Settings.IsEf6() || Settings.IsEfCore6Plus())) Settings.UseMappingTables = false; if (Settings.UseMappingTables) filter.Tables.IdentifyMappingTables(foreignKeys, true, DatabaseReader.IncludeSchema); // Now we know our foreign key relationships and have worked out if there are any name clashes, // re-map again with intelligently named relationships. filter.Tables.ResetNavigationProperties(); ProcessForeignKeys(foreignKeys, false, filter); if (Settings.UseMappingTables) filter.Tables.IdentifyMappingTables(foreignKeys, false, DatabaseReader.IncludeSchema); } } /*private void SortForeignKeys(List rawForeignKeys) { foreach (var fk in rawForeignKeys) { fk.SortOrder = 10; var fkColumn = fk.FkColumn.ToLowerInvariant(); var pkTable = fk.PkTableName.ToLowerInvariant(); // Matches exactly if (fkColumn == pkTable) { fk.SortOrder = 1; continue; } // Matches without 'id' if(fkColumn.EndsWith("id") && fkColumn.Remove(fkColumn.Length - 2, 2) == pkTable) { fk.SortOrder = 2; continue; } // Matches if trimmed if(fkColumn.Length > pkTable.Length && fkColumn.Substring(0, pkTable.Length) == pkTable) { fk.SortOrder = 3; continue; } } }*/ private void AddTriggersToFilters(List triggers) { if (triggers == null || !triggers.Any()) return; foreach (var filterKeyValuePair in FilterList.GetFilters()) { var filter = filterKeyValuePair.Value; Table t = null; foreach (var trigger in triggers) { // Lookup table if (t == null || t.DbName != trigger.TableName || t.Schema.DbName != trigger.SchemaName) t = filter.Tables.GetTable(trigger.TableName, trigger.SchemaName); if (t == null) continue; // Only store the one trigger name as EFCore 7 does not care what its name is. It only cares that there is one present. t.TriggerName = trigger.TriggerName; } } } private void AddMemoryOptimisedTablesToFilters(List memoryOptimisedTables) { if (memoryOptimisedTables == null || !memoryOptimisedTables.Any()) return; foreach (var filterKeyValuePair in FilterList.GetFilters()) { var filter = filterKeyValuePair.Value; Table t = null; foreach (var trigger in memoryOptimisedTables) { // Lookup table if (t == null || t.DbName != trigger.TableName || t.Schema.DbName != trigger.SchemaName) t = filter.Tables.GetTable(trigger.TableName, trigger.SchemaName); if (t == null) continue; t.IsMemoryOptimised = true; } } } private void AddExtendedPropertiesToFilters(List extendedProperties) { if (extendedProperties == null || !extendedProperties.Any()) return; var commentsInSummaryBlock = Settings.IncludeExtendedPropertyComments == CommentsStyle.InSummaryBlock; var multiLine = new Regex("[\r\n]+", RegexOptions.Compiled); var whiteSpace = new Regex("\\s+", RegexOptions.Compiled); foreach (var filterKeyValuePair in FilterList.GetFilters()) { var filter = filterKeyValuePair.Value; Table t = null; foreach (var extendedProperty in extendedProperties) { // Lookup table if (t == null || t.DbName != extendedProperty.TableName || t.Schema.DbName != extendedProperty.SchemaName) t = filter.Tables.GetTable(extendedProperty.TableName, extendedProperty.SchemaName); if (t == null) continue; if (extendedProperty.TableLevelExtendedComment) { // Table level extended comment t.ExtendedProperty.Add(multiLine.Replace(extendedProperty.ExtendedProperty, "\r\n /// ")); continue; } // Column level extended comment var col = t.Columns.Find(x => x.DbName == extendedProperty.ColumnName); if (col == null) continue; if (commentsInSummaryBlock) col.ExtendedProperty = multiLine.Replace(extendedProperty.ExtendedProperty, "\r\n /// "); else col.ExtendedProperty = whiteSpace.Replace(multiLine.Replace(extendedProperty.ExtendedProperty, " "), " "); } } } private void SetPrimaryKeys() { foreach (var filterKeyValuePair in FilterList.GetFilters()) { var filter = filterKeyValuePair.Value; foreach (var table in filter.Tables) { table.SetPrimaryKeys(); } if (_hasTrialLicence) filter.Tables.TrimForTrialLicence(); } } private void SetupEntityAndConfig() { foreach (var filterKeyValuePair in FilterList.GetFilters()) { var filter = filterKeyValuePair.Value; foreach (var table in filter.Tables) { table.Columns.ForEach(SetupEntityAndConfig); } } } public void LoadStoredProcs() { if (DatabaseReader == null || !DatabaseReader.CanReadStoredProcedures()) return; try { #pragma warning disable CS0618 // Type or member is obsolete var deleteFilteredOutFiles = (Settings.FileManagerType == FileManagerType.Custom || Settings.FileManagerType == FileManagerType.EfCore) && Settings.GenerateSeparateFiles; #pragma warning restore CS0618 // Type or member is obsolete var spFilters = FilterList .GetFilters() .Where(x => x.Value.IncludeStoredProcedures || x.Value.IncludeTableValuedFunctions || x.Value.IncludeScalarValuedFunctions) .ToList(); if (!spFilters.Any()) return; var includeSynonyms = FilterList.IncludeSynonyms(); var rawStoredProcs = DatabaseReader.ReadStoredProcs(includeSynonyms); // Only call stored procedures to obtain the return models that are not filtered out // We don't want to do this for every db context we are generating as that is inefficient var procs = rawStoredProcs .Select(sp => new { sp.Schema, sp.Name, sp.IsTableValuedFunction, sp.IsScalarValuedFunction, sp.IsStoredProcedure }) .Distinct() .OrderBy(x => x.Schema) .ThenBy(x => x.Name); var storedProcs = new List(); foreach (var proc in procs) { var sp = new StoredProcedure { DbName = proc.Name, NameHumanCase = (Settings.UsePascalCase ? Inflector.ToTitleCase(proc.Name) : proc.Name).Replace(" ", "").Replace("$", ""), Schema = new Schema(proc.Schema), IsTableValuedFunction = proc.IsTableValuedFunction, IsScalarValuedFunction = proc.IsScalarValuedFunction, IsStoredProcedure = proc.IsStoredProcedure }; sp.NameHumanCase = DatabaseReader.CleanUp(sp.NameHumanCase); if (Settings.PrependSchemaName && (string.Compare(proc.Schema, Settings.DefaultSchema, StringComparison.OrdinalIgnoreCase) != 0)) sp.NameHumanCase = proc.Schema + "_" + sp.NameHumanCase; sp.Parameters.AddRange(rawStoredProcs .Where(x => x.Parameter != null && x.Schema == proc.Schema && x.Name == proc.Name) .Select(x => x.Parameter)); sp.HasSpatialParameter = sp.Parameters.Any(x => x.IsSpatial); if (Settings.DisableGeographyTypes && sp.HasSpatialParameter) continue; // Ignore stored procedure due to spatial parameter // Check to see if this stored proc is to be kept by any of the filters if (spFilters.All(x => x.Value.IsExcluded(sp))) { if(deleteFilteredOutFiles) FileManagementService.DeleteFile(sp.WriteStoredProcReturnModelName(spFilters[0].Value) + Settings.FileExtension); continue; // All Db Context exclude this stored proc, ignore it as nobody wants it } storedProcs.Add(sp); } if (!storedProcs.Any()) return; // No stored procs to read the return model for, so exit // Read in the return objects for the wanted stored proc DatabaseReader.ReadStoredProcReturnObjects(storedProcs); // Check if any of the stored proc return models have spatial types /*foreach (var sp in storedProcs) { if(sp.ReturnModels.Any()) { // todo } }*/ // Force generation of Async stored procs /*foreach (var sp in storedProcs.Where(x => !x.IsTableValuedFunction && !x.ReturnModels.Any())) { sp.ReturnModels.Add(new List { new DataColumn("result", typeof(int)) }); }*/ // Remove stored procs where the return model type contains spaces and cannot be mapped // Also need to remove any TVF functions with parameters that are non scalar types, such as DataTable var validStoredProcs = new List(); foreach (var sp in storedProcs) { if (!sp.ReturnModels.Any()) { validStoredProcs.Add(sp); continue; } if (sp.ReturnModels.Any(returnColumns => returnColumns.Any(c => c.ColumnName.Contains(" ")))) continue; // Invalid, ignore stored procedure if (sp.IsTableValuedFunction && sp.Parameters.Any(c => c.PropertyType == "DataTable")) continue; // Invalid, ignore validStoredProcs.Add(sp); // Valid, keep this stored proc } // Update the list of stored procs for each of the db context filters that want them foreach (var filterKeyValuePair in spFilters) { var filter = filterKeyValuePair.Value; foreach (var sp in validStoredProcs) { if (!filter.IsExcluded(sp)) { if (_hasTrialLicence) { const int n = 1 + 2 + 3 + 4; if (filter.StoredProcs.Count < n) filter.StoredProcs.Add(sp); } else filter.StoredProcs.Add(sp); } else { if (deleteFilteredOutFiles) FileManagementService.DeleteFile(sp.WriteStoredProcReturnModelName(filter) + Settings.FileExtension); } } } } catch (Exception x) { var error = FormatError(x); _fileManagementService.Error(string.Empty); _fileManagementService.Error("// -----------------------------------------------------------------------------------------"); _fileManagementService.Error(string.Format("// Failed to read database schema for stored procedures - {0}", error)); _fileManagementService.Error("/*" + x.StackTrace + "*/"); _fileManagementService.Error("// -----------------------------------------------------------------------------------------"); _fileManagementService.Error(string.Empty); } } /// AddRelationship overload for single-column foreign-keys. public void AddRelationship(IDbContextFilter filter, List fkList, Tables tablesAndViews, string name, string pkSchema, string pkTable, string pkColumn, string fkSchema, string fkTable, string fkColumn, string parentName, string childName, bool isUnique) { AddRelationship(filter, fkList, tablesAndViews, name, pkSchema, pkTable, new[] { pkColumn }, fkSchema, fkTable, new[] { fkColumn }, parentName, childName, isUnique); } public void AddRelationship(IDbContextFilter filter, List fkList, Tables tablesAndViews, string relationshipName, string pkSchema, string pkTableName, string[] pkColumns, string fkSchema, string fkTableName, string[] fkColumns, string parentName, string childName, bool isUnique) { // Argument validation: if (filter == null) throw new ArgumentNullException(nameof(filter)); if (fkList == null) throw new ArgumentNullException(nameof(fkList)); if (tablesAndViews == null) throw new ArgumentNullException(nameof(tablesAndViews)); if (string.IsNullOrEmpty(relationshipName)) throw new ArgumentNullException(nameof(relationshipName)); if (string.IsNullOrEmpty(pkSchema)) throw new ArgumentNullException(nameof(pkSchema)); if (string.IsNullOrEmpty(pkTableName)) throw new ArgumentNullException(nameof(pkTableName)); if (pkColumns == null) throw new ArgumentNullException(nameof(pkColumns)); if (pkColumns.Length == 0 || pkColumns.Any(string.IsNullOrEmpty)) throw new ArgumentException("Invalid primary-key columns: No primary-key column names are specified, or at least one primary-key column name is empty.", nameof(pkColumns)); if (string.IsNullOrEmpty(fkSchema)) throw new ArgumentNullException(nameof(fkSchema)); if (string.IsNullOrEmpty(fkTableName)) throw new ArgumentNullException(nameof(fkTableName)); if (fkColumns == null) throw new ArgumentNullException(nameof(fkColumns)); if (fkColumns.Length != pkColumns.Length || fkColumns.Any(string.IsNullOrEmpty)) throw new ArgumentException("Invalid foreign-key columns:Foreign-key column list has a different number of columns than the primary-key column list, or at least one foreign-key column name is empty.", nameof(pkColumns)); ////////////////// var pkTable = tablesAndViews.GetTable(pkTableName, pkSchema); if (pkTable == null) throw new ArgumentException("Couldn't find table " + pkSchema + "." + pkTableName); var fkTable = tablesAndViews.GetTable(fkTableName, fkSchema); if (fkTable == null) throw new ArgumentException("Couldn't find table " + fkSchema + "." + fkTableName); // Ensure all columns exist: foreach (var pkCol in pkColumns) { if (pkTable.Columns.SingleOrDefault(c => c.DbName == pkCol) == null) throw new ArgumentException("The relationship primary-key column \"" + pkCol + "\" does not exist in table or view " + pkSchema + "." + pkTableName); } foreach (var fkCol in fkColumns) { if (fkTable.Columns.SingleOrDefault(c => c.DbName == fkCol) == null) throw new ArgumentException("The relationship foreign-key column \"" + fkCol + "\" does not exist in table or view " + fkSchema + "." + fkTableName); } for (var i = 0; i < pkColumns.Length; i++) { var pkc = pkColumns[i]; var fkc = fkColumns[i]; var fkTableNameFiltered = filter.TableRename(fkTableName, fkSchema, fkTable.IsView); var pkTableNameFiltered = filter.TableRename(pkTableName, pkSchema, pkTable.IsView); var fk = new ForeignKey( fkTable.DbName, fkSchema, pkTable.DbName, pkSchema, fkc, pkc, "AddRelationship: " + relationshipName, fkTableNameFiltered, pkTableNameFiltered, int.MaxValue, false, false, parentName, childName, isUnique ) { IncludeReverseNavigation = true }; fkList.Add(fk); fkTable.HasForeignKey = true; } } private void ProcessForeignKeys(List fkList, bool checkForFkNameClashes, IDbContextFilter filter) { var constraints = fkList.Select(x => x.FkSchema + "." + x.ConstraintName).Distinct(); foreach (var constraint in constraints) { var foreignKeys = fkList .Where(x => string.Format("{0}.{1}", x.FkSchema, x.ConstraintName).Equals(constraint, StringComparison.InvariantCultureIgnoreCase)) .ToList(); var foreignKey = foreignKeys.First(); var fkTable = filter.Tables.GetTable(foreignKey.FkTableName, foreignKey.FkSchema); if (fkTable == null || fkTable.IsMapping || !fkTable.HasForeignKey) continue; var pkTable = filter.Tables.GetTable(foreignKey.PkTableName, foreignKey.PkSchema); if (pkTable == null || pkTable.IsMapping) continue; var fkCols = foreignKeys.Select(x => new ColumnAndForeignKey { ForeignKey = x, Column = fkTable.Columns.Find(n => string.Equals(n.DbName, x.FkColumn, StringComparison.InvariantCultureIgnoreCase)) }) .Where(x => x.Column != null) .OrderBy(o => o.ForeignKey.Ordinal) .ToList(); if (!fkCols.Any()) continue; if (FkMustHaveSameNumberOfColumnsAsPrimaryKey() || AllowFkToNonPrimaryKey()) { // Check FK has same number of columns as the primary key it points to var pks = pkTable.PrimaryKeys .OrderBy(x => x.PropertyType).ThenBy(y => y.DbName).ToArray(); var cols = fkCols.Select(x => x.Column).OrderBy(x => x.PropertyType).ThenBy(y => y.DbName).ToArray(); if (FkMustHaveSameNumberOfColumnsAsPrimaryKey() && pks.Length != cols.Length) { // Also check unique constraints if (!AllowFkToNonPrimaryKey()) continue; if(!fkCols.All(x => x.ForeignKey.HasUniqueConstraint)) continue; } if (!AllowFkToNonPrimaryKey() && pks.Where((pk, n) => pk.PropertyType != cols[n].PropertyType).Any()) continue; } var pkCols = foreignKeys.Select(x => new ColumnAndForeignKey { ForeignKey = x, Column = pkTable.Columns.Find(n => string.Equals(n.DbName, x.PkColumn, StringComparison.InvariantCultureIgnoreCase)) }) .Where(x => x.Column != null) .OrderBy(o => o.ForeignKey.Ordinal) .ToList(); if (!pkCols.Any()) continue; var allPkColsArePrimaryKeys = pkCols.All(c => c.Column.IsPrimaryKey); if (!AllowFkToNonPrimaryKey() && !allPkColsArePrimaryKeys) continue; // Cannot have a FK to a non-primary key var relationship = CalcRelationship(pkTable, fkTable, fkCols, pkCols); if (relationship == Relationship.DoNotUse) continue; var pkTableHumanCaseWithSuffix = foreignKey.PkTableHumanCase(pkTable.Suffix); var pkTableHumanCase = foreignKey.PkTableHumanCase(null); var fkHasUniqueConstraint = pkCols.All(x => x.ForeignKey.HasUniqueConstraint) && relationship == Relationship.OneToOne; if (fkHasUniqueConstraint && pkCols.Any(x => x.Column.IsNullable)) continue; // This would force the column to be not null var flipRelationship = FlipRelationship(relationship); var fkMakePropNameSingular = relationship == Relationship.OneToOne; var pkPropName = fkTable.GetUniqueForeignKeyName(true, pkTableHumanCase, foreignKey, checkForFkNameClashes, true, relationship); var fkPropName = pkTable.GetUniqueForeignKeyName(false, fkTable.NameHumanCase, foreignKey, checkForFkNameClashes, fkMakePropNameSingular, flipRelationship); var fkd = new PropertyAndComments { AdditionalDataAnnotations = filter.ForeignKeyAnnotationsProcessing(fkTable, pkTable, pkPropName, fkPropName), PropertyName = pkPropName, Definition = string.Format("public {0}{1} {2} {3}{4}", Table.GetLazyLoadingMarker(), pkTableHumanCaseWithSuffix, pkPropName, "{ get; set; }", Settings.IncludeComments != CommentsStyle.None ? " // " + foreignKey.ConstraintName : string.Empty), Comments = string.Format("Parent {0} pointed by [{1}].({2}) ({3})", pkTableHumanCase, fkTable.DbName, string.Join(", ", fkCols.Select(x => "[" + x.Column.NameHumanCase + "]").Distinct().ToArray()), foreignKey.ConstraintName) }; var firstFkCol = fkCols.First(); firstFkCol.Column.EntityFk.Add(fkd); string manyToManyMapping, mapKey; if (foreignKeys.Count > 1) { manyToManyMapping = string.Format("c => new {{ {0} }}", string.Join(", ", fkCols.Select(x => "c." + x.Column.NameHumanCase).Distinct().ToArray())); mapKey = string.Format("{0}", string.Join(",", fkCols.Select(x => "\"" + x.Column.DbName + "\"").Distinct().ToArray())); } else { manyToManyMapping = string.Format("c => c.{0}", firstFkCol.Column.NameHumanCase); mapKey = string.Format("\"{0}\"", firstFkCol.Column.DbName); } var primaryKeyColumns = string.Empty; if (!allPkColsArePrimaryKeys) { if (pkCols.Count > 1) primaryKeyColumns = string.Format("p => new {{ {0} }}", string.Join(", ", pkCols.Select(x => "p." + x.Column.NameHumanCase).Distinct().ToArray())); else primaryKeyColumns = string.Format("p => p.{0}", pkCols.First().Column.NameHumanCase); } var fkCols2 = fkCols.Select(c => c.Column).ToList(); var pkCols2 = pkCols.Select(c => c.Column).ToList(); var rel = GetRelationship(relationship, fkCols2, pkCols2, pkPropName, fkPropName, manyToManyMapping, mapKey, foreignKey.CascadeOnDelete, foreignKey.IncludeReverseNavigation, foreignKey.IsNotEnforced, foreignKey.ConstraintName, pkTableHumanCase, fkTable.NameHumanCaseWithSuffix(), primaryKeyColumns, fkHasUniqueConstraint); var com = Settings.IncludeComments != CommentsStyle.None && string.IsNullOrEmpty(GetForeignKeyConstraintName("x")) ? " // " + foreignKey.ConstraintName : string.Empty; firstFkCol.Column.ConfigFk.Add(string.Format("{0};{1}", rel, com)); if (foreignKey.IncludeReverseNavigation) pkTable.AddReverseNavigation(relationship, fkTable, fkPropName, string.Format("{0}.{1}", fkTable.DbName, foreignKey.ConstraintName), foreignKeys); } } private void IdentifyForeignKeys(List fkList, Tables tables) { foreach (var foreignKey in fkList) { var fkTable = tables.GetTable(foreignKey.FkTableName, foreignKey.FkSchema); if (fkTable == null) continue; // Could be filtered out var pkTable = tables.GetTable(foreignKey.PkTableName, foreignKey.PkSchema); if (pkTable == null) continue; // Could be filtered out var fkCol = fkTable.Columns.Find(n => string.Equals(n.DbName, foreignKey.FkColumn, StringComparison.InvariantCultureIgnoreCase)); if (fkCol == null) continue; // Could not find fk column var pkCol = pkTable.Columns.Find(n => string.Equals(n.DbName, foreignKey.PkColumn, StringComparison.InvariantCultureIgnoreCase)); if (pkCol == null) continue; // Could not find pk column fkTable.HasForeignKey = true; } } private string GetRelationship(Relationship relationship, IList fkCols, IList pkCols, string pkPropName, string fkPropName, string manyToManyMapping, string mapKey, bool cascadeOnDelete, bool includeReverseNavigation, bool isNotEnforced, string foreignKeyConstraintName, string pkTableNameHumanCase, string fkTableNameHumanCase, string primaryKeyColumns, bool fkHasUniqueConstraint) { var hasMethod = GetHasMethod(relationship, fkCols, pkCols, isNotEnforced, fkHasUniqueConstraint); if (hasMethod == null) return string.Empty; // Relationship not supported var withMethod = GetWithMethod(relationship, fkCols, fkPropName, manyToManyMapping, mapKey, includeReverseNavigation, hasMethod, pkTableNameHumanCase, fkTableNameHumanCase, primaryKeyColumns, fkHasUniqueConstraint); return string.Format("{0}(a => a.{1}){2}{3}{4}", hasMethod, pkPropName, withMethod, GetCascadeOnDelete(cascadeOnDelete), GetForeignKeyConstraintName(foreignKeyConstraintName)); } // Calculates the relationship between a child table and it's parent table. public static Relationship CalcRelationship(Table parentTable, Table childTable, List fkCols, List pkCols) { var childTableCols = fkCols.Select(c => c.Column).ToList(); var parentTableCols = pkCols.Select(c => c.Column).ToList(); if (childTableCols.Count == 1 && parentTableCols.Count == 1) return CalcRelationshipSingle(parentTable, childTable, childTableCols.First(), parentTableCols.First()); // This relationship has multiple composite keys // childTable FK columns are exactly the primary key (they are part of primary key, and no other columns are primary keys) // TODO: we could also check if they are a unique index var childTableColumnsAllPrimaryKeys = (childTableCols.Count == childTableCols.Count(x => x.IsPrimaryKey)) && (childTableCols.Count == childTable.PrimaryKeys.Count()); // parentTable columns are exactly the primary key (they are part of primary key, and no other columns are primary keys) // TODO: we could also check if they are a unique index var parentTableColumnsAllPrimaryKeys = (parentTableCols.Count == parentTableCols.Count(x => x.IsPrimaryKey)) && (parentTableCols.Count == parentTable.PrimaryKeys.Count()); // childTable FK columns are not only FK but also the whole PK (not only part of PK); parentTable columns are the whole PK (not only part of PK) - so it's 1:1 if (childTableColumnsAllPrimaryKeys && parentTableColumnsAllPrimaryKeys) return Relationship.OneToOne; // Check if covered by a unique constraint if(fkCols.All(x => x.ForeignKey.HasUniqueConstraint)) return Relationship.OneToOne; // Check if covered by a unique index on PK table if (parentTableCols.All(x => x.IsUnique)) return Relationship.OneToOne; return Relationship.ManyToOne; } // Calculates the relationship between a child table and it's parent table. public static Relationship CalcRelationshipSingle(Table parentTable, Table childTable, Column childTableCol, Column parentTableCol) { if (!childTableCol.IsPrimaryKey && !childTableCol.IsUniqueConstraint) return Relationship.ManyToOne; if (!parentTableCol.IsPrimaryKey && !parentTableCol.IsUniqueConstraint) return Relationship.ManyToOne; if (childTable.PrimaryKeys.Count() != 1) return Relationship.ManyToOne; if (parentTable.PrimaryKeys.Count() != 1) return Relationship.ManyToOne; return Relationship.OneToOne; } public static Relationship FlipRelationship(Relationship relationship) { switch (relationship) { case Relationship.OneToOne: return Relationship.OneToOne; case Relationship.OneToMany: return Relationship.ManyToOne; case Relationship.ManyToOne: return Relationship.OneToMany; case Relationship.ManyToMany: return Relationship.ManyToMany; case Relationship.DoNotUse: return Relationship.DoNotUse; default: throw new ArgumentOutOfRangeException(nameof(relationship), relationship, null); } } public void SetupEntityAndConfig(Column c) { SetupEntity(c); SetupConfig(c); } public void GenerateCode() { try { CreateOutputFolders(); var fallback = Settings.TemplateFolder; foreach (var filter in FilterList.GetFilters()) { _fileManagementService.UseFileManager(filter.Key); if (!Settings.GenerateSingleDbContext) { // Multi-context Settings.DbContextInterfaceName = null; Settings.DbContextName = ((MultiContextFilter) filter.Value).GetSettings().Name ?? filter.Key; if (Settings.TemplateType == TemplateType.FileBasedCore3 || Settings.TemplateType == TemplateType.FileBasedCore6) { // Use file based templates, set the path var multiContextSetting = ((MultiContextFilter) filter.Value).GetSettings(); if (multiContextSetting != null && !string.IsNullOrEmpty(multiContextSetting.TemplatePath)) Settings.TemplateFolder = multiContextSetting.TemplatePath; } } GenerateCode(filter.Value); Settings.TemplateFolder = fallback; // Reset back } } catch (Exception x) { var error = FormatError(x); _fileManagementService.Error(string.Empty); _fileManagementService.Error("// -----------------------------------------------------------------------------------------"); _fileManagementService.Error(string.Format("// Failed to generate the code in GenerateCode() - {0}", error)); _fileManagementService.Error("/*" + x.StackTrace + "*/"); _fileManagementService.Error("// -----------------------------------------------------------------------------------------"); _fileManagementService.Error(string.Empty); } } private void CreateOutputFolders() { if (Settings.FileManagerType != FileManagerType.EfCore || Settings.GenerateSeparateFiles != true) return; CreateOutputFolder(Settings.ContextFolder); CreateOutputFolder(Settings.InterfaceFolder); CreateOutputFolder(Settings.PocoFolder); CreateOutputFolder(Settings.PocoConfigurationFolder); } private void CreateOutputFolder(string folder) { if (string.IsNullOrEmpty(folder)) return; var fullPath = Path.Combine(Settings.Root, folder); try { Directory.CreateDirectory(fullPath); } catch (Exception x) { var error = FormatError(x); _fileManagementService.Error(string.Empty); _fileManagementService.Error(string.Format("// Unable to create folder: {0} Error: {1}", fullPath, error)); _fileManagementService.Error("/*" + x.StackTrace + "*/"); _fileManagementService.Error(string.Empty); } } private void GenerateCode(IDbContextFilter filter) { var codeGenerator = new CodeGenerator(this, filter); const string contextInterface = "contextInterface:"; const string contextFactory = "contextFactory:"; const string contextClass = "contextClass:"; const string contextFakeClass = "contextFakeClass:"; const string contextFakeDbSet = "contextFakeDbSet:"; const string pocoClass = "pocoClass:"; const string pocoConfiguration= "pocoConfiguration:"; const string spReturnModels = "spReturnModel:"; const string enumType = "enumType:"; var codeOutputList = new CodeOutputList(); codeOutputList.Add(contextInterface, codeGenerator.GenerateInterface()); codeOutputList.Add(contextFactory, codeGenerator.GenerateFactory()); codeOutputList.Add(contextClass, codeGenerator.GenerateContext()); codeOutputList.Add(contextFakeClass, codeGenerator.GenerateFakeContext()); codeOutputList.Add(contextFakeDbSet, codeGenerator.GenerateFakeDbSet()); var isEfCore3Plus = Settings.IsEfCore3Plus(); foreach (var table in filter.Tables .Where(t => !t.IsMapping) .OrderBy(x => x.NameHumanCase)) { // Write poco class, even if it has no primary key, for completeness. codeOutputList.Add(pocoClass + table.NameHumanCase, codeGenerator.GeneratePoco(table)); // Only write the config if it has a primary key if (table.HasPrimaryKey || (table.IsView && isEfCore3Plus)) codeOutputList.Add(pocoConfiguration + table.NameHumanCase, codeGenerator.GeneratePocoConfiguration(table)); } foreach (var sp in filter.StoredProcs .Where(x => x.ReturnModels.Count > 0 && x.ReturnModels.Any(returnColumns => returnColumns.Any()) && !Settings.StoredProcedureReturnTypes.ContainsKey(x.NameHumanCase) && !Settings.StoredProcedureReturnTypes.ContainsKey(x.DbName)) .OrderBy(x => x.NameHumanCase)) { var key = spReturnModels + sp.WriteStoredProcReturnModelName(filter); codeOutputList.Add(key, codeGenerator.GenerateStoredProcReturnModel(sp)); } foreach (var enumeration in filter.Enums) { codeOutputList.Add(enumType + enumeration.EnumName, codeGenerator.GenerateEnum(enumeration)); } _fileHeaderFooter = new FileHeaderFooter(filter.SubNamespace); if (!Settings.GenerateSeparateFiles) { var preHeader = _preHeaderInfo.ToString(); if(!string.IsNullOrWhiteSpace(preHeader)) _fileManagementService.WriteLine(preHeader.Trim()); var header = _fileHeaderFooter.Header; if (!string.IsNullOrWhiteSpace(header)) _fileManagementService.WriteLine(header.Trim()); var usings = codeGenerator.GenerateUsings(codeOutputList.GetUsings()); if (!string.IsNullOrWhiteSpace(usings)) { _fileManagementService.WriteLine(""); _fileManagementService.WriteLine(usings.Trim()); } var ns = _fileHeaderFooter.Namespace; if (!string.IsNullOrWhiteSpace(ns)) { _fileManagementService.WriteLine(""); _fileManagementService.WriteLine(ns.Trim()); } } // Write the pre header info with the database context and it's interface if(codeOutputList.Files.ContainsKey(contextInterface)) WriteCodeOutput(codeGenerator, codeOutputList.Files[contextInterface], true, firstInGroup: true); if(codeOutputList.Files.ContainsKey(contextClass)) WriteCodeOutput(codeGenerator, codeOutputList.Files[contextClass], true, firstInGroup: true); if(codeOutputList.Files.ContainsKey(contextFactory)) WriteCodeOutput(codeGenerator, codeOutputList.Files[contextFactory], true); if(codeOutputList.Files.ContainsKey(contextFakeClass)) WriteCodeOutput(codeGenerator, codeOutputList.Files[contextFakeClass], true, firstInGroup: true); if(codeOutputList.Files.ContainsKey(contextFakeDbSet)) WriteCodeOutput(codeGenerator, codeOutputList.Files[contextFakeDbSet], true); WriteCodeOutputForGroup(codeGenerator, "POCO classes", true, codeOutputList.Files .Where(x => x.Key.StartsWith(pocoClass)) .OrderBy(x => x.Key) .Select(x => x.Value) .ToList()); WriteCodeOutputForGroup(codeGenerator, "POCO Configuration", true, codeOutputList.Files .Where(x => x.Key.StartsWith(pocoConfiguration)) .OrderBy(x => x.Key) .Select(x => x.Value) .ToList()); WriteCodeOutputForGroup(codeGenerator, "Enumerations", true, codeOutputList.Files .Where(x => x.Key.StartsWith(enumType)) .OrderBy(x => x.Key) .Select(x => x.Value) .ToList()); WriteCodeOutputForGroup(codeGenerator, "Stored procedure return models", true, codeOutputList.Files .Where(x => x.Key.StartsWith(spReturnModels)) .OrderBy(x => x.Key) .Select(x => x.Value) .ToList()); if (!Settings.GenerateSeparateFiles) _fileManagementService.WriteLine(_fileHeaderFooter.Footer); } private void WriteCodeOutputForGroup(CodeGenerator codeGenerator, string regionNameForGroup, bool writePreHeaderInfo, List list) { var count = 0; var max = list.Count; foreach (var co in list) { ++count; WriteCodeOutput(codeGenerator, co, writePreHeaderInfo, regionNameForGroup, count == 1, count == max); } } private void WriteCodeOutput(CodeGenerator codeGenerator, CodeOutput code, bool writePreHeaderInfo, string regionNameForGroup = null, bool firstInGroup = false, bool lastInGroup = false) { if (Settings.GenerateSeparateFiles) { _fileManagementService.EndBlock(); _fileManagementService.StartNewFile(code.Filename); // If generating separate files, check if the db context is the same name as the tt filename. // If it is the same, force writing to outer. if (Path.GetFileNameWithoutExtension(code.Filename).Equals(Settings.TemplateFile, StringComparison.CurrentCultureIgnoreCase)) _fileManagementService.ForceWriteToOuter = true; if (writePreHeaderInfo) { var preHeader = _preHeaderInfo.ToString(); if (!string.IsNullOrWhiteSpace(preHeader)) _fileManagementService.WriteLine(preHeader.Trim()); } var header = _fileHeaderFooter.Header; if (!string.IsNullOrWhiteSpace(header)) _fileManagementService.WriteLine(header.Trim()); var usings = codeGenerator.GenerateUsings(code.GetUsings()); if (!string.IsNullOrWhiteSpace(usings)) { _fileManagementService.WriteLine(""); _fileManagementService.WriteLine(usings.Trim()); } var ns = _fileHeaderFooter.Namespace; if (!string.IsNullOrWhiteSpace(ns)) { _fileManagementService.WriteLine(""); _fileManagementService.WriteLine(ns.Trim()); } } WriteLines(IndentCode(code, regionNameForGroup, firstInGroup, lastInGroup)); if (Settings.GenerateSeparateFiles) _fileManagementService.WriteLine(_fileHeaderFooter.Footer); _fileManagementService.ForceWriteToOuter = false; } private List IndentCode(CodeOutput output, string regionNameForGroup, bool firstInGroup, bool lastInGroup) { if (output == null) return null; var indentNum = Settings.UseNamespace ? 1 : 0; var useRegion = !Settings.GenerateSeparateFiles && !string.IsNullOrEmpty(output.Region); var lines = new List(); if (Settings.UseRegions && useRegion) { lines.Add(IndentedStringBuilder(indentNum, "#region " + output.Region)); lines.Add(null); // Add blank line after the region } else { if (Settings.UseRegions && !Settings.GenerateSeparateFiles && firstInGroup && !string.IsNullOrEmpty(regionNameForGroup)) { lines.Add(IndentedStringBuilder(indentNum, "#region " + regionNameForGroup)); lines.Add(null); // Add blank line after the region group } } if (firstInGroup && (_hasAcademicLicence || _hasTrialLicence)) { lines.Add(IndentedStringBuilder(indentNum, "// ****************************************************************************************************")); lines.Add(IndentedStringBuilder(indentNum, "// This is not a commercial licence, therefore only a few tables/views/stored procedures are generated.")); lines.Add(IndentedStringBuilder(indentNum, "// ****************************************************************************************************")); lines.Add(null); } if (Settings.IncludeCodeGeneratedAttribute) lines.Add(IndentedStringBuilder(indentNum, _codeGeneratedAttribute)); lines.AddRange(IndentedStringBuilder(indentNum, output.Code)); if (useRegion || (!Settings.GenerateSeparateFiles && lastInGroup && !string.IsNullOrEmpty(regionNameForGroup))) { if (Settings.UseRegions) { lines.Add(null); // Include blank line after #endregion lines.Add(IndentedStringBuilder(indentNum, "#endregion")); lines.Add(null); // Include blank line after #endregion } else { lines.Add(null); // Include blank line } } return lines; } protected string IndentedStringBuilder(int indentNum, string line) { var indent = new string(' ', indentNum * Indent); return string.IsNullOrWhiteSpace(line) ? string.Empty : string.Format("{0}{1}", indent, line); } protected IEnumerable IndentedStringBuilder(int indentNum, List lines) { var indent = new string(' ', indentNum * Indent); return ( from line in lines select string.IsNullOrWhiteSpace(line) ? string.Empty : string.Format("{0}{1}", indent, line) ); } private void WriteLines(List lines) { foreach (var line in lines) _fileManagementService.WriteLine(line); } private void BuildPreHeaderInfo(Licence licence) { if (Settings.ShowLicenseInfo) { _preHeaderInfo.Append("// This code was generated by EntityFramework Reverse POCO Generator"); if (Settings.IncludeGeneratorVersionInCode) { _preHeaderInfo.Append(" "); _preHeaderInfo.Append(EfrpgVersion.Version()); } _preHeaderInfo.AppendLine(" (http://www.reversepoco.co.uk/)."); _preHeaderInfo.AppendLine("// Created by Simon Hughes (https://about.me/simon.hughes)."); _preHeaderInfo.AppendLine("//"); _preHeaderInfo.AppendLine(string.Format("// {0}{1}", LicenceConstants.RegisteredTo, licence.RegisteredTo)); _preHeaderInfo.AppendLine(string.Format("// {0}{1}", LicenceConstants.Company, licence.Company)); _preHeaderInfo.AppendLine(string.Format("// {0}{1}", LicenceConstants.LicenceType, licence.GetLicenceType())); _preHeaderInfo.AppendLine(string.Format("// {0}{1}", LicenceConstants.NumLicences, licence.NumLicences)); _preHeaderInfo.AppendLine(string.Format("// {0}{1}", LicenceConstants.ValidUntil, licence.ValidUntil.ToString(LicenceConstants.ExpiryFormat).ToUpperInvariant())); _preHeaderInfo.AppendLine("//"); } if (Settings.IncludeConnectionSettingComments) { _preHeaderInfo.AppendLine("// The following connection settings were used to generate this file:"); if (!string.IsNullOrEmpty(Settings.ConnectionStringName)) _preHeaderInfo.AppendLine(string.Format("// Connection String Name: \"{0}\"", Settings.ConnectionStringName)); _preHeaderInfo.AppendLine(string.Format("// Connection String: \"{0}\"", ZapPassword(Settings.ConnectionString))); if (!Settings.GenerateSingleDbContext) { var conn = string.IsNullOrWhiteSpace(Settings.MultiContextSettingsConnectionString) ? Settings.ConnectionString : Settings.MultiContextSettingsConnectionString; _preHeaderInfo.AppendLine(string.Format("// Multi-context settings: \"{0}\"", ZapPassword(conn))); } _preHeaderInfo.AppendLine("//"); } } private string ZapPassword(string conn) { var rx = new Regex("password=[^\";]*", RegexOptions.Singleline | RegexOptions.Multiline | RegexOptions.IgnoreCase); return rx.Replace(conn, "password=**zapped**;"); } private string FormatError(Exception ex) { return ex.Message.Replace("\r\n", "\n").Replace("\n", " "); } } // Fill in the following functions with your own code if not using EF6 / EfCore // Take a look at GeneratorEf6 and GeneratorEfCore to see what's been done for those. public class GeneratorCustom : Generator { public GeneratorCustom(FileManagementService fileManagementService, Type fileManagerType) : base(fileManagementService, fileManagerType) { } protected override bool AllowFkToNonPrimaryKey() { return false; } protected override bool FkMustHaveSameNumberOfColumnsAsPrimaryKey() { return true; } protected override void SetupEntity(Column c) { } protected override void SetupConfig(Column c) { c.Config = string.Empty; } public override string PrimaryKeyModelBuilder(Table table) { return null; } public override List IndexModelBuilder(Table t) { return null; } public override string IndexModelBuilder(Column c) { return null; } protected override string GetHasMethod(Relationship relationship, IList fkCols, IList pkCols, bool isNotEnforced, bool fkHasUniqueConstraint) { return null; } protected override string GetWithMethod(Relationship relationship, IList fkCols, string fkPropName, string manyToManyMapping, string mapKey, bool includeReverseNavigation, string hasMethod, string pkTableNameHumanCase, string fkTableNameHumanCase, string primaryKeyColumns, bool fkHasUniqueConstraint) { return string.Empty; } protected override string GetCascadeOnDelete(bool cascadeOnDelete) { return string.Empty; } protected override string GetForeignKeyConstraintName(string foreignKeyConstraintName) { return string.Empty; } } public class GeneratorEf6 : Generator { public GeneratorEf6(FileManagementService fileManagementService, Type fileManagerType) : base(fileManagementService, fileManagerType) { } protected override bool AllowFkToNonPrimaryKey() { return false; // Cannot have a FK to a non-primary key } protected override bool FkMustHaveSameNumberOfColumnsAsPrimaryKey() { return true; } protected override void SetupEntity(Column c) { if (c.PropertyType == "Hierarchy.HierarchyId") c.PropertyType = "System.Data.Entity.Hierarchy.HierarchyId"; var comments = string.Empty; if (Settings.IncludeComments != CommentsStyle.None) { comments = c.DbName; if (c.IsPrimaryKey) { if (c.IsUniqueConstraint) comments += " (Primary key via unique index " + c.UniqueIndexName + ")"; else comments += " (Primary key)"; } if (c.MaxLength > 0) comments += string.Format(" (length: {0})", c.MaxLength); } c.InlineComments = Settings.IncludeComments == CommentsStyle.AtEndOfField ? " // " + comments : string.Empty; c.SummaryComments = string.Empty; if (Settings.IncludeComments == CommentsStyle.InSummaryBlock && !string.IsNullOrEmpty(comments)) { c.SummaryComments = comments; } if (Settings.IncludeExtendedPropertyComments == CommentsStyle.InSummaryBlock && !string.IsNullOrEmpty(c.ExtendedProperty)) { if (string.IsNullOrEmpty(c.SummaryComments)) c.SummaryComments = c.ExtendedProperty; else c.SummaryComments += ". " + c.ExtendedProperty; } if (Settings.IncludeExtendedPropertyComments == CommentsStyle.AtEndOfField && !string.IsNullOrEmpty(c.ExtendedProperty)) { if (string.IsNullOrEmpty(c.InlineComments)) c.InlineComments = " // " + c.ExtendedProperty; else c.InlineComments += ". " + c.ExtendedProperty; } } protected override void SetupConfig(Column c) { string databaseGeneratedOption = null; var isNewSequentialId = !string.IsNullOrEmpty(c.Default) && c.Default.ToLower().Contains("newsequentialid"); var isTemporalColumn = c.GeneratedAlwaysType != ColumnGeneratedAlwaysType.NotApplicable; // Identity, instead of Computed, seems the best for Temporal `GENERATED ALWAYS` columns: https://stackoverflow.com/questions/40742142/entity-framework-not-working-with-temporal-table if (c.IsIdentity || isNewSequentialId || isTemporalColumn) { databaseGeneratedOption = ".HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)"; } else if (c.IsComputed) { databaseGeneratedOption = ".HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed)"; } else if (c.IsPrimaryKey) { databaseGeneratedOption = ".HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)"; } var sb = new StringBuilder(255); sb.AppendFormat(".HasColumnName(@\"{0}\")", c.DbName); var excludedHasColumnType = string.Empty; if (!Settings.UseDataAnnotations && !string.IsNullOrEmpty(c.SqlPropertyType)) { if(Column.ExcludedHasColumnType.Contains(c.SqlPropertyType)) excludedHasColumnType = string.Format(" // .HasColumnType(\"{0}\") was excluded", c.SqlPropertyType); else sb.AppendFormat(".HasColumnType(\"{0}\")", c.SqlPropertyType); } sb.Append(c.IsNullable ? ".IsOptional()" : ".IsRequired()"); if (c.IsFixedLength || c.IsRowVersion) sb.Append(".IsFixedLength()"); if (!c.IsUnicode) sb.Append(".IsUnicode(false)"); if (!c.IsMaxLength && c.MaxLength > 0) { var doNotSpecifySize = (DatabaseReader.DoNotSpecifySizeForMaxLength && c.MaxLength > 4000); // Issue #179 if (doNotSpecifySize) sb.Append(".HasMaxLength(null)"); else sb.AppendFormat(".HasMaxLength({0})", c.MaxLength); } if (c.IsMaxLength) sb.Append(".IsMaxLength()"); if ((c.Precision > 0 || c.Scale > 0) && DatabaseReader.IsPrecisionAndScaleType(c.SqlPropertyType)) sb.AppendFormat(".HasPrecision({0},{1})", c.Precision, c.Scale); else if (c.Precision > 0 && DatabaseReader.IsPrecisionType(c.SqlPropertyType) && c.SqlPropertyType != "float") sb.AppendFormat(".HasPrecision({0})", c.Precision); if (c.IsRowVersion) { sb.Append(".IsRowVersion()"); c.IsConcurrencyToken = true; } if (c.IsConcurrencyToken) sb.Append(".IsConcurrencyToken()"); if (databaseGeneratedOption != null) sb.Append(databaseGeneratedOption); var config = sb.ToString(); if (!string.IsNullOrEmpty(config)) c.Config = string.Format("Property(x => x.{0}){1};{2}", c.NameHumanCase, config, excludedHasColumnType); } public override string PrimaryKeyModelBuilder(Table table) { return null; } public override List IndexModelBuilder(Table t) { return null; } public override string IndexModelBuilder(Column c) { var sb = new StringBuilder(1024); var indexes = c.Indexes.Where(x => !x.IsPrimaryKey).OrderBy(x => x.IndexName).ThenBy(x => x.KeyOrdinal).ToList(); var count = indexes.Count; var first = true; var cannotUseAdded = false; var closeBrackets = false; var n = count; foreach (var index in indexes) { --n; if (first) { sb.AppendLine($"modelBuilder.Entity<{c.ParentTable.NameHumanCaseWithSuffix()}>()"); sb.AppendLine($" .Property(e => e.{c.NameHumanCase})"); sb.AppendLine(" .HasColumnAnnotation("); sb.AppendLine(" IndexAnnotation.AnnotationName,"); } if (count == 1) sb.AppendLine($" new IndexAnnotation({AddIndexAttribute(index)})"); else { if (first) { sb.AppendLine(" new IndexAnnotation(new[]"); sb.AppendLine(" {"); closeBrackets = true; } sb.Append(" "); sb.Append(AddIndexAttribute(index)); if (n > 0) sb.Append(","); sb.AppendLine(); } first = false; if (n == 0) { sb.AppendLine(closeBrackets ? " }));" : " );"); } } if (cannotUseAdded) sb.Append(" */"); return sb.ToString(); } private string AddIndexAttribute(RawIndex rawIndex) { var sb = new StringBuilder(255); var properties = new List(); sb.Append($"new IndexAttribute(\"{rawIndex.IndexName}\", {rawIndex.KeyOrdinal})"); if (rawIndex.IsUnique) properties.Add("IsUnique = true"); if (rawIndex.IsClustered) properties.Add("IsClustered = true"); if (properties.Any()) { sb.Append(" { "); sb.Append($"{string.Join(", ", properties)}"); sb.Append(" }"); } return sb.ToString(); } // HasOptional // HasRequired // HasMany protected override string GetHasMethod(Relationship relationship, IList fkCols, IList pkCols, bool isNotEnforced, bool fkHasUniqueConstraint) { var withMany = relationship == Relationship.ManyToOne || relationship == Relationship.ManyToMany; var fkIsNullable = fkCols.Any(c => c.IsNullable); var pkIsUnique = pkCols.Any(c => c.IsUnique || c.IsUniqueConstraint || c.IsPrimaryKey); if (withMany || pkIsUnique || fkHasUniqueConstraint) { if (fkIsNullable || isNotEnforced) return "HasOptional"; return "HasRequired"; } return "HasMany"; } // WithOptional // WithRequired // WithMany // WithRequiredPrincipal // WithRequiredDependent protected override string GetWithMethod(Relationship relationship, IList fkCols, string fkPropName, string manyToManyMapping, string mapKey, bool includeReverseNavigation, string hasMethod, string pkTableNameHumanCase, string fkTableNameHumanCase, string primaryKeyColumns, bool fkHasUniqueConstraint) { var withParam = includeReverseNavigation ? string.Format("b => b.{0}", fkPropName) : string.Empty; switch (relationship) { case Relationship.OneToOne: if (hasMethod == "HasOptional") return string.Format(".WithOptionalPrincipal({0})", withParam); return string.Format(".WithOptional({0})", withParam); case Relationship.OneToMany: return string.Format(".WithRequiredDependent({0})", withParam); case Relationship.ManyToOne: if (!fkCols.Any(c => c.Hidden)) return string.Format(".WithMany({0}).HasForeignKey({1})", withParam, manyToManyMapping); // Foreign Key Association return string.Format(".WithMany({0}).Map(c => c.MapKey({1}))", withParam, mapKey); // Independent Association case Relationship.ManyToMany: return string.Format(".WithMany({0}).HasForeignKey({1})", withParam, manyToManyMapping); default: throw new ArgumentOutOfRangeException(nameof(relationship)); } } protected override string GetCascadeOnDelete(bool cascadeOnDelete) { return cascadeOnDelete ? string.Empty : ".WillCascadeOnDelete(false)"; } protected override string GetForeignKeyConstraintName(string foreignKeyConstraintName) { return string.Empty; } } public class GeneratorEfCore : Generator { public GeneratorEfCore(FileManagementService fileManagementService, Type fileManagerType) : base(fileManagementService, fileManagerType) { } protected override bool AllowFkToNonPrimaryKey() { return true; // It is allowed to have a FK to a non-primary key } protected override bool FkMustHaveSameNumberOfColumnsAsPrimaryKey() { return true; } protected override void SetupEntity(Column c) { if (c.PropertyType == "Hierarchy.HierarchyId") c.PropertyType = Settings.IsEfCore6Plus() ? "HierarchyId" : "Microsoft.SqlServer.Types.SqlHierarchyId"; var comments = string.Empty; if (Settings.IncludeComments != CommentsStyle.None) { comments = c.DbName; if (c.IsPrimaryKey) { if (c.IsUniqueConstraint) comments += " (Primary key via unique index " + c.UniqueIndexName + ")"; else comments += " (Primary key)"; } if (c.MaxLength > 0) comments += string.Format(" (length: {0})", c.MaxLength); } c.InlineComments = Settings.IncludeComments == CommentsStyle.AtEndOfField ? " // " + comments : string.Empty; c.SummaryComments = string.Empty; if (Settings.IncludeComments == CommentsStyle.InSummaryBlock && !string.IsNullOrEmpty(comments)) { c.SummaryComments = comments; } if (Settings.IncludeExtendedPropertyComments == CommentsStyle.InSummaryBlock && !string.IsNullOrEmpty(c.ExtendedProperty)) { if (string.IsNullOrEmpty(c.SummaryComments)) c.SummaryComments = c.ExtendedProperty; else c.SummaryComments += ". " + c.ExtendedProperty; } if (Settings.IncludeExtendedPropertyComments == CommentsStyle.AtEndOfField && !string.IsNullOrEmpty(c.ExtendedProperty)) { if (string.IsNullOrEmpty(c.InlineComments)) c.InlineComments = " // " + c.ExtendedProperty; else c.InlineComments += ". " + c.ExtendedProperty; } } protected override void SetupConfig(Column c) { string databaseGeneratedOption = null; var isNewSequentialId = !string.IsNullOrEmpty(c.Default) && c.Default.ToLower().Contains("newsequentialid"); var isTemporalColumn = c.GeneratedAlwaysType != ColumnGeneratedAlwaysType.NotApplicable; var hasDefaultValueSql = !string.IsNullOrEmpty(c.HasDefaultValueSql); // Identity, instead of Computed, seems the best for Temporal 'GENERATED ALWAYS' columns: https://stackoverflow.com/questions/40742142/entity-framework-not-working-with-temporal-table if (!hasDefaultValueSql) { if (c.IsIdentity || isNewSequentialId || isTemporalColumn) { databaseGeneratedOption = ".ValueGeneratedOnAdd()"; if (c.IsIdentity && DatabaseReader.HasIdentityColumnSupport() && Column.CanUseSqlServerIdentityColumn.Contains(c.PropertyType)) databaseGeneratedOption += Settings.ColumnIdentity(c); } else if (c.IsComputed) { databaseGeneratedOption = ".ValueGeneratedOnAddOrUpdate()"; } else if (c.IsPrimaryKey) { databaseGeneratedOption = ".ValueGeneratedNever()"; } } var sb = new StringBuilder(255); sb.AppendFormat(".HasColumnName(@\"{0}\")", c.DbName); var doNotSpecifySize = false; if (!c.IsMaxLength && c.MaxLength > 0) doNotSpecifySize = (DatabaseReader.DoNotSpecifySizeForMaxLength && c.MaxLength > 4000); // Issue #179 var excludedHasColumnType = string.Empty; if (!string.IsNullOrEmpty(c.SqlPropertyType)) { var columnTypeParameters = string.Empty; if ((c.Precision > 0 || c.Scale > 0) && (c.SqlPropertyType == "decimal" || c.SqlPropertyType == "numeric")) columnTypeParameters = $"({c.Precision},{c.Scale})"; else if (!c.IsMaxLength && c.MaxLength > 0 && !doNotSpecifySize) columnTypeParameters = $"({c.MaxLength})"; if (Column.ExcludedHasColumnType.Contains(c.SqlPropertyType)) excludedHasColumnType = string.Format(" // .HasColumnType(\"{0}{1}\") was excluded", c.SqlPropertyType, columnTypeParameters); else sb.AppendFormat(".HasColumnType(\"{0}{1}\")", c.SqlPropertyType, columnTypeParameters); if (Settings.IsEfCore6Plus()) { if ((c.Precision > 0 || c.Scale > 0) && DatabaseReader.IsPrecisionAndScaleType(c.SqlPropertyType)) sb.AppendFormat(".HasPrecision({0},{1})", c.Precision, c.Scale); else if (c.Precision > 0 && DatabaseReader.IsPrecisionType(c.SqlPropertyType)) sb.AppendFormat(".HasPrecision({0})", c.Precision); } if (Settings.TrimCharFields && c.MaxLength > 1 && c.SqlPropertyType == "char") { sb.Append(".HasConversion(new ValueConverter(v => v.TrimEnd(), v => v.TrimEnd()))"); } } sb.Append(c.IsNullable ? ".IsRequired(false)" : ".IsRequired()"); if (c.IsFixedLength || c.IsRowVersion) sb.Append(".IsFixedLength()"); if (!c.IsUnicode) sb.Append(".IsUnicode(false)"); if (!c.IsMaxLength && c.MaxLength > 0 && !doNotSpecifySize) sb.AppendFormat(".HasMaxLength({0})", c.MaxLength); //if (c.IsMaxLength) // sb.Append(".IsMaxLength()"); if (c.IsRowVersion) { sb.Append(".IsRowVersion()"); c.IsConcurrencyToken = true; } if (c.IsConcurrencyToken) sb.Append(".IsConcurrencyToken()"); if (databaseGeneratedOption != null) sb.Append(databaseGeneratedOption); if (hasDefaultValueSql) sb.AppendFormat(".HasDefaultValueSql(@\"{0}\")", c.HasDefaultValueSql); var config = sb.ToString(); if (!string.IsNullOrEmpty(config)) c.Config = string.Format("builder.Property(x => x.{0}){1};{2}", c.NameHumanCase, config, excludedHasColumnType); } public override string PrimaryKeyModelBuilder(Table t) { var isEfCore3Plus = Settings.IsEfCore3Plus(); if (isEfCore3Plus && t.IsView && !t.HasPrimaryKey) return "builder.HasNoKey();"; if (t.PrimaryKeys.All(k => k.Hidden)) return string.Empty; var defaultKey = $"builder.HasKey({t.PrimaryKeyNameHumanCase()})"; if (t.Indexes == null || !t.Indexes.Any()) return defaultKey + ";"; var indexName = t.Indexes.Where(x => x.IsPrimaryKey).Select(x => x.IndexName).Distinct().FirstOrDefault(); if(string.IsNullOrEmpty(indexName)) return defaultKey + ";"; var indexesForName = t.Indexes .Where(x => x.IndexName == indexName) .OrderBy(x => x.KeyOrdinal) .ThenBy(x => x.ColumnName) .ToList(); var sb = new StringBuilder(255); sb.Append(defaultKey); sb.Append(".HasName(\""); sb.Append(indexName); sb.Append("\")"); if (indexesForName.All(x => x.IsClustered)) sb.Append(isEfCore3Plus ? ".IsClustered()" : ".ForSqlServerIsClustered()"); sb.Append(";"); return sb.ToString(); } public override List IndexModelBuilder(Table t) { var indexes = new List(); if (t.Indexes == null || !t.Indexes.Any()) return indexes; var isEfCore3Plus = Settings.IsEfCore3Plus(); var isEfCore5Plus = Settings.IsEfCore6Plus(); var indexNames = t.Indexes.Where(x => !x.IsPrimaryKey).Select(x => x.IndexName).Distinct(); foreach (var indexName in indexNames.OrderBy(x => x)) { var indexesForName = t.Indexes .Where(x => x.IndexName == indexName) .OrderBy(x => x.KeyOrdinal) .ThenBy(x => x.ColumnName) .ToList(); var sb = new StringBuilder(255); var ok = true; var count = 0; sb.Append("builder.HasIndex(x => "); if (indexesForName.Count > 1) sb.Append("new { "); foreach (var index in indexesForName.OrderBy(x => x.KeyOrdinal).ThenBy(x => x.ColumnName)) { var col = t.Columns.Find(x => x.DbName == index.ColumnName); if (col == null || col.Hidden || string.IsNullOrEmpty(col.Config)) { ok = false; break; // Cannot use index, as one of the columns is invalid } if (count > 0) sb.Append(", "); sb.Append("x."); sb.Append(col.NameHumanCase); ++count; } if (!ok) continue; if (indexesForName.Count > 1) sb.Append(" }"); sb.Append(")"); // Close bracket for HasIndex() sb.Append(isEfCore5Plus ? ".HasDatabaseName(\"" : ".HasName(\""); sb.Append(indexName); sb.Append("\")"); if (indexesForName.All(x => x.IsUnique) || indexesForName.All(x => x.IsUniqueConstraint)) sb.Append(".IsUnique()"); if (indexesForName.All(x => x.IsClustered)) sb.Append(isEfCore3Plus ? ".IsClustered()" : ".ForSqlServerIsClustered()"); sb.Append(";"); indexes.Add(sb.ToString()); } return indexes; } public override string IndexModelBuilder(Column c) { return null; } // HasOne // HasMany protected override string GetHasMethod(Relationship relationship, IList fkCols, IList pkCols, bool isNotEnforced, bool fkHasUniqueConstraint) { if (relationship == Relationship.ManyToMany) return null; // Not yet supported in EF.Core var withMany = relationship == Relationship.ManyToOne || relationship == Relationship.ManyToMany; var pkIsUnique = pkCols.Any(c => c.IsUnique || c.IsUniqueConstraint || c.IsPrimaryKey); if (withMany || pkIsUnique || fkHasUniqueConstraint) return "builder.HasOne"; return "builder.HasMany"; } // WithOne // WithMany protected override string GetWithMethod(Relationship relationship, IList fkCols, string fkPropName, string manyToManyMapping, string mapKey, bool includeReverseNavigation, string hasMethod, string pkTableNameHumanCase, string fkTableNameHumanCase, string primaryKeyColumns, bool fkHasUniqueConstraint) { var withParam = includeReverseNavigation ? string.Format("b => b.{0}", fkPropName) : string.Empty; var principalEntityType = fkHasUniqueConstraint ? string.Format("<{0}>", pkTableNameHumanCase) : string.Empty; var hasPrincipleKey = !string.IsNullOrEmpty(primaryKeyColumns) ? string.Format(".HasPrincipalKey{0}({1})", principalEntityType, primaryKeyColumns) : string.Empty; switch (relationship) { case Relationship.OneToOne: return string.Format(".WithOne({0}){1}.HasForeignKey<{2}>({3})", withParam, hasPrincipleKey, fkTableNameHumanCase, manyToManyMapping); case Relationship.OneToMany: return string.Format(".WithMany({0})", withParam); case Relationship.ManyToOne: case Relationship.ManyToMany: return string.Format(".WithMany({0}){1}.HasForeignKey({2})", withParam, hasPrincipleKey, manyToManyMapping); default: throw new ArgumentOutOfRangeException(nameof(relationship)); } } protected override string GetCascadeOnDelete(bool cascadeOnDelete) { return cascadeOnDelete ? string.Empty : ".OnDelete(DeleteBehavior.ClientSetNull)"; } protected override string GetForeignKeyConstraintName(string foreignKeyConstraintName) { return string.Format(".HasConstraintName(\"{0}\")", foreignKeyConstraintName); } } public static class GeneratorFactory { public static Generator Create(FileManagementService fileManagementService, Type fileManagerType, string singleDbContextSubNamespace = null) { Generator generator; switch (Settings.GeneratorType) { case GeneratorType.Ef6: generator = new GeneratorEf6(fileManagementService, fileManagerType); break; case GeneratorType.EfCore: generator = new GeneratorEfCore(fileManagementService, fileManagerType); break; case GeneratorType.Custom: generator = new GeneratorCustom(fileManagementService, fileManagerType); break; default: throw new ArgumentOutOfRangeException(); } var providerName = "unknown"; try { providerName = DatabaseProvider.GetProvider(Settings.DatabaseType); var factory = DbProviderFactories.GetFactory(providerName); var databaseReader = DatabaseReaderFactory.Create(factory); generator.Init(databaseReader, singleDbContextSubNamespace); return generator; } catch (Exception x) { var error = x.Message.Replace("\r\n", "\n").Replace("\n", " "); Console.WriteLine(error); fileManagementService.Error(generator.GetPreHeaderInfo()); fileManagementService.Error(string.Empty); fileManagementService.Error("// ------------------------------------------------------------------------------------------------"); fileManagementService.Error(string.Format("// WARNING: Failed to load provider \"{0}\" - {1}", providerName, error)); fileManagementService.Error("// Allowed providers:"); foreach (DataRow fc in DbProviderFactories.GetFactoryClasses().Rows) { var s = string.Format("// \"{0}\"", fc[2]); fileManagementService.Error(s); } fileManagementService.Error(string.Empty); fileManagementService.Error("/*" + x.StackTrace + "*/"); fileManagementService.Error("// ------------------------------------------------------------------------------------------------"); fileManagementService.Error(string.Empty); } return null; } } public class MultipleModelReturnColumns { public int Model { get; } public List ReturnColumns { get; } public MultipleModelReturnColumns(int model, List returnColumns) { Model = model; ReturnColumns = returnColumns; } } public class ResultSetResultReaderCommand { public int Index { get; } public string ReaderCommand { get; } public bool NotLastRecord { get; } public string WriteStoredProcReturnModelName { get; } public ResultSetResultReaderCommand(int index, string readerCommand, bool notLastRecord, string writeStoredProcReturnModelName) { Index = index; ReaderCommand = readerCommand; NotLastRecord = notLastRecord; WriteStoredProcReturnModelName = writeStoredProcReturnModelName; } } public class ScalarValuedFunctionsTemplateData { public string ExecName { get; } public string ReturnType { get; } public string WriteStoredProcFunctionParamsFalseTrue { get; } public string WriteStoredProcFunctionParamsFalseFalse { get; } public string Name { get; } public string Schema { get; } public ScalarValuedFunctionsTemplateData( string execName, string returnType, string writeStoredProcFunctionParamsFalseTrue, string writeStoredProcFunctionParamsFalseFalse, string name, string schema) { ExecName = execName; ReturnType = returnType; WriteStoredProcFunctionParamsFalseTrue = writeStoredProcFunctionParamsFalseTrue; WriteStoredProcFunctionParamsFalseFalse = writeStoredProcFunctionParamsFalseFalse; Name = name; Schema = schema; } } public class StoredProcTemplateData { public bool HasNoReturnModels { get; set; } // sp.ReturnModels.Count == 0 public bool HasReturnModels { get; } // sp.ReturnModels.Count > 0 public bool SingleReturnModel { get; } // sp.ReturnModels.Count == 1 public bool MultipleReturnModels { get; } // sp.ReturnModels.Count > 1 public string ReturnType { get; } // sp.WriteStoredProcReturnType() public string ReturnModelName { get; } // sp.WriteStoredProcReturnModelName() public string FunctionName { get; } // sp.WriteStoredProcFunctionName() public string WriteStoredProcFunctionParamsFalseFalse { get; } // WriteStoredProcFunctionParams(false, false, false) public string WriteStoredProcFunctionParamsFalseTrue { get; } // WriteStoredProcFunctionParams(false, true, false) public string WriteStoredProcFunctionParamsTrueFalse { get; } // WriteStoredProcFunctionParams(true, false, false) public string WriteStoredProcFunctionParamsTrueTrue { get; } // WriteStoredProcFunctionParams(true, true, false) public string WriteStoredProcFunctionParamsFalseFalseToken { get; } // WriteStoredProcFunctionParams(false, false, true) public string WriteStoredProcFunctionParamsFalseTrueToken { get; } // WriteStoredProcFunctionParams(false, true, true) public string WriteStoredProcFunctionParamsTrueFalseToken { get; } // WriteStoredProcFunctionParams(true, false, true) public string WriteStoredProcFunctionParamsTrueTrueToken { get; } // WriteStoredProcFunctionParams(true, true, true) public bool AsyncFunctionCannotBeCreated { get; } // !sp.StoredProcCanExecuteAsync() public string WriteStoredProcFunctionOverloadCall { get; } // sp.WriteStoredProcFunctionOverloadCall() public string WriteStoredProcFunctionSetSqlParametersFalse { get; } // WriteStoredProcFunctionSetSqlParameters(false) public string WriteStoredProcFunctionSetSqlParametersTrue { get; } // WriteStoredProcFunctionSetSqlParameters(true) public string Exec { get; } public string AsyncExec { get; } public string WriteStoredProcReturnModelName { get; } // sp.WriteStoredProcReturnModelName() public string WriteStoredProcFunctionSqlParameterAnonymousArrayTrue { get; } // sp.WriteStoredProcFunctionSqlParameterAnonymousArray(true, true, false) public string WriteStoredProcFunctionSqlParameterAnonymousArrayFalse { get; } // sp.WriteStoredProcFunctionSqlParameterAnonymousArray(false, true, false) public string WriteStoredProcFunctionSqlParameterAnonymousArrayTrueToken { get; } // sp.WriteStoredProcFunctionSqlParameterAnonymousArray(true, true, true) public string WriteStoredProcFunctionSqlParameterAnonymousArrayFalseToken { get; } // sp.WriteStoredProcFunctionSqlParameterAnonymousArray(false, true, true) public string WriteStoredProcFunctionDeclareSqlParameterTrue { get; } // sp.WriteStoredProcFunctionDeclareSqlParameter(true) public string WriteStoredProcFunctionDeclareSqlParameterFalse { get; } // sp.WriteStoredProcFunctionDeclareSqlParameter(false) public List Parameters { get; } public int ReturnModelsCount { get; } public string ExecWithNoReturnModel { get; } public List ReturnModelResultSetReaderCommand { get; } public bool CreateDbSetForReturnModel { get; set; } public StoredProcTemplateData( bool hasNoReturnModels, bool hasReturnModels, bool singleReturnModel, bool multipleReturnModels, string returnType, string returnModelName, string functionName, string writeStoredProcFunctionParamsFalseFalse, string writeStoredProcFunctionParamsFalseTrue, string writeStoredProcFunctionParamsTrueFalse, string writeStoredProcFunctionParamsTrueTrue, string writeStoredProcFunctionParamsFalseFalseToken, string writeStoredProcFunctionParamsFalseTrueToken, string writeStoredProcFunctionParamsTrueFalseToken, string writeStoredProcFunctionParamsTrueTrueToken, bool asyncFunctionCannotBeCreated, string writeStoredProcFunctionOverloadCall, string writeStoredProcFunctionSetSqlParametersFalse, string writeStoredProcFunctionSetSqlParametersTrue, string exec, string asyncExec, string writeStoredProcReturnModelName, string writeStoredProcFunctionSqlParameterAnonymousArrayTrue, string writeStoredProcFunctionSqlParameterAnonymousArrayFalse, string writeStoredProcFunctionSqlParameterAnonymousArrayTrueToken, string writeStoredProcFunctionSqlParameterAnonymousArrayFalseToken, string writeStoredProcFunctionDeclareSqlParameterTrue, string writeStoredProcFunctionDeclareSqlParameterFalse, List parameters, int returnModelsCount, string execWithNoReturnModel) { HasNoReturnModels = hasNoReturnModels; HasReturnModels = hasReturnModels; SingleReturnModel = singleReturnModel; MultipleReturnModels = multipleReturnModels; ReturnType = returnType; ReturnModelName = returnModelName; FunctionName = functionName; WriteStoredProcFunctionParamsFalseFalse = writeStoredProcFunctionParamsFalseFalse; WriteStoredProcFunctionParamsFalseTrue = writeStoredProcFunctionParamsFalseTrue; WriteStoredProcFunctionParamsTrueFalse = writeStoredProcFunctionParamsTrueFalse; WriteStoredProcFunctionParamsTrueTrue = writeStoredProcFunctionParamsTrueTrue; WriteStoredProcFunctionParamsFalseFalseToken = writeStoredProcFunctionParamsFalseFalseToken; WriteStoredProcFunctionParamsFalseTrueToken = writeStoredProcFunctionParamsFalseTrueToken; WriteStoredProcFunctionParamsTrueFalseToken = writeStoredProcFunctionParamsTrueFalseToken; WriteStoredProcFunctionParamsTrueTrueToken = writeStoredProcFunctionParamsTrueTrueToken; AsyncFunctionCannotBeCreated = asyncFunctionCannotBeCreated; WriteStoredProcFunctionOverloadCall = writeStoredProcFunctionOverloadCall; WriteStoredProcFunctionSetSqlParametersFalse = writeStoredProcFunctionSetSqlParametersFalse; WriteStoredProcFunctionSetSqlParametersTrue = writeStoredProcFunctionSetSqlParametersTrue; Exec = exec; AsyncExec = asyncExec; WriteStoredProcReturnModelName = writeStoredProcReturnModelName; WriteStoredProcFunctionSqlParameterAnonymousArrayTrue = writeStoredProcFunctionSqlParameterAnonymousArrayTrue; WriteStoredProcFunctionSqlParameterAnonymousArrayFalse = writeStoredProcFunctionSqlParameterAnonymousArrayFalse; WriteStoredProcFunctionSqlParameterAnonymousArrayTrueToken = writeStoredProcFunctionSqlParameterAnonymousArrayTrueToken; WriteStoredProcFunctionSqlParameterAnonymousArrayFalseToken = writeStoredProcFunctionSqlParameterAnonymousArrayFalseToken; WriteStoredProcFunctionDeclareSqlParameterTrue = writeStoredProcFunctionDeclareSqlParameterTrue; WriteStoredProcFunctionDeclareSqlParameterFalse = writeStoredProcFunctionDeclareSqlParameterFalse; Parameters = parameters; ReturnModelsCount = returnModelsCount; ExecWithNoReturnModel = execWithNoReturnModel; CreateDbSetForReturnModel = true; ReturnModelResultSetReaderCommand = new List(returnModelsCount); for (var n = 1; n <= returnModelsCount; ++n) { var lastRecord = n == returnModelsCount; ReturnModelResultSetReaderCommand.Add(new ResultSetResultReaderCommand(n, lastRecord ? "Close" : "NextResult", !lastRecord, writeStoredProcReturnModelName)); } } } public class TableTemplateData { public string DbSetName { get; } public string DbSetConfigName { get; } public string PluralTableName { get; } public string DbSetModifier { get; } public string Comment { get; } public string DbSetPrimaryKeys { get; } public Table Table { get; } public TableTemplateData(Table table) { Table = table; DbSetName = table.NameHumanCaseWithSuffix(); DbSetConfigName = table.NameHumanCaseWithSuffix() + Settings.ConfigurationClassName; PluralTableName = !string.IsNullOrWhiteSpace(table.PluralNameOverride) ? table.PluralNameOverride : Inflector.MakePlural(table.NameHumanCase); DbSetModifier = table.DbSetModifier; Comment = Settings.IncludeComments == CommentsStyle.None ? string.Empty : " // " + table.DbName; DbSetPrimaryKeys = string.Join(", ", table.PrimaryKeys.Select(x => "\"" + x.NameHumanCase + "\"")); } } public class TableValuedFunctionsTemplateData { public bool SingleReturnModel { get; } public string SingleReturnColumnName { get; } public string ExecName { get; } public string ReturnClassName { get; } public string PluralReturnClassName { get; } public string WriteStoredProcFunctionParamsFalseTrue { get; } public string WriteStoredProcFunctionParamsFalseFalse { get; } public string Name { get; } public string Schema { get; } public string WriteTableValuedFunctionDeclareSqlParameter { get; } public string WriteTableValuedFunctionSqlParameterAnonymousArray { get; } public string WriteStoredProcFunctionSqlAtParams { get; } public string FromSql { get; } public string QueryString { get; } public string ModelBuilderCommand { get; } public string ModelBuilderPostCommand { get; } public bool IncludeModelBuilder { get; } public TableValuedFunctionsTemplateData(bool singleReturnModel, string singleReturnColumnName, string execName, string returnClassName, string writeStoredProcFunctionParamsFalseTrue, string writeStoredProcFunctionParamsFalseFalse, string name, string schema, string writeTableValuedFunctionDeclareSqlParameter, string writeTableValuedFunctionSqlParameterAnonymousArray, string writeStoredProcFunctionSqlAtParams, string fromSql, string queryString, string modelBuilderCommand, string modelBuilderPostCommand, bool includeModelBuilder) { SingleReturnModel = singleReturnModel && !string.IsNullOrEmpty(singleReturnColumnName); SingleReturnColumnName = singleReturnColumnName; ExecName = execName; ReturnClassName = returnClassName; PluralReturnClassName = Inflector.MakePlural(returnClassName); WriteStoredProcFunctionParamsFalseTrue = writeStoredProcFunctionParamsFalseTrue; WriteStoredProcFunctionParamsFalseFalse = writeStoredProcFunctionParamsFalseFalse; Name = name; Schema = schema; WriteTableValuedFunctionDeclareSqlParameter = writeTableValuedFunctionDeclareSqlParameter; WriteTableValuedFunctionSqlParameterAnonymousArray = writeTableValuedFunctionSqlParameterAnonymousArray; WriteStoredProcFunctionSqlAtParams = writeStoredProcFunctionSqlAtParams; FromSql = fromSql; QueryString = queryString; ModelBuilderCommand = modelBuilderCommand; ModelBuilderPostCommand = modelBuilderPostCommand; IncludeModelBuilder = includeModelBuilder; } } public class HiLoSequence { public string Schema; public string Table; public string SequenceName; public string SequenceSchema; } public static class Inflector { public static IPluralizationService PluralisationService = null; public static List IgnoreWordsThatEndWith = new List(); /// /// Makes the plural. /// /// The word. /// public static string MakePlural(string word) { try { if (string.IsNullOrEmpty(word)) return string.Empty; if (PluralisationService == null) return word; if (word.Contains('_')) return MakePluralHelper(word, '_'); if (word.Contains(' ')) return MakePluralHelper(word, ' '); if (word.Contains('-')) return MakePluralHelper(word, '-'); if (IgnoreWordsThatEndWith != null && IgnoreWordsThatEndWith.Any(endsWith => word.EndsWith(endsWith, StringComparison.InvariantCulture))) return word; return PluralisationService.Pluralize(word); } catch (Exception) { return word; } } private static string MakePluralHelper(string word, char split) { if (string.IsNullOrEmpty(word)) return string.Empty; var parts = word.Split(split); parts[parts.Length - 1] = PluralisationService.Pluralize(parts[parts.Length - 1]); // Pluralise just the last word return string.Join(split.ToString(), parts); } /// /// Makes the singular. /// /// The word. /// public static string MakeSingular(string word) { try { if (string.IsNullOrEmpty(word)) return string.Empty; if (PluralisationService == null) return word; if (word.Contains('_')) return MakeSingularHelper(word, '_'); if (word.Contains(' ')) return MakeSingularHelper(word, ' '); if (word.Contains('-')) return MakeSingularHelper(word, '-'); if (IgnoreWordsThatEndWith != null && IgnoreWordsThatEndWith.Any(endsWith => word.EndsWith(endsWith, StringComparison.InvariantCulture))) return word; return PluralisationService.Singularize(word); } catch (Exception) { return word; } } private static string MakeSingularHelper(string word, char split) { if (string.IsNullOrEmpty(word)) return string.Empty; var parts = word.Split(split); parts[parts.Length - 1] = PluralisationService.Singularize(parts[parts.Length - 1]); // Pluralise just the last word return string.Join(split.ToString(), parts); } /// /// Converts the string to title case. /// /// The word. /// public static string ToTitleCase(string word) { if (string.IsNullOrEmpty(word)) return string.Empty; var s = Regex.Replace(ToHumanCase(AddUnderscores(word)), @"\b([a-z])", match => match.Captures[0].Value.ToUpperInvariant()); var digit = false; var sb = new StringBuilder(word.Length + 1); foreach (var c in s) { if (char.IsDigit(c)) { digit = true; sb.Append(c); } else { if (digit && char.IsLower(c)) sb.Append(char.ToUpperInvariant(c)); else sb.Append(c); digit = false; } } return sb.ToString(); } /// /// Converts the string to human case. /// /// The lowercase and underscored word. /// public static string ToHumanCase(string lowercaseAndUnderscoredWord) { if (string.IsNullOrEmpty(lowercaseAndUnderscoredWord)) return string.Empty; return MakeInitialCaps(Regex.Replace(lowercaseAndUnderscoredWord, @"_", " ")); } /// /// Adds the underscores. /// /// The pascal cased word. /// public static string AddUnderscores(string pascalCasedWord) { if (string.IsNullOrEmpty(pascalCasedWord)) return string.Empty; return Regex.Replace( Regex.Replace( Regex.Replace(pascalCasedWord, @"([A-Z]+)([A-Z][a-z])", "$1_$2"), @"([a-z\d])([A-Z])","$1_$2"), @"[-\s]", "_") .ToLowerInvariant(); } /// /// Makes the initial caps. /// /// The word. /// public static string MakeInitialCaps(string word) { if (string.IsNullOrEmpty(word)) return string.Empty; return string.Concat(word.Substring(0, 1).ToUpperInvariant(), word.Substring(1).ToLowerInvariant()); } /// /// Makes the initial character lowercase. /// /// The word. /// public static string MakeInitialLower(string word) { if (string.IsNullOrEmpty(word)) return string.Empty; return string.Concat(word.Substring(0, 1).ToLowerInvariant(), word.Substring(1)); } public static string MakeLowerIfAllCaps(string word) { if (string.IsNullOrEmpty(word)) return string.Empty; return IsAllCaps(word) ? word.ToLowerInvariant() : word; } public static bool IsAllCaps(string word) { if (string.IsNullOrEmpty(word)) return false; return word.All(char.IsUpper); } } public static class DatabaseToPropertyTypeFactory { public static IDatabaseToPropertyType Create() { var factory = CreateDatabaseLanguageFactory(); return factory.Create(); } private static IDatabaseLanguageFactory CreateDatabaseLanguageFactory() { switch (Settings.DatabaseType) { case DatabaseType.SqlServer: case DatabaseType.SqlCe: return new SqlServerLanguageFactory(); case DatabaseType.SQLite: return new SQLiteLanguageFactory(); case DatabaseType.Plugin: return new PluginLanguageFactory(); case DatabaseType.MySql: return new MySqlLanguageFactory(); case DatabaseType.PostgreSQL: return new PostgresLanguageFactory(); case DatabaseType.Oracle: return new OracleLanguageFactory(); default: throw new ArgumentOutOfRangeException(); } } } public enum GenerationLanguage { CSharp, Javascript } public interface IDatabaseToPropertyType { Dictionary GetMapping(); // [Database type] = Language type /// /// A list of the database types that are spatial. /// List SpatialTypes(); /// /// A list of the database types that contain precision. /// List PrecisionTypes(); /// /// A list of the database types that contain precision and scale. /// List PrecisionAndScaleTypes(); } public interface IDatabaseLanguageFactory { IDatabaseToPropertyType Create(); } public class MySqlLanguageFactory : IDatabaseLanguageFactory { public IDatabaseToPropertyType Create() { switch (Settings.GenerationLanguage) { case GenerationLanguage.CSharp: return new MySqlToCSharp(); case GenerationLanguage.Javascript: // Not yet supported default: return new MySqlToCSharp(); } } } public class OracleLanguageFactory : IDatabaseLanguageFactory { public IDatabaseToPropertyType Create() { switch (Settings.GenerationLanguage) { case GenerationLanguage.CSharp: return new MySqlToCSharp(); case GenerationLanguage.Javascript: // Not yet supported default: return new MySqlToCSharp(); } } } public class PluginLanguageFactory : IDatabaseLanguageFactory { public IDatabaseToPropertyType Create() { return null; // Will ask plugin for it } } public class PostgresLanguageFactory : IDatabaseLanguageFactory { public IDatabaseToPropertyType Create() { switch (Settings.GenerationLanguage) { case GenerationLanguage.CSharp: return new PostgresToCSharp(); case GenerationLanguage.Javascript: // Not yet supported default: return new PostgresToCSharp(); } } } public class SQLiteLanguageFactory : IDatabaseLanguageFactory { public IDatabaseToPropertyType Create() { switch (Settings.GenerationLanguage) { case GenerationLanguage.CSharp: return new SqLiteToCSharp(); case GenerationLanguage.Javascript: // Not yet supported default: throw new ArgumentOutOfRangeException(); } } } public class SqlServerLanguageFactory : IDatabaseLanguageFactory { public IDatabaseToPropertyType Create() { switch (Settings.GenerationLanguage) { case GenerationLanguage.CSharp: return new SqlServerToCSharp(); case GenerationLanguage.Javascript: return new SqlServerToJavascript(); default: throw new ArgumentOutOfRangeException(); } } } public class MySqlToCSharp : IDatabaseToPropertyType { public Dictionary GetMapping() { var geographyType = Settings.TemplateType == TemplateType.Ef6 ? "DbGeography" : "NetTopologySuite.Geometries.Point"; var geometryType = Settings.TemplateType == TemplateType.Ef6 ? "DbGeometry" : "NetTopologySuite.Geometries.Geometry"; // [Database type] = Language type return new Dictionary { { string.Empty, "string" }, // default { "bigint unsigned", "decimal" }, { "bigint", "long" }, { "binary", "byte[]" }, { "bit", "long" }, { "bit(1)", "bool" }, { "blob", "byte[]" }, { "bool", "bool" }, { "boolean", "bool" }, { "char byte", "byte[]" }, { "char", "string" }, { "character varying", "string" }, { "date", "DateTime" }, { "datetime", "DateTime" }, { "datetimeoffset", "DateTimeOffset" }, { "dec", "decimal" }, { "decimal", "decimal" }, { "double unsigned", "decimal" }, { "double", "double" }, { "enum", "string" }, { "fixed", "decimal" }, { "float unsigned", "decimal" }, { "float", "double" }, { "geography", Settings.DisableGeographyTypes ? string.Empty : geographyType }, { "geometry", Settings.DisableGeographyTypes ? string.Empty : geometryType }, { "int unsigned", "long" }, { "int", "int" }, { "integer unsigned", "long" }, { "integer", "int" }, { "longblob", "byte[]" }, { "longtext", "string" }, { "mediumblob", "byte[]" }, { "mediumint", "int" }, { "mediumtext", "string" }, { "national char", "string" }, { "national varchar", "string" }, { "nchar", "string" }, { "numeric", "decimal" }, { "nvarchar", "string" }, { "real", "double" }, { "serial", "decimal" }, { "set", "string" }, { "smallint unsigned", "int" }, { "smallint", "short" }, { "text", "string" }, { "time", "TimeSpan" }, { "timestamp", "DateTime" }, { "tinyblob", "byte[]" }, { "tinyint unsigned", "byte" }, { "tinyint", "SByte" }, { "tinytext", "string" }, { "varbinary", "byte[]" }, { "varchar", "string" }, { "year", "short" } }; } public List SpatialTypes() { return new List { "geography", "geometry", "point", "linestring", "polygon", "multipoint", "multilinestring", "multipolygon", "geometrycollection" }; } public List PrecisionTypes() { return new List { "float", "datetime", "time", "timestamp", "year" }; } public List PrecisionAndScaleTypes() { return new List { "decimal", "numeric" }; } } public class OracleToCSharp : IDatabaseToPropertyType { // [Database type] = Language type public Dictionary GetMapping() { return new Dictionary { { string.Empty, "string" }, // default { "binary_double", "decimal" }, { "binary_float", "double" }, { "binary_integer", "long" }, { "blob", "byte[]" }, { "char", "string" }, { "clob", "string" }, { "date", "DateTime" }, { "float", "double" }, { "interval day to second", "decimal" }, { "interval year to month", "decimal" }, { "long raw", "byte[]" }, { "long", "long" }, { "nchar", "string" }, { "nclob", "string" }, { "number", "decimal" }, { "nvarchar2", "string" }, { "pls_integer", "long" }, { "raw", "byte[]" }, { "real", "float" }, { "rowid", "string" }, { "timestamp with local time zone", "DateTime" }, { "timestamp with time zone", "DateTime" }, { "timestamp", "DateTime" }, { "urowid", "string" }, { "varchar2", "string" }, { "xmltype", "string" } }; } public List SpatialTypes() { return new List { "sdo_geometry" }; } public List PrecisionTypes() { return new List { "float", "timestamp", "timestamp with time zone", "timestamp with local time zone" }; } public List PrecisionAndScaleTypes() { return new List { "number" }; } } public class PostgresToCSharp : IDatabaseToPropertyType { // [Database type] = Language type public Dictionary GetMapping() { return new Dictionary { { string.Empty, "string" }, // default { "bigint", "long" }, { "bigserial", "long" }, { "bit varying", "BitArray" }, { "bit", "BitArray" }, { "bool", "bool" }, { "boolean", "bool" }, { "box", "NpgsqlBox" }, { "bytea", "byte[]" }, { "char", "char" }, { "character varying", "string" }, { "character", "string" }, { "cid", "uint" }, { "cidr", "NpgsqlInet" }, { "circle", "NpgsqlCircle" }, { "citext", "string" }, { "date", "DateTime" }, { "decimal", "decimal" }, { "double precision", "double" }, { "float4", "float" }, { "float8", "double" }, { "geometry", Settings.DisableGeographyTypes ? string.Empty : "PostgisGeometry" }, { "hstore", "Dictionary" }, { "inet", "NpgsqlInet" }, { "int", "int" }, { "int2", "short" }, { "int4", "int" }, { "int8", "long" }, { "integer", "int" }, { "interval", "TimeSpan" }, { "json", "string" }, { "jsonb", "string" }, { "line", "NpgsqlLine" }, { "lseg", "NpgsqlLSeg" }, { "macaddr", "PhysicalAddress" }, { "money", "decimal" }, { "name", "string" }, { "numeric", "decimal" }, { "oid", "uint" }, { "oidvector", "uint[]" }, { "path", "NpgsqlPath" }, { "point", "NpgsqlPoint" }, { "polygon", "NpgsqlPolygon" }, { "real", "float" }, { "record", "object[]" }, { "serial", "int" }, { "serial4", "int" }, { "serial8", "long" }, { "smallint", "short" }, { "text", "string" }, { "time", "TimeSpan" }, { "time with time zone", "DateTimeOffset" }, { "time without time zone", "TimeSpan" }, { "timetz", "TimeSpan" }, { "timestamp", "DateTime" }, { "timestamp with time zone", "DateTime" }, { "timestamp without time zone", "DateTime" }, { "timestamptz", "DateTime" }, { "tsquery", "NpgsqlTsQuery" }, { "tsvector", "NpgsqlTsVector" }, { "uuid", "Guid" }, { "varbit", "BitArray" }, { "xid", "uint" }, { "xml", "string" } //{ "composite types", "T" }, //{ "range subtypes", "NpgsqlRange" }, //{ "enum types", "TEnum" }, //{ "array types", "Array (of element type)" }, }; } public List SpatialTypes() { return new List { "geometry", "point", "line", "lseg", "box", "path", "polygon", "circle" }; } public List PrecisionTypes() { return new List { "float" }; } public List PrecisionAndScaleTypes() { return new List { "decimal", "numeric" }; } } public class SqLiteToCSharp : IDatabaseToPropertyType { // [Database type] = Language type public Dictionary GetMapping() { return new Dictionary { { string.Empty, "string" }, // default { "bigint", "long" }, { "blob", "byte[]" }, { "boolean", "bool" }, { "character", "string" }, { "clob", "string" }, { "date", "DateTime" }, { "datetime", "DateTime" }, { "decimal", "double" }, { "double", "double" }, { "double precision", "double" }, { "float", "double" }, { "int", "long" }, { "int2", "long" }, { "int8", "long" }, { "integer", "long" }, { "mediumint", "long" }, { "native character", "string" }, { "nchar", "string" }, { "numeric", "decimal" }, { "nvarchar", "string" }, { "real", "double" }, { "smallint", "int" }, { "text", "string" }, { "unsigned big int", "long" }, { "varchar", "string" }, { "varying character", "string" } }; } public List SpatialTypes() { return new List(); } public List PrecisionTypes() { return new List(); } public List PrecisionAndScaleTypes() { return new List(); } } public class SqlServerToCSharp : IDatabaseToPropertyType { // [Database type] = Language type public Dictionary GetMapping() { var geographyType = Settings.TemplateType == TemplateType.Ef6 ? "DbGeography" : "NetTopologySuite.Geometries.Point"; var geometryType = Settings.TemplateType == TemplateType.Ef6 ? "DbGeometry" : "NetTopologySuite.Geometries.Geometry"; return new Dictionary { { string.Empty, "string" }, // default { "bigint", "long" }, { "binary", "byte[]" }, { "bit", "bool" }, { "date", "DateTime" }, { "datetime", "DateTime" }, { "datetime2", "DateTime" }, { "datetimeoffset", "DateTimeOffset" }, { "decimal", "decimal" }, { "float", "double" }, { "geography", Settings.DisableGeographyTypes ? string.Empty : geographyType }, { "geometry", Settings.DisableGeographyTypes ? string.Empty : geometryType }, { "hierarchyid", "Hierarchy.HierarchyId" }, { "image", "byte[]" }, { "int", "int" }, { "money", "decimal" }, { "numeric", "decimal" }, { "real", "float" }, { "smalldatetime", "DateTime" }, { "smallint", "short" }, { "smallmoney", "decimal" }, { "table type", "DataTable" }, { "time", "TimeSpan" }, { "timestamp", "byte[]" }, { "tinyint", "byte" }, { "uniqueidentifier", "Guid" }, { "varbinary", "byte[]" }, { "varbinary(max)", "byte[]" } }; } public List SpatialTypes() { return new List { "geography", "geometry" }; } public List PrecisionTypes() { return new List { "float", "datetime2", "datetimeoffset" }; } public List PrecisionAndScaleTypes() { return new List { "decimal", "numeric" }; } } public class SqlServerToJavascript : IDatabaseToPropertyType { // [Database type] = Language type public Dictionary GetMapping() { return new Dictionary { { string.Empty, "string" }, // default { "bigint", "Number" }, { "binary", "string" }, { "bit", "boolean" }, { "date", "string" }, { "datetime", "string" }, { "datetime2", "string" }, { "datetimeoffset", "string" }, { "decimal", "Number" }, { "float", "Number" }, { "geography", Settings.DisableGeographyTypes ? string.Empty : "string" }, { "geometry", Settings.DisableGeographyTypes ? string.Empty : "string" }, { "hierarchyid", "string" }, { "image", "string" }, { "int", "Number" }, { "money", "Number" }, { "numeric", "Number" }, { "real", "Number" }, { "smalldatetime", "string" }, { "smallint", "Number" }, { "smallmoney", "Number" }, { "table type", string.Empty }, { "time", "string" }, { "timestamp", "string" }, { "tinyint", "Number" }, { "uniqueidentifier", "string" }, { "varbinary", "string" }, { "varbinary(max)", "string" } }; } public List SpatialTypes() { return new List { "geography", "geometry" }; } public List PrecisionTypes() { return new List { "float", "datetime2", "datetimeoffset" }; } public List PrecisionAndScaleTypes() { return new List { "decimal", "numeric" }; } } public class DigitalSignaturePublic { private readonly RSAParameters _publicKey; public DigitalSignaturePublic() { _publicKey = new RSAParameters { Exponent = Hex.HexToByteArray("010001"), Modulus = Hex.HexToByteArray("B9C08035CECAA5CE4442D2A44B62EAEC0FF337972E6DD7A2135FA00863607C0E6C3B7B25520A562F180C1479E832945F7F82721DE2E1DA01D572F734B92CA1A8EB5FC419FA6B34A2E71DCB0B25818D2ACA5AD8A41647C9814315887324562B422C835DA270D8843F8E44C02BEE4EFCC524F40807148EDCB5D43362F9F05077EF816177BD0C680A6B8A7005251C77BA8F43C47881967341FC7D3AAD9055CADA320E3AB5890E64FCD68B1B0E2E3661EABBB5FC087FBC2E495FC90769F92DE0BB9CFBFC15E36927B52DDD43A3BA7B47C713BCAA532CBD5DEAE60D1D1E6F6D31A6E871452528F0EB96803EC725BB67B4CA123840EAF04D0F5E74EAFE3AF7F9970021") }; } public bool VerifySignature(string text, byte[] signature) { using (var rsa = new RSACryptoServiceProvider(2048)) { rsa.ImportParameters(_publicKey); var rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa); rsaDeformatter.SetHashAlgorithm("SHA256"); return rsaDeformatter.VerifySignature(HashText(text), signature); } } private byte[] HashText(string text) { var provider = new SHA256CryptoServiceProvider(); var hash = provider.ComputeHash(Encoding.Unicode.GetBytes(text)); return hash; } } public static class Hex { /// /// Converts HEX string to byte array. /// Opposite of ByteArrayToHex. /// public static byte[] HexToByteArray(string hexString) { if (hexString == null) return null; if ((hexString.Length % 2) != 0) throw new ApplicationException("Hex string must be multiple of 2 in length"); var byteCount = hexString.Length / 2; var byteValues = new byte[byteCount]; for (var i = 0; i < byteCount; i++) { byteValues[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16); } return byteValues; } /// /// Convert bytes to 2 hex characters per byte, "-" separators are removed. /// Opposite of HexToByteArray /// public static string ByteArrayToHex(byte[] data) { if (data == null) return string.Empty; return BitConverter.ToString(data).Replace("-", ""); } } public class Licence { public string RegisteredTo { get; private set; } public string Company { get; private set; } public LicenceType LicenceType { get; private set; } public string NumLicences { get; private set; } public DateTime ValidUntil { get; private set; } public Licence(string registeredTo, string company, LicenceType licenceType, string numLicences, DateTime validUntil) { RegisteredTo = registeredTo; Company = company; LicenceType = licenceType; NumLicences = numLicences; ValidUntil = validUntil; } public string GetLicenceType() { return GetLicenceType(LicenceType); } public static string GetLicenceType(LicenceType licenceType) { switch (licenceType) { case LicenceType.Academic: return "Academic license - for non-commercial use only"; case LicenceType.Commercial: return "Commercial"; case LicenceType.Trial: return "Trial - for non-commercial trial use only"; default: throw new ArgumentOutOfRangeException(); } } public static LicenceType ParseLicenceType(string licenceType) { licenceType = licenceType.Substring(0, 5); foreach (var type in Enum.GetValues(typeof(LicenceType)).Cast()) { if (GetLicenceType(type).Substring(0, 5) == licenceType) return type; } throw new ArgumentOutOfRangeException(); } public override string ToString() { return string.Format("{0}|{1}|{2}|{3}|{4}", RegisteredTo.ToUpperInvariant().Trim(), Company.ToUpperInvariant().Trim(), GetLicenceType(), NumLicences.ToUpperInvariant().Trim(), ValidUntil.ToString(LicenceConstants.ExpiryFormat, CultureInfo.InvariantCulture).ToUpperInvariant()); } } public static class LicenceConstants { public static string ExpiryFormat = "dd MMM yyyy"; public static string RegisteredTo = "Registered to: "; public static string Company = "Company : "; public static string LicenceType = "Licence Type : "; public static string NumLicences = "Licences : "; public static string ValidUntil = "Valid until : "; public static string Signature = "Signature:"; } public enum LicenceType { Academic, Commercial, Trial } public class LicenceValidator { private readonly DigitalSignaturePublic _ds; public Licence Licence; public bool Expired; public LicenceValidator() { _ds = new DigitalSignaturePublic(); } public bool Validate(string licenceInput) { try { var array = licenceInput.Replace("\n", "\r").Replace("\r\r", "\r").Trim().Split('\r'); var expiryText = ParseString(array, LicenceConstants.ValidUntil); var parsedExpiry = DateTime.ParseExact(expiryText, LicenceConstants.ExpiryFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal); var expiryEndOfDay = new DateTime(parsedExpiry.Year, parsedExpiry.Month, parsedExpiry.Day, 23, 59, 59, DateTimeKind.Local); Expired = expiryEndOfDay < DateTime.Now; if (Expired) return false; Licence = new Licence( ParseString(array, LicenceConstants.RegisteredTo), ParseString(array, LicenceConstants.Company), Licence.ParseLicenceType(ParseString(array, LicenceConstants.LicenceType)), ParseString(array, LicenceConstants.NumLicences), expiryEndOfDay); var foundSignature = false; var sigUpperCase = LicenceConstants.Signature; var signature = new StringBuilder(1024); foreach (var line in array) { if (foundSignature) signature.Append(line); else if (line.StartsWith(sigUpperCase)) foundSignature = true; } return _ds.VerifySignature(Licence.ToString(), Hex.HexToByteArray(signature.ToString().Trim())); } catch { return false; } } private string ParseString(string[] array, string find) { foreach (var line in array) { if (line.StartsWith(find)) return line.Substring(line.IndexOf(':') + 2).Trim(); } throw new ArgumentOutOfRangeException(); } } /// /// Associates parameters to their argument values. /// internal sealed class ArgumentCollection { private readonly Dictionary _argumentLookup; /// /// Initializes a new instance of an ArgumentCollection. /// public ArgumentCollection() { _argumentLookup = new Dictionary(); } /// /// Associates the given parameter to the key placeholder. /// /// The parameter to associate the key with. /// The argument. /// If the key is null, the default value of the parameter will be used. public void AddArgument(TagParameter parameter, IArgument argument) { _argumentLookup.Add(parameter, argument); } /// /// Gets the key that will be used to find the substitute value. /// /// The name of the parameter. public string GetKey(TagParameter parameter) { IArgument argument; if (_argumentLookup.TryGetValue(parameter, out argument) && argument != null) { return argument.GetKey(); } else { return null; } } /// /// Substitutes the key placeholders with their respective values. /// /// The key/value pairs in the current lexical scope. /// The key/value pairs in current context. /// A dictionary associating the parameter name to the associated value. public Dictionary GetArguments(Scope keyScope, Scope contextScope) { Dictionary arguments = new Dictionary(); foreach (KeyValuePair pair in _argumentLookup) { object value; if (pair.Value == null) { value = pair.Key.DefaultValue; } else { value = pair.Value.GetValue(keyScope, contextScope); } arguments.Add(pair.Key.Name, value); } return arguments; } public Dictionary GetArgumentKeyNames() { return _argumentLookup.ToDictionary(p => p.Key.Name, p => (object)GetKey(p.Key)); } } /// /// Builds text by combining the output of other generators. /// internal sealed class CompoundMustacheGenerator : IMustacheGenerator { private readonly TagDefinition _definition; private readonly ArgumentCollection _arguments; private readonly List _primaryGenerators; private IMustacheGenerator _subMustacheGenerator; /// /// Initializes a new instance of a CompoundGenerator. /// /// The tag that the text is being generated for. /// The arguments that were passed to the tag. public CompoundMustacheGenerator(TagDefinition definition, ArgumentCollection arguments) { _definition = definition; _arguments = arguments; _primaryGenerators = new List(); } /// /// Adds the given generator. /// /// The generator to add. public void AddGenerator(IMustacheGenerator mustacheGenerator) { addGenerator(mustacheGenerator, false); } /// /// Adds the given generator, determining whether the generator should /// be part of the primary generators or added as an secondary generator. /// /// The tag that the generator is generating text for. /// The generator to add. public void AddGenerator(TagDefinition definition, IMustacheGenerator mustacheGenerator) { bool isSubGenerator = _definition.ShouldCreateSecondaryGroup(definition); addGenerator(mustacheGenerator, isSubGenerator); } private void addGenerator(IMustacheGenerator mustacheGenerator, bool isSubGenerator) { if (isSubGenerator) { _subMustacheGenerator = mustacheGenerator; } else { _primaryGenerators.Add(mustacheGenerator); } } void IMustacheGenerator.GetText(TextWriter writer, Scope keyScope, Scope contextScope, Action postProcessor) { Dictionary arguments = _arguments.GetArguments(keyScope, contextScope); IEnumerable contexts = _definition.GetChildContext(writer, keyScope, arguments, contextScope); List generators; if (_definition.ShouldGeneratePrimaryGroup(arguments)) { generators = _primaryGenerators; } else { generators = new List(); if (_subMustacheGenerator != null) { generators.Add(_subMustacheGenerator); } } foreach (NestedContext context in contexts) { foreach (IMustacheGenerator generator in generators) { generator.GetText(context.Writer ?? writer, context.KeyScope ?? keyScope, context.ContextScope, postProcessor); } if (context.WriterNeedsConsidated) { writer.Write(_definition.ConsolidateWriter(context.Writer ?? writer, arguments)); } } } } /// /// Defines a tag that conditionally prints its content. /// internal abstract class ConditionTagDefinition : ContentTagDefinition { private const string ConditionParameter = "condition"; private static readonly TagParameter[] InnerParameters = { new TagParameter(ConditionParameter) { IsRequired = true } }; private static readonly TagParameter[] InnerChildContextParameters = { }; private static readonly string[] InnerChildTags = { "elif", "else" }; /// /// Initializes a new instance of a ConditionTagDefinition. /// /// The name of the tag. protected ConditionTagDefinition(string tagName) : base(tagName, true) { } /// /// Gets the parameters that can be passed to the tag. /// /// The parameters. protected override IEnumerable GetParameters() => InnerParameters; /// /// Gets the tags that come into scope within the context of the current tag. /// /// The child tag definitions. protected override IEnumerable GetChildTags() => InnerChildTags; /// /// Gets whether the given tag's generator should be used for a secondary (or substitute) text block. /// /// The tag to inspect. /// True if the tag's generator should be used as a secondary generator. public override bool ShouldCreateSecondaryGroup(TagDefinition definition) { return InnerChildTags.Contains(definition.Name); } /// /// Gets whether the primary generator group should be used to render the tag. /// /// The arguments passed to the tag. /// /// True if the primary generator group should be used to render the tag; /// otherwise, false to use the secondary group. /// public override bool ShouldGeneratePrimaryGroup(Dictionary arguments) { object condition = arguments[ConditionParameter]; return isConditionSatisfied(condition); } private bool isConditionSatisfied(object condition) { if (condition == null || condition == DBNull.Value) { return false; } IEnumerable enumerable = condition as IEnumerable; if (enumerable != null) { return enumerable.Cast().Any(); } if (condition is char) { return (char)condition != '\0'; } try { decimal number = (decimal)Convert.ChangeType(condition, typeof(decimal)); return number != 0.0m; } catch { return true; } } /// /// Gets the parameters that are used to create a new child context. /// /// The parameters that are used to create a new child context. public override IEnumerable GetChildContextParameters() => InnerChildContextParameters; } internal static class Constants { public static readonly string[] EmptyTags = { }; public static readonly TagParameter[] EmptyTagParameters = { }; } /// /// Defines a tag that can contain inner text. /// public abstract class ContentTagDefinition : TagDefinition { /// /// Initializes a new instance of a ContentTagDefinition. /// /// The name of the tag being defined. protected ContentTagDefinition(string tagName) : base(tagName) { } /// /// Initializes a new instance of a ContentTagDefinition. /// /// The name of the tag being defined. /// Specifies whether the tag is a built-in tag. internal ContentTagDefinition(string tagName, bool isBuiltin) : base(tagName, isBuiltin) { } /// /// Gets or sets whether the tag can have content. /// /// True if the tag can have a body; otherwise, false. protected override bool GetHasContent() { return true; } } /// /// Represents a context within a template. /// public sealed class Context { /// /// Initializes a new instance of a Context. /// /// The name of the tag that created the context. /// The context parameters. internal Context(string tagName, params ContextParameter[] parameters) { TagName = tagName; Parameters = parameters; } /// /// Gets the tag that created the context. /// public string TagName { get; private set; } /// /// Gets the argument used to create the context. /// public ContextParameter[] Parameters { get; private set; } } /// /// Holds information describing a parameter that creates a new context. /// public sealed class ContextParameter { /// /// Initializes a new instance of a ContextParameter. /// /// The parameter that is used to create a new context. /// The key whose corresponding value will be used to create the context. internal ContextParameter(string parameter, string argument) { Parameter = parameter; Argument = argument; } /// /// Gets the parameter that is used to create a new context. /// public string Parameter { get; private set; } /// /// Gets the key whose corresponding value will be used to create the context. /// public string Argument { get; private set; } } /// /// Defines a tag that can iterate over a collection of items and render /// the content using each item as the context. /// internal sealed class EachTagDefinition : ContentTagDefinition { private const string EachTag = "each"; private const string CollectionParameter = "collection"; private static readonly TagParameter[] InnerParameters = { new TagParameter(CollectionParameter) { IsRequired = true } }; private static readonly string[] InnerTags = { "index" }; /// /// Initializes a new instance of an EachTagDefinition. /// public EachTagDefinition() : base(EachTag, true) { } /// /// Gets whether the tag only exists within the scope of its parent. /// protected override bool GetIsContextSensitive() { return false; } /// /// Gets the parameters that can be passed to the tag. /// /// The parameters. protected override IEnumerable GetParameters() => InnerParameters; /// /// Gets the context to use when building the inner text of the tag. /// /// The text writer passed /// The current scope. /// The arguments passed to the tag. /// The scope context. /// The scope to use when building the inner text of the tag. public override IEnumerable GetChildContext( TextWriter writer, Scope keyScope, Dictionary arguments, Scope contextScope) { object value = arguments[CollectionParameter]; IEnumerable enumerable = value as IEnumerable; if (enumerable == null) { yield break; } int index = 0; foreach (object item in enumerable) { NestedContext childContext = new NestedContext() { KeyScope = keyScope.CreateChildScope(item), Writer = writer, ContextScope = contextScope.CreateChildScope(), }; childContext.ContextScope.Set("index", index); yield return childContext; ++index; } } /// /// Gets the tags that are in scope under this tag. /// /// The name of the tags that are in scope. protected override IEnumerable GetChildTags() => InnerTags; /// /// Gets the parameters that are used to create a new child context. /// /// The parameters that are used to create a new child context. public override IEnumerable GetChildContextParameters() => InnerParameters; } /// /// Defines a tag that conditionally renders its content if preceding if and elif tags fail. /// internal sealed class ElifTagDefinition : ConditionTagDefinition { private const string ElifTag = "elif"; private static readonly string[] InnerClosingTags = { "if" }; /// /// Initializes a new instance of an ElifTagDefinition. /// public ElifTagDefinition() : base(ElifTag) { } /// /// Gets whether the tag only exists within the scope of its parent. /// protected override bool GetIsContextSensitive() { return true; } /// /// Gets the tags that indicate the end of the current tags context. /// protected override IEnumerable GetClosingTags() => InnerClosingTags; } /// /// Defines a tag that renders its content if all preceding if and elif tags. /// internal sealed class ElseTagDefinition : ContentTagDefinition { private const string ElseTag = "else"; private static readonly string[] InnerClosingTags = { "if" }; /// /// Initializes a new instance of a ElseTagDefinition. /// public ElseTagDefinition() : base(ElseTag, true) { } /// /// Gets whether the tag only exists within the scope of its parent. /// protected override bool GetIsContextSensitive() { return true; } /// /// Gets the tags that indicate the end of the current tag's content. /// protected override IEnumerable GetClosingTags() => InnerClosingTags; /// /// Gets the parameters that are used to create a new child context. /// /// The parameters that are used to create a new child context. public override IEnumerable GetChildContextParameters() => Constants.EmptyTagParameters; } /// /// Parses a format string and returns a text generator. /// public sealed class FormatCompiler { private readonly Dictionary _tagLookup; private readonly Dictionary _regexLookup; private readonly Dictionary _partialLookup; private readonly MasterTagDefinition _masterDefinition; /// /// Initializes a new instance of a FormatCompiler. /// public FormatCompiler() { _tagLookup = new Dictionary(); _regexLookup = new Dictionary(); _partialLookup = new Dictionary(); _masterDefinition = new MasterTagDefinition(); IfTagDefinition ifDefinition = new IfTagDefinition(); _tagLookup.Add(ifDefinition.Name, ifDefinition); ElifTagDefinition elifDefinition = new ElifTagDefinition(); _tagLookup.Add(elifDefinition.Name, elifDefinition); ElseTagDefinition elseDefinition = new ElseTagDefinition(); _tagLookup.Add(elseDefinition.Name, elseDefinition); EachTagDefinition eachDefinition = new EachTagDefinition(); _tagLookup.Add(eachDefinition.Name, eachDefinition); IndexTagDefinition indexDefinition = new IndexTagDefinition(); _tagLookup.Add(indexDefinition.Name, indexDefinition); WithTagDefinition withDefinition = new WithTagDefinition(); _tagLookup.Add(withDefinition.Name, withDefinition); NewlineTagDefinition newlineDefinition = new NewlineTagDefinition(); _tagLookup.Add(newlineDefinition.Name, newlineDefinition); SetTagDefinition setDefinition = new SetTagDefinition(); _tagLookup.Add(setDefinition.Name, setDefinition); RemoveNewLines = true; } /// /// Occurs when a placeholder is found in the template. /// public event EventHandler PlaceholderFound; /// /// Occurs when a variable is found in the template. /// public event EventHandler VariableFound; /// /// Gets or sets whether newlines are removed from the template (default: false). /// public bool RemoveNewLines { get; set; } /// /// Gets or sets whether the compiler searches for tags using triple curly braces. /// public bool AreExtensionTagsAllowed { get; set; } /// /// Registers the given tag definition with the parser. /// /// The tag definition to register. /// Specifies whether the tag is immediately in scope. public void RegisterTag(TagDefinition definition, bool isTopLevel) { if (definition == null) { throw new ArgumentNullException("definition"); } if (_tagLookup.ContainsKey(definition.Name)) { string message = String.Format(Resources.DuplicateTagDefinition, definition.Name); throw new ArgumentException(message, "definition"); } _tagLookup.Add(definition.Name, definition); } public void RegisterPartial(string name, string template) { _partialLookup.Add(name, template); } /// /// Builds a text generator based on the given format. /// /// The format to parse. /// The text generator. public MustacheGenerator Compile(string format) { if (format == null) { throw new ArgumentNullException("format"); } CompoundMustacheGenerator generator = new CompoundMustacheGenerator(_masterDefinition, new ArgumentCollection()); Dictionary partials = new Dictionary(_partialLookup); List context = new List() { new Context(_masterDefinition.Name, new ContextParameter[0]) }; int formatIndex = buildCompoundGenerator(_masterDefinition, partials, context, generator, format, 0); string trailing = format.Substring(formatIndex); if (!trailing.Equals(string.Empty)) generator.AddGenerator(new StaticMustacheGenerator(trailing, RemoveNewLines)); return new MustacheGenerator(generator); } private Match findNextTag(TagDefinition definition, string format, int formatIndex) { Regex regex = prepareRegex(definition); return regex.Match(format, formatIndex); } private Regex prepareRegex(TagDefinition definition) { Regex regex; if (!_regexLookup.TryGetValue(definition.Name, out regex)) { List matches = new List(); matches.Add(getKeyRegex()); matches.Add(getCommentTagRegex()); matches.Add(getPartialDefinitionRegex()); matches.Add(getPartialCallRegex()); foreach (string closingTag in definition.ClosingTags) { matches.Add(getClosingTagRegex(closingTag)); } foreach (TagDefinition globalDefinition in _tagLookup.Values) { if (!globalDefinition.IsContextSensitive) { matches.Add(getTagRegex(globalDefinition)); } } foreach (string childTag in definition.ChildTags) { TagDefinition childDefinition = _tagLookup[childTag]; matches.Add(getTagRegex(childDefinition)); } matches.Add(getUnknownTagRegex()); string combined = String.Join("|", matches); string match = "{{(?" + combined + ")}}"; if (AreExtensionTagsAllowed) { string tripleMatch = "{{{(?" + combined + ")}}}"; match = "(?:" + match + ")|(?:" + tripleMatch + ")"; } regex = new Regex(match); _regexLookup.Add(definition.Name, regex); } return regex; } private static string getClosingTagRegex(string tagName) { StringBuilder regexBuilder = new StringBuilder(); regexBuilder.Append(@"(?(/(?"); regexBuilder.Append(tagName); regexBuilder.Append(@")\s*?))"); return regexBuilder.ToString(); } private static string getCommentTagRegex() { return @"(?#!.*?)"; } private static string getKeyRegex() { return @"((?" + RegexHelper.CompoundKey + @")(,(?(\+|-)?[\d]+))?(:(?.*?))?)"; } private static string getPartialDefinitionRegex() { StringBuilder regexBuilder = new StringBuilder(); regexBuilder.Append(@"(?"); regexBuilder.Append(@"#\*inline\s+?"); regexBuilder.Append(@"""(?[a-zA-Z0-9]+?)"""); regexBuilder.Append(@"}?}}"); regexBuilder.Append(@"(?.*)"); regexBuilder.Append(@"{?{{/inline)"); return regexBuilder.ToString(); } private static string getPartialCallRegex() { StringBuilder regexBuilder = new StringBuilder(); regexBuilder.Append(@"(?"); regexBuilder.Append(@">\s+?(?(?"); regexBuilder.Append(RegexHelper.Key); regexBuilder.Append(@"))(?:\s+?(?(?"); regexBuilder.Append(RegexHelper.CompoundKey); regexBuilder.Append(@")))?\s*?)"); return regexBuilder.ToString(); } private static string getTagRegex(TagDefinition definition) { StringBuilder regexBuilder = new StringBuilder(); regexBuilder.Append(@"(?(#(?"); regexBuilder.Append(definition.Name); regexBuilder.Append(@")"); foreach (TagParameter parameter in definition.Parameters) { regexBuilder.Append(@"(\s+?"); regexBuilder.Append(@"(?("); regexBuilder.Append(RegexHelper.Argument); regexBuilder.Append(@")))"); if (!parameter.IsRequired) { regexBuilder.Append("?"); } } regexBuilder.Append(@"\s*?))"); return regexBuilder.ToString(); } private static string getUnknownTagRegex() { return @"(?(#.*?))"; } private int buildCompoundGenerator( TagDefinition tagDefinition, Dictionary partials, List context, CompoundMustacheGenerator generator, string format, int formatIndex) { while (true) { Match match = findNextTag(tagDefinition, format, formatIndex); if (!match.Success) { if (tagDefinition.ClosingTags.Any()) { string message = String.Format(Resources.MissingClosingTag, tagDefinition.Name); throw new FormatException(message); } break; } string leading = format.Substring(formatIndex, match.Index - formatIndex); if (match.Groups["key"].Success) { if (!leading.Equals(string.Empty)) generator.AddGenerator(new StaticMustacheGenerator(leading, RemoveNewLines)); formatIndex = match.Index + match.Length; bool isExtension = match.Groups["extension"].Success; string key = match.Groups["key"].Value; string alignment = match.Groups["alignment"].Value; string formatting = match.Groups["format"].Value; if (key.StartsWith("@")) { VariableFoundEventArgs args = new VariableFoundEventArgs(key.Substring(1), alignment, formatting, isExtension, context.ToArray()); if (VariableFound != null) { VariableFound(this, args); key = "@" + args.Name; alignment = args.Alignment; formatting = args.Formatting; isExtension = args.IsExtension; } } else { PlaceholderFoundEventArgs args = new PlaceholderFoundEventArgs(key, alignment, formatting, isExtension, context.ToArray()); if (PlaceholderFound != null) { PlaceholderFound(this, args); key = args.Key; alignment = args.Alignment; formatting = args.Formatting; isExtension = args.IsExtension; } } KeyMustacheGenerator keyGenerator = new KeyMustacheGenerator(key, alignment, formatting, isExtension); generator.AddGenerator(keyGenerator); } // if we come across a partial template definition else if (match.Groups["define"].Success) { formatIndex = match.Index + match.Length; // add the template definition to the lookup partials.Add(match.Groups["name"].Value, match.Groups["definition"].Value); } // if we come across a call for a partial template else if (match.Groups["call"].Success) { formatIndex = match.Index + match.Length; // include the substring since the last match if (!leading.Equals(string.Empty)) generator.AddGenerator(new StaticMustacheGenerator(leading, RemoveNewLines)); var partialTag = new PartialCallTagDefinition(); // retrieve the arguments from the regex ArgumentCollection arguments = getArguments(partialTag, match, context); string name = match.Groups["name"].Value; string partialTemplate; if (partials.TryGetValue(name, out partialTemplate)) { bool hasContext = match.Groups["context"].Success; if (hasContext) { // if a special context is to be provided, do it var contextString = match.Groups["context"].Value; var param = new ContextParameter("context", contextString); context.Add(new Context(partialTag.Name, param)); } // include a fully compiled copy of the template CompoundMustacheGenerator partialGenerator = new CompoundMustacheGenerator(partialTag, arguments); int trailingIndex = buildCompoundGenerator(partialTag, partials, context, partialGenerator, partialTemplate, 0); generator.AddGenerator(partialGenerator); // and the part of the template after the last match string trailing = partialTemplate.Substring(trailingIndex); if (!trailing.Equals(string.Empty)) generator.AddGenerator(new StaticMustacheGenerator(trailing, RemoveNewLines)); if (hasContext) { // undo the context change context.RemoveAt(context.Count - 1); } } else { string message = String.Format(Resources.PartialNotDefined, name); throw new FormatException(message); } } else if (match.Groups["open"].Success) { formatIndex = match.Index + match.Length; string tagName = match.Groups["name"].Value; TagDefinition nextDefinition = _tagLookup[tagName]; if (nextDefinition == null) { string message = String.Format(Resources.UnknownTag, tagName); throw new FormatException(message); } if (!leading.Equals(string.Empty)) generator.AddGenerator(new StaticMustacheGenerator(leading, RemoveNewLines)); ArgumentCollection arguments = getArguments(nextDefinition, match, context); if (nextDefinition.HasContent) { CompoundMustacheGenerator compoundGenerator = new CompoundMustacheGenerator(nextDefinition, arguments); IEnumerable contextParameters = nextDefinition.GetChildContextParameters(); bool hasContext = contextParameters.Any(); if (hasContext) { ContextParameter[] parameters = contextParameters.Select(p => new ContextParameter(p.Name, arguments.GetKey(p))).ToArray(); context.Add(new Context(nextDefinition.Name, parameters)); } formatIndex = buildCompoundGenerator(nextDefinition, partials, context, compoundGenerator, format, formatIndex); generator.AddGenerator(nextDefinition, compoundGenerator); if (hasContext) { context.RemoveAt(context.Count - 1); } } else { InlineMustacheGenerator inlineGenerator = new InlineMustacheGenerator(nextDefinition, arguments); generator.AddGenerator(inlineGenerator); } } else if (match.Groups["close"].Success) { if (!leading.Equals(string.Empty)) generator.AddGenerator(new StaticMustacheGenerator(leading, RemoveNewLines)); string tagName = match.Groups["name"].Value; TagDefinition nextDefinition = _tagLookup[tagName]; formatIndex = match.Index; if (tagName == tagDefinition.Name) { formatIndex += match.Length; } break; } else if (match.Groups["comment"].Success) { if (!leading.Equals(string.Empty)) generator.AddGenerator(new StaticMustacheGenerator(leading, RemoveNewLines)); formatIndex = match.Index + match.Length; } else if (match.Groups["unknown"].Success) { string tagName = match.Value; string message = String.Format(Resources.UnknownTag, tagName); throw new FormatException(message); } } return formatIndex; } private ArgumentCollection getArguments(TagDefinition definition, Match match, List context) { // make sure we don't have too many arguments List captures = match.Groups["argument"].Captures.Cast().ToList(); List parameters = definition.Parameters.ToList(); if (captures.Count > parameters.Count) { string message = String.Format(Resources.WrongNumberOfArguments, definition.Name); throw new FormatException(message); } // provide default values for missing arguments if (captures.Count < parameters.Count) { captures.AddRange(Enumerable.Repeat((Capture)null, parameters.Count - captures.Count)); } // pair up the parameters to the given arguments // provide default for parameters with missing arguments // throw an error if a missing argument is for a required parameter Dictionary arguments = new Dictionary(); foreach (var pair in parameters.Zip(captures, (p, c) => new { Capture = c, Parameter = p })) { string value = null; if (pair.Capture != null) { value = pair.Capture.Value; } else if (pair.Parameter.IsRequired) { string message = String.Format(Resources.WrongNumberOfArguments, definition.Name); throw new FormatException(message); } arguments.Add(pair.Parameter, value); } // indicate that a key/variable has been encountered // update the key/variable name ArgumentCollection collection = new ArgumentCollection(); foreach (var pair in arguments) { string placeholder = pair.Value; IArgument argument = null; if (placeholder != null) { if (placeholder.StartsWith("@")) { string variableName = placeholder.Substring(1); VariableFoundEventArgs args = new VariableFoundEventArgs(placeholder.Substring(1), string.Empty, string.Empty, false, context.ToArray()); if (VariableFound != null) { VariableFound(this, args); variableName = args.Name; } argument = new VariableArgument(variableName); } else if (RegexHelper.IsString(placeholder)) { string value = placeholder.Trim('\''); argument = new StringArgument(value); } else if (RegexHelper.IsNumber(placeholder)) { decimal number; if (Decimal.TryParse(placeholder, out number)) { argument = new NumberArgument(number); } } else { string placeholderName = placeholder; PlaceholderFoundEventArgs args = new PlaceholderFoundEventArgs(placeholder, string.Empty, string.Empty, false, context.ToArray()); if (PlaceholderFound != null) { PlaceholderFound(this, args); placeholderName = args.Key; } argument = new PlaceholderArgument(placeholderName); } } collection.AddArgument(pair.Key, argument); } return collection; } } public sealed class HtmlFormatCompiler { private readonly FormatCompiler compiler; public HtmlFormatCompiler() { compiler = new FormatCompiler(); compiler.AreExtensionTagsAllowed = true; compiler.RemoveNewLines = true; } /// /// Occurs when a placeholder is found in the template. /// public event EventHandler PlaceholderFound { add { compiler.PlaceholderFound += value; } remove { compiler.PlaceholderFound -= value; } } /// /// Occurs when a variable is found in the template. /// public event EventHandler VariableFound { add { compiler.VariableFound += value; } remove { compiler.VariableFound -= value; } } /// /// Registers the given tag definition with the parser. /// /// The tag definition to register. /// Specifies whether the tag is immediately in scope. public void RegisterTag(TagDefinition definition, bool isTopLevel) { compiler.RegisterTag(definition, isTopLevel); } /// /// Builds a text generator based on the given format. /// /// The format to parse. /// The text generator. public MustacheGenerator Compile(string format) { MustacheGenerator mustacheGenerator = compiler.Compile(format); mustacheGenerator.TagFormatted += escapeInvalidHtml; return mustacheGenerator; } private static void escapeInvalidHtml(object sender, TagFormattedEventArgs e) { if (e.IsExtension) { // Do not escape text within triple curly braces return; } e.Substitute = SecurityElement.Escape(e.Substitute); } } public interface IArgument { string GetKey(); object GetValue(Scope keyScope, Scope contextScope); } /// /// Defines a tag that renders its content depending on the truthyness /// of its argument, with optional elif and else nested tags. /// internal sealed class IfTagDefinition : ConditionTagDefinition { private const string IfTag = "if"; /// /// Initializes a new instance of a IfTagDefinition. /// public IfTagDefinition() : base(IfTag) { } /// /// Gets whether the tag only exists within the scope of its parent. /// protected override bool GetIsContextSensitive() { return false; } } /// /// Applies the values of an object to the format plan, generating a string. /// internal interface IMustacheGenerator { /// /// Generates the text when applying the format plan. /// /// The text writer to send all text to. /// The current lexical scope of the keys. /// The data associated to the context. /// A function to apply after a substitution is made. /// The generated text. void GetText(TextWriter writer, Scope keyScope, Scope contextScope, Action postProcessor); } /// /// Defines a tag that outputs the current index within an each loop. /// internal sealed class IndexTagDefinition : InlineTagDefinition { /// /// Initializes a new instance of an IndexTagDefinition. /// public IndexTagDefinition() : base("index", true) { } /// /// Gets the text to output. /// /// The writer to write the output to. /// The arguments passed to the tag. /// Extra data passed along with the context. public override void GetText(TextWriter writer, Dictionary arguments, Scope contextScope) { object index; if (contextScope.TryFind("index", out index)) { writer.Write(index); } } } /// /// Generates the text for a tag that is replaced with its generated text. /// internal sealed class InlineMustacheGenerator : IMustacheGenerator { private readonly TagDefinition _definition; private readonly ArgumentCollection _arguments; /// /// Initializes a new instance of an InlineGenerator. /// /// The tag to render the text for. /// The arguments passed to the tag. public InlineMustacheGenerator(TagDefinition definition, ArgumentCollection arguments) { _definition = definition; _arguments = arguments; } void IMustacheGenerator.GetText(TextWriter writer, Scope scope, Scope context, Action postProcessor) { Dictionary arguments; if (_definition.IsSetter) { arguments = _arguments.GetArgumentKeyNames(); } else { arguments = _arguments.GetArguments(scope, context); } _definition.GetText(writer, arguments, context); } } /// /// Defines a tag that cannot contain inner text. /// public abstract class InlineTagDefinition : TagDefinition { /// /// Initializes a new instance of an InlineTagDefinition. /// /// The name of the tag being defined. protected InlineTagDefinition(string tagName) : base(tagName) { } /// /// Initializes a new instance of an InlineTagDefinition. /// /// The name of the tag being defined. /// Specifies whether the tag is a built-in tag. internal InlineTagDefinition(string tagName, bool isBuiltin) : base(tagName, isBuiltin) { } /// /// Gets or sets whether the tag can have content. /// /// True if the tag can have a body; otherwise, false. protected override bool GetHasContent() { return false; } /// /// Gets the parameters that are used to create a child context. /// /// The parameters that are used to create a child context. public override IEnumerable GetChildContextParameters() { return new TagParameter[0]; } } /// /// Holds the information about a key that was found. /// public class KeyFoundEventArgs : EventArgs { /// /// Initializes a new instance of a KeyFoundEventArgs. /// /// The fully-qualified key. /// The object to use as the substitute. /// Specifies whether the key was found within triple curly braces. internal KeyFoundEventArgs(string key, object value, bool isExtension) { Key = key; Substitute = value; } /// /// Gets the fully-qualified key. /// public string Key { get; private set; } /// /// Gets or sets whether the key appeared within triple curly braces. /// public bool IsExtension { get; private set; } /// /// Gets or sets the object to use as the substitute. /// public object Substitute { get; set; } } /// /// Substitutes a key placeholder with the textual representation of the associated object. /// internal sealed class KeyMustacheGenerator : IMustacheGenerator { private readonly string _key; private readonly string _format; private readonly bool _isVariable; private readonly bool _isExtension; /// /// Initializes a new instance of a KeyGenerator. /// /// The key to substitute with its value. /// The alignment specifier. /// The format specifier. /// Specifies whether the key was found within triple curly braces. public KeyMustacheGenerator(string key, string alignment, string formatting, bool isExtension) { if (key.StartsWith("@")) { _key = key.Substring(1); _isVariable = true; } else { _key = key; _isVariable = false; } _format = getFormat(alignment, formatting); _isExtension = isExtension; } private static string getFormat(string alignment, string formatting) { StringBuilder formatBuilder = new StringBuilder(); formatBuilder.Append("{0"); if (!String.IsNullOrWhiteSpace(alignment)) { formatBuilder.Append(","); formatBuilder.Append(alignment.TrimStart('+')); } if (!String.IsNullOrWhiteSpace(formatting)) { formatBuilder.Append(":"); formatBuilder.Append(formatting); } formatBuilder.Append("}"); return formatBuilder.ToString(); } void IMustacheGenerator.GetText(TextWriter writer, Scope scope, Scope context, Action postProcessor) { object value = _isVariable ? context.Find(_key, _isExtension) : scope.Find(_key, _isExtension); string result = String.Format(writer.FormatProvider, _format, value); Substitution substitution = new Substitution() { Key = _key, Substitute = result, IsExtension = _isExtension }; postProcessor(substitution); writer.Write(substitution.Substitute); } } /// /// Holds the information needed to handle a missing key. /// public class KeyNotFoundEventArgs : EventArgs { /// /// Initializes a new instance of a KeyNotFoundEventArgs. /// /// The fully-qualified key. /// The part of the key that could not be found. /// Specifies whether the key appears within triple curly braces. internal KeyNotFoundEventArgs(string key, string missingMember, bool isExtension) { Key = key; MissingMember = missingMember; IsExtension = isExtension; } /// /// Gets the fully-qualified key. /// public string Key { get; private set; } /// /// Gets the part of the key that could not be found. /// public string MissingMember { get; private set; } /// /// Gets whether the key appeared within triple curly braces. /// public bool IsExtension { get; private set; } /// /// Gets or sets whether to use the substitute. /// public bool Handled { get; set; } /// /// Gets or sets the object to use as the substitute. /// public object Substitute { get; set; } } /// /// Defines a pseudo tag that wraps the entire content of a format string. /// internal sealed class MasterTagDefinition : ContentTagDefinition { /// /// Initializes a new instance of a MasterTagDefinition. /// public MasterTagDefinition() : base(string.Empty, true) { } /// /// Gets whether the tag only exists within the scope of its parent. /// protected override bool GetIsContextSensitive() { return true; } /// /// Gets the name of the tags that indicate that the tag's context is closed. /// /// The tag names. protected override IEnumerable GetClosingTags() => Constants.EmptyTags; /// /// Gets the parameters that are used to create a new child context. /// /// The parameters that are used to create a new child context. public override IEnumerable GetChildContextParameters() => Constants.EmptyTagParameters; } /// /// Generates text by substituting an object's values for placeholders. /// public sealed class MustacheGenerator { private readonly IMustacheGenerator _mustacheGenerator; private readonly List> _foundHandlers; private readonly List> _notFoundHandlers; private readonly List> _valueRequestedHandlers; /// /// Initializes a new instance of a Generator. /// /// The text generator to wrap. internal MustacheGenerator(IMustacheGenerator mustacheGenerator) { _mustacheGenerator = mustacheGenerator; _foundHandlers = new List>(); _notFoundHandlers = new List>(); _valueRequestedHandlers = new List>(); } /// /// Occurs when a key/property is found. /// public event EventHandler KeyFound { add { _foundHandlers.Add(value); } remove { _foundHandlers.Remove(value); } } /// /// Occurs when a key/property is not found in the object graph. /// public event EventHandler KeyNotFound { add { _notFoundHandlers.Add(value); } remove { _notFoundHandlers.Remove(value); } } /// /// Occurs when a setter is encountered and requires a value to be provided. /// public event EventHandler ValueRequested { add { _valueRequestedHandlers.Add(value); } remove { _valueRequestedHandlers.Remove(value); } } /// /// Occurs when a tag is replaced by its text. /// public event EventHandler TagFormatted; /// /// Gets the text that is generated for the given object. /// /// The object to generate the text with. /// The text generated for the given object. public string Render(object source) { return render(CultureInfo.CurrentCulture, source); } /// /// Gets the text that is generated for the given object. /// /// The format provider to use. /// The object to generate the text with. /// The text generated for the given object. public string Render(IFormatProvider provider, object source) { if (provider == null) { provider = CultureInfo.CurrentCulture; } return render(provider, source); } private string render(IFormatProvider provider, object source) { Scope keyScope = new Scope(source); Scope contextScope = new Scope(new Dictionary()); foreach (EventHandler handler in _foundHandlers) { keyScope.KeyFound += handler; contextScope.KeyFound += handler; } foreach (EventHandler handler in _notFoundHandlers) { keyScope.KeyNotFound += handler; contextScope.KeyNotFound += handler; } foreach (EventHandler handler in _valueRequestedHandlers) { contextScope.ValueRequested += handler; } StringWriter writer = new StringWriter(provider); _mustacheGenerator.GetText(writer, keyScope, contextScope, postProcess); return writer.ToString(); } private void postProcess(Substitution substitution) { if (TagFormatted == null) { return; } TagFormattedEventArgs args = new TagFormattedEventArgs(substitution.Key, substitution.Substitute, substitution.IsExtension); TagFormatted(this, args); substitution.Substitute = args.Substitute; } } /// /// Holds the objects to use when processing a child context of another tag. /// public sealed class NestedContext { /// /// Initializes a new instance of a NestedContext. /// public NestedContext() { } /// /// Gets or sets the writer to use when generating the child context. /// /// Setting the writer to null will indicate that the tag's writer should be used. public TextWriter Writer { get; set; } /// /// Gets or sets whether the text sent to the returned writer needs to be added /// to the parent tag's writer. This should be false if the parent writer is /// being returned or is being wrapped. /// public bool WriterNeedsConsidated { get; set; } /// /// Gets or sets the key scope to use when generating the child context. /// /// Setting the scope to null will indicate that the current scope should be used. public Scope KeyScope { get; set; } /// /// Gets or sets data associated with the context. /// public Scope ContextScope { get; set; } } /// /// Defines a tag that outputs a newline. /// internal sealed class NewlineTagDefinition : InlineTagDefinition { private const string NewlineTag = "newline"; /// /// Initializes a new instance of an NewlineTagDefinition. /// public NewlineTagDefinition() : base(NewlineTag) { } /// /// Gets the text to output. /// /// The writer to write the output to. /// The arguments passed to the tag. /// Extra data passed along with the context. public override void GetText(TextWriter writer, Dictionary arguments, Scope context) { writer.Write(Environment.NewLine); } } public class NumberArgument : IArgument { private readonly decimal value; public NumberArgument(decimal value) { this.value = value; } public string GetKey() { return null; } public object GetValue(Scope keyScope, Scope contextScope) { return value; } } public class PartialCallTagDefinition : TagDefinition { private const string PartialCallTag = ">"; private const string NameParameter = "name"; private const string ContextParameter = "context"; private static readonly TagParameter[] InnerTags = { new TagParameter(NameParameter) { IsRequired = true }, new TagParameter(ContextParameter) { IsRequired = false } }; private static readonly TagParameter[] InnerContextTags = { new TagParameter(ContextParameter) { IsRequired = false } }; public PartialCallTagDefinition() : base(PartialCallTag, true) { } protected override IEnumerable GetParameters() => InnerTags; public override IEnumerable GetChildContextParameters() => InnerContextTags; protected override bool GetHasContent() => false; public override IEnumerable GetChildContext( TextWriter writer, Scope keyScope, Dictionary arguments, Scope contextScope) { object contextSource = arguments[ContextParameter]; Scope scope; if (contextSource == null) scope = keyScope.CreateChildScope(); else scope = keyScope.CreateChildScope(contextSource); NestedContext context = new NestedContext() { KeyScope = scope, Writer = writer, ContextScope = contextScope.CreateChildScope() }; yield return context; } } public class PlaceholderArgument : IArgument { private readonly string name; public PlaceholderArgument(string name) { this.name = name; } public string GetKey() { return name; } public object GetValue(Scope keyScope, Scope contextScope) { return keyScope.Find(name, false); } } /// /// Holds the information descibing a key that is found in a template. /// public class PlaceholderFoundEventArgs : EventArgs { /// /// Initializes a new instance of a PlaceholderFoundEventArgs. /// /// The key that was found. /// The alignment that will be applied to the substitute value. /// The formatting that will be applied to the substitute value. /// Indicates whether the placeholder was found within triple curly braces. /// The context where the placeholder was found. internal PlaceholderFoundEventArgs(string key, string alignment, string formatting, bool isExtension, Context[] context) { Key = key; Alignment = alignment; Formatting = formatting; Context = context; } /// /// Gets or sets the key that was found. /// public string Key { get; set; } /// /// Gets or sets the alignment that will be applied to the substitute value. /// public string Alignment { get; set; } /// /// Gets or sets the formatting that will be applied to the substitute value. /// public string Formatting { get; set; } /// /// Gets or sets whether the placeholder was found within triple curly braces. /// public bool IsExtension { get; set; } /// /// Gets the context where the placeholder was found. /// public Context[] Context { get; private set; } } /// /// Provides methods for creating instances of PropertyDictionary. /// internal sealed class PropertyDictionary : IDictionary { private static readonly Dictionary>> _cache = new Dictionary>>(); private readonly object _instance; private readonly Dictionary> _typeCache; /// /// Initializes a new instance of a PropertyDictionary. /// /// The instance to wrap in the PropertyDictionary. public PropertyDictionary(object instance) { _instance = instance; if (instance == null) { _typeCache = new Dictionary>(); } else { lock (_cache) { _typeCache = getCacheType(_instance); } } } private static Dictionary> getCacheType(object instance) { Type type = instance.GetType(); Dictionary> typeCache; if (!_cache.TryGetValue(type, out typeCache)) { typeCache = new Dictionary>(); BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy; var properties = getMembers(type, type.GetProperties(flags).Where(p => !p.IsSpecialName)); foreach (PropertyInfo propertyInfo in properties) { typeCache.Add(propertyInfo.Name, i => propertyInfo.GetValue(i, null)); } var fields = getMembers(type, type.GetFields(flags).Where(f => !f.IsSpecialName)); foreach (FieldInfo fieldInfo in fields) { typeCache.Add(fieldInfo.Name, i => fieldInfo.GetValue(i)); } _cache.Add(type, typeCache); } return typeCache; } private static IEnumerable getMembers(Type type, IEnumerable members) where TMember : MemberInfo { var singles = from member in members group member by member.Name into nameGroup where nameGroup.Count() == 1 from property in nameGroup select property; var multiples = from member in members group member by member.Name into nameGroup where nameGroup.Count() > 1 select ( from member in nameGroup orderby getDistance(type, member) select member ).First(); var combined = singles.Concat(multiples); return combined; } private static int getDistance(Type type, MemberInfo memberInfo) { int distance = 0; for (; type != null && type != memberInfo.DeclaringType; type = type.GetTypeInfo().BaseType) { ++distance; } return distance; } /// /// Gets the underlying instance. /// public object Instance { get { return _instance; } } [EditorBrowsable(EditorBrowsableState.Never)] void IDictionary.Add(string key, object value) { throw new NotSupportedException(); } /// /// Determines whether a property with the given name exists. /// /// The name of the property. /// True if the property exists; otherwise, false. public bool ContainsKey(string key) { return _typeCache.ContainsKey(key); } /// /// Gets the name of the properties in the type. /// public ICollection Keys { get { return _typeCache.Keys; } } [EditorBrowsable(EditorBrowsableState.Never)] bool IDictionary.Remove(string key) { throw new NotSupportedException(); } /// /// Tries to get the value for the given property name. /// /// The name of the property to get the value for. /// The variable to store the value of the property or the default value if the property is not found. /// True if a property with the given name is found; otherwise, false. /// The name of the property was null. public bool TryGetValue(string key, out object value) { Func getter; if (!_typeCache.TryGetValue(key, out getter)) { value = null; return false; } value = getter(_instance); return true; } /// /// Gets the values of all of the properties in the object. /// public ICollection Values { get { ICollection> getters = _typeCache.Values; List values = new List(); foreach (Func getter in getters) { object value = getter(_instance); values.Add(value); } return values.AsReadOnly(); } } /// /// Gets or sets the value of the property with the given name. /// /// The name of the property to get or set. /// The value of the property with the given name. /// The property name was null. /// The type does not have a property with the given name. /// The property did not support getting or setting. /// /// The object does not match the target type, or a property is a value type but the value is null. /// public object this[string key] { get { Func getter = _typeCache[key]; return getter(_instance); } [EditorBrowsable(EditorBrowsableState.Never)] set { throw new NotSupportedException(); } } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection>.Add(KeyValuePair item) { throw new NotSupportedException(); } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection>.Clear() { throw new NotSupportedException(); } bool ICollection>.Contains(KeyValuePair item) { Func getter; if (!_typeCache.TryGetValue(item.Key, out getter)) { return false; } object value = getter(_instance); return Equals(item.Value, value); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { List> pairs = new List>(); ICollection>> collection = _typeCache; foreach (KeyValuePair> pair in collection) { Func getter = pair.Value; object value = getter(_instance); pairs.Add(new KeyValuePair(pair.Key, value)); } pairs.CopyTo(array, arrayIndex); } /// /// Gets the number of properties in the type. /// public int Count { get { return _typeCache.Count; } } /// /// Gets or sets whether updates will be ignored. /// bool ICollection>.IsReadOnly { get { return true; } } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection>.Remove(KeyValuePair item) { throw new NotSupportedException(); } /// /// Gets the propety name/value pairs in the object. /// /// public IEnumerator> GetEnumerator() { foreach (KeyValuePair> pair in _typeCache) { Func getter = pair.Value; object value = getter(_instance); yield return new KeyValuePair(pair.Key, value); } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } /// /// Provides utility methods that require regular expressions. /// internal static class RegexHelper { public const string Key = @"[_\w][_\w\d]*"; public const string String = @"'.*?'"; public const string Number = @"[-+]?\d*\.?\d+"; public const string CompoundKey = "@?" + Key + @"(?:\." + Key + ")*"; public const string Argument = @"(?:(?" + CompoundKey + @")|(?" + String + @")|(?" + Number + @"))"; /// /// Determines whether the given name is a legal identifier. /// /// The name to check. /// True if the name is a legal identifier; otherwise, false. public static bool IsValidIdentifier(string name) { if (name == null) { return false; } Regex regex = new Regex("^" + Key + "$"); return regex.IsMatch(name); } public static bool IsString(string value) { if (value == null) { return false; } Regex regex = new Regex("^" + String + "$"); return regex.IsMatch(value); } public static bool IsNumber(string value) { if (value == null) { return false; } Regex regex = new Regex("^" + Number + "$"); return regex.IsMatch(value); } } public static class Resources { public const string BlankParameterName = "An attempt was made to define a parameter with a null or an invalid identifier."; public const string BlankTagName = "An attempt was made to define a tag with a null or an invalid identifier."; //public const string DuplicateParameter = "A parameter with the same name already exists within the tag."; public const string DuplicateTagDefinition = "The {0} tag has already been registered."; public const string MissingClosingTag = "Expected a matching {0} tag but none was found."; public const string PartialNotDefined = "A partial template named {0} could not be found."; public const string UnknownTag = "Encountered an unknown tag: {0}. It was either not registered or exists in a different context."; public const string WrongNumberOfArguments = "The wrong number of arguments were passed to an {0} tag."; } /// /// Represents a scope of keys. /// public sealed class Scope { private readonly object _source; private readonly Scope _parent; /// /// Initializes a new instance of a KeyScope. /// /// The object to search for keys in. internal Scope(object source) : this(source, null) { } /// /// Initializes a new instance of a KeyScope. /// /// The object to search for keys in. /// The parent scope to search in if the value is not found. internal Scope(object source, Scope parent) { _parent = parent; _source = source; } /// /// Occurs when a key/property is found in the object graph. /// public event EventHandler KeyFound; /// /// Occurs when a key/property is not found in the object graph. /// public event EventHandler KeyNotFound; /// /// Occurs when a setter is encountered and requires a value to be provided. /// public event EventHandler ValueRequested; /// /// Creates a child scope that searches for keys in a default dictionary of key/value pairs. /// /// The new child scope. public Scope CreateChildScope() { return CreateChildScope(new Dictionary()); } /// /// Creates a child scope that searches for keys in the given object. /// /// The object to search for keys in. /// The new child scope. public Scope CreateChildScope(object source) { Scope scope = new Scope(source, this); scope.KeyFound = KeyFound; scope.KeyNotFound = KeyNotFound; scope.ValueRequested = ValueRequested; return scope; } /// /// Attempts to find the value associated with the key with given name. /// /// The name of the key. /// Specifies whether the key appeared within triple curly braces. /// The value associated with the key with the given name. internal object Find(string name, bool isExtension) { SearchResults results = tryFind(name); if (results.Found) { return onKeyFound(name, results.Value, isExtension); } object value; if (onKeyNotFound(name, results.Member, isExtension, out value)) { return value; } // if the key was not found, return the original tag return "{{" + name + "}}"; } private object onKeyFound(string name, object value, bool isExtension) { if (KeyFound == null) { return value; } KeyFoundEventArgs args = new KeyFoundEventArgs(name, value, isExtension); KeyFound(this, args); return args.Substitute; } private bool onKeyNotFound(string name, string member, bool isExtension, out object value) { if (KeyNotFound == null) { value = null; return false; } KeyNotFoundEventArgs args = new KeyNotFoundEventArgs(name, member, isExtension); KeyNotFound(this, args); if (!args.Handled) { value = null; return false; } value = args.Substitute; return true; } private static IDictionary toLookup(object value) { IDictionary lookup = UpcastDictionary.Create(value); if (lookup == null) { lookup = new PropertyDictionary(value); } return lookup; } internal void Set(string key) { SearchResults results = tryFind(key); if (ValueRequested == null) { set(results, results.Value); return; } ValueRequestEventArgs e = new ValueRequestEventArgs(); if (results.Found) { e.Value = results.Value; } ValueRequested(this, e); set(results, e.Value); } internal void Set(string key, object value) { SearchResults results = tryFind(key); set(results, value); } private void set(SearchResults results, object value) { // handle setting value in child scope while (results.MemberIndex < results.Members.Length - 1) { Dictionary context = new Dictionary(); results.Value = context; results.Lookup[results.Member] = results.Value; results.Lookup = context; ++results.MemberIndex; } results.Lookup[results.Member] = value; } public bool TryFind(string name, out object value) { SearchResults result = tryFind(name); value = result.Value; return result.Found; } private SearchResults tryFind(string name) { SearchResults results = new SearchResults(); results.Members = name.Split('.'); results.MemberIndex = 0; if (results.Member == "this") { results.Found = true; results.Lookup = toLookup(_source); results.Value = _source; } else { tryFindFirst(results); } for (int index = 1; results.Found && index < results.Members.Length; ++index) { results.Lookup = toLookup(results.Value); results.MemberIndex = index; object value; results.Found = results.Lookup.TryGetValue(results.Member, out value); results.Value = value; } return results; } private void tryFindFirst(SearchResults results) { results.Lookup = toLookup(_source); object value; if (results.Lookup.TryGetValue(results.Member, out value)) { results.Found = true; results.Value = value; return; } if (_parent == null) { results.Found = false; results.Value = null; return; } _parent.tryFindFirst(results); } } internal class SearchResults { public IDictionary Lookup { get; set; } public string[] Members { get; set; } public int MemberIndex { get; set; } public string Member { get { return Members[MemberIndex]; } } public bool Found { get; set; } public object Value { get; set; } } /// /// Defines a tag that declares a named value in the current context. /// internal sealed class SetTagDefinition : InlineTagDefinition { private const string nameParameter = "name"; private static readonly TagParameter name = new TagParameter(nameParameter) { IsRequired = true }; /// /// Initializes a new instance of an SetTagDefinition. /// public SetTagDefinition() : base("set", true) { } protected override bool GetIsSetter() { return true; } protected override IEnumerable GetParameters() { return new TagParameter[] { name }; } /// /// Gets the text to output. /// /// The writer to write the output to. /// The arguments passed to the tag. /// Extra data passed along with the context. public override void GetText(TextWriter writer, Dictionary arguments, Scope contextScope) { string name = (string)arguments[nameParameter]; contextScope.Set(name); } } /// /// Generates a static block of text. /// internal sealed class StaticMustacheGenerator : IMustacheGenerator { private readonly string value; /// /// Initializes a new instance of a StaticGenerator. /// public StaticMustacheGenerator(string value, bool removeNewLines) { if (removeNewLines) { this.value = value.Replace(Environment.NewLine, string.Empty); } else { this.value = value; } } /// /// Gets or sets the static text. /// public string Value { get { return value; } } void IMustacheGenerator.GetText(TextWriter writer, Scope scope, Scope context, Action postProcessor) { writer.Write(Value); } } public class StringArgument : IArgument { private readonly string value; public StringArgument(string value) { this.value = value; } public string GetKey() { return null; } public object GetValue(Scope keyScope, Scope contextScope) { return value; } } internal class Substitution { public string Key { get; set; } public string Substitute { get; set; } public bool IsExtension { get; set; } } /// /// Defines the attributes of a custom tag. /// public abstract class TagDefinition { private readonly string _tagName; /// /// Initializes a new instance of a TagDefinition. /// /// The name of the tag. /// The name of the tag is null or blank. protected TagDefinition(string tagName) : this(tagName, false) { } /// /// Initializes a new instance of a TagDefinition. /// /// The name of the tag. /// Specifies whether the tag is built-in or not. Checks are not performed on the names of built-in tags. internal TagDefinition(string tagName, bool isBuiltIn) { if (!isBuiltIn && !RegexHelper.IsValidIdentifier(tagName)) { throw new ArgumentException(Resources.BlankTagName, "tagName"); } _tagName = tagName; } /// /// Gets the name of the tag. /// public string Name { get { return _tagName; } } internal bool IsSetter { get { return GetIsSetter(); } } protected virtual bool GetIsSetter() { return false; } /// /// Gets whether the tag is limited to the parent tag's context. /// internal bool IsContextSensitive { get { return GetIsContextSensitive(); } } /// /// Gets whether a tag is limited to the parent tag's context. /// protected virtual bool GetIsContextSensitive() { return false; } /// /// Gets the parameters that are defined for the tag. /// internal IEnumerable Parameters { get { return GetParameters(); } } /// /// Specifies which parameters are passed to the tag. /// /// The tag parameters. protected virtual IEnumerable GetParameters() { return new TagParameter[] { }; } /// /// Gets whether the tag contains content. /// internal bool HasContent { get { return GetHasContent(); } } /// /// Gets whether tag has content. /// /// True if the tag has content; otherwise, false. protected abstract bool GetHasContent(); /// /// Gets the tags that can indicate that the tag has closed. /// This field is only used if no closing tag is expected. /// internal IEnumerable ClosingTags { get { return GetClosingTags(); } } protected virtual IEnumerable GetClosingTags() { if (HasContent) { return new string[] { Name }; } else { return new string[] { }; } } /// /// Gets the tags that are in scope within the current tag. /// internal IEnumerable ChildTags { get { return GetChildTags(); } } /// /// Specifies which tags are scoped under the current tag. /// /// The child tag definitions. protected virtual IEnumerable GetChildTags() { return new string[] { }; } /// /// Gets the parameter that will be used to create a new child scope. /// /// The parameter that will be used to create a new child scope -or- null if no new scope is created. public abstract IEnumerable GetChildContextParameters(); /// /// Gets the context to use when building the inner text of the tag. /// /// The text writer passed /// The current key scope. /// The arguments passed to the tag. /// The scope context. /// The scope to use when building the inner text of the tag. public virtual IEnumerable GetChildContext( TextWriter writer, Scope keyScope, Dictionary arguments, Scope contextScope) { NestedContext context = new NestedContext() { KeyScope = keyScope, Writer = writer, ContextScope = contextScope.CreateChildScope() }; yield return context; } /// /// Applies additional formatting to the inner text of the tag. /// /// The text writer to write to. /// The arguments passed to the tag. /// The data associated to the context. public virtual void GetText(TextWriter writer, Dictionary arguments, Scope context) { } /// /// Consolidates the text in the given writer to a string, using the given arguments as necessary. /// /// The writer containing the text to consolidate. /// The arguments passed to the tag. /// The consolidated string. public virtual string ConsolidateWriter(TextWriter writer, Dictionary arguments) { return writer.ToString(); } /// /// Requests which generator group to associate the given tag type. /// /// The child tag definition being grouped. /// The name of the group to associate the given tag with. public virtual bool ShouldCreateSecondaryGroup(TagDefinition definition) { return false; } /// /// Gets whether the group with the given name should have text generated for them. /// /// The arguments passed to the tag. /// True if text should be generated for the group; otherwise, false. public virtual bool ShouldGeneratePrimaryGroup(Dictionary arguments) { return true; } } /// /// Holds the information about a tag that's been converted to text. /// public class TagFormattedEventArgs : EventArgs { /// /// Initializes a new instance of a TagFormattedEventArgs. /// /// The fully-qualified key. /// The formatted value being extended. /// Specifies whether the key was found within triple curly braces. internal TagFormattedEventArgs(string key, string value, bool isExtension) { Key = key; Substitute = value; IsExtension = isExtension; } /// /// Gets the fully-qualified key. /// public string Key { get; private set; } /// /// Gets or sets whether the key appeared within triple curly braces. /// public bool IsExtension { get; private set; } /// /// Gets or sets the object to use as the substitute. /// public string Substitute { get; set; } } /// /// Defines a parameter belonging to a custom tag. /// public sealed class TagParameter { private readonly string _name; /// /// Initializes a new instance of a TagParameter. /// /// The name of the parameter. /// The parameter name is null or an invalid identifier. public TagParameter(string parameterName) { if (!RegexHelper.IsValidIdentifier(parameterName)) { throw new ArgumentException(Resources.BlankParameterName, "parameterName"); } _name = parameterName; } /// /// Gets the name of the parameter. /// public string Name { get { return _name; } } /// /// Gets or sets whether the field is required. /// public bool IsRequired { get; set; } /// /// Gets or sets the default value to use when an argument is not provided /// for the parameter. /// public object DefaultValue { get; set; } } public static class UpcastDictionary { public static IDictionary Create(object source) { if (source == null) { return null; } IDictionary sourceDictionary = source as IDictionary; if (sourceDictionary != null) { return sourceDictionary; } Type sourceType = source.GetType(); var types = getTypes(sourceType); return getDictionary(types, source); } private static IEnumerable getTypes(Type sourceType) { Queue pending = new Queue(); HashSet visited = new HashSet(); pending.Enqueue(sourceType); while (pending.Count != 0) { Type type = pending.Dequeue(); visited.Add(type); yield return type; var typeInfo = type.GetTypeInfo(); if (typeInfo.BaseType != null) { if (!visited.Contains(typeInfo.BaseType)) { pending.Enqueue(typeInfo.BaseType); } } foreach (Type interfaceType in typeInfo.GenericTypeArguments) { if (!visited.Contains(interfaceType)) { pending.Enqueue(interfaceType); } } } } private static IDictionary getDictionary(IEnumerable types, object source) { var dictionaries = from type in types let valueType = getValueType(type) where valueType != null let upcastType = typeof(UpcastDictionary<>).MakeGenericType(valueType) select (IDictionary)Activator.CreateInstance(upcastType, source); return dictionaries.FirstOrDefault(); } private static Type getValueType(Type type) { var typeInfo = type.GetTypeInfo(); if (!typeInfo.IsGenericType) { return null; } Type[] argumentTypes = typeInfo.GenericTypeArguments; if (argumentTypes.Length != 2) { return null; } Type keyType = argumentTypes[0]; if (keyType != typeof(string)) { return null; } Type valueType = argumentTypes[1]; Type genericType = typeof(IDictionary<,>).MakeGenericType(typeof(string), valueType); if (!genericType.IsAssignableFrom(type)) { return null; } return valueType; } } public class UpcastDictionary : IDictionary { private readonly IDictionary dictionary; public UpcastDictionary(IDictionary dictionary) { this.dictionary = dictionary; } [EditorBrowsable(EditorBrowsableState.Never)] void IDictionary.Add(string key, object value) { throw new NotSupportedException(); } public bool ContainsKey(string key) { return dictionary.ContainsKey(key); } public ICollection Keys { get { return dictionary.Keys; } } [EditorBrowsable(EditorBrowsableState.Never)] bool IDictionary.Remove(string key) { throw new NotSupportedException(); } public bool TryGetValue(string key, out object value) { TValue result; if (dictionary.TryGetValue(key, out result)) { value = result; return true; } else { value = null; return false; } } public ICollection Values { get { return dictionary.Values.Cast().ToArray(); } } public object this[string key] { get { return dictionary[key]; } [EditorBrowsable(EditorBrowsableState.Never)] set { throw new NotSupportedException(); } } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection>.Add(KeyValuePair item) { throw new NotSupportedException(); } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection>.Clear() { throw new NotSupportedException(); } bool ICollection>.Contains(KeyValuePair item) { if (!(item.Value is TValue)) { return false; } KeyValuePair pair = new KeyValuePair(item.Key, (TValue)item.Value); ICollection> collection = dictionary; return dictionary.Contains(pair); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { var pairs = dictionary.Select(p => new KeyValuePair(p.Key, p.Value)).ToArray(); pairs.CopyTo(array, arrayIndex); } public int Count { get { return dictionary.Count; } } bool ICollection>.IsReadOnly { get { return true; } } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection>.Remove(KeyValuePair item) { throw new NotSupportedException(); } public IEnumerator> GetEnumerator() { return dictionary.Select(p => new KeyValuePair(p.Key, p.Value)).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } /// /// Holds the value that a context variable is set to. /// public class ValueRequestEventArgs : EventArgs { /// /// Gets or sets the value being set. /// public object Value { get; set; } } public class VariableArgument : IArgument { private readonly string name; public VariableArgument(string name) { this.name = name; } public string GetKey() { return null; } public object GetValue(Scope keyScope, Scope contextScope) { return contextScope.Find(name, false); } } /// /// Holds the information descibing a variable that is found in a template. /// public class VariableFoundEventArgs : EventArgs { /// /// Initializes a new instance of a VariableFoundEventArgs. /// /// The key that was found. /// The alignment that will be applied to the substitute value. /// The formatting that will be applied to the substitute value. /// Specifies whether the variable was found within triple curly braces. /// The context where the placeholder was found. internal VariableFoundEventArgs(string name, string alignment, string formatting, bool isExtension, Context[] context) { Name = name; Alignment = alignment; Formatting = formatting; IsExtension = isExtension; Context = context; } /// /// Gets or sets the key that was found. /// public string Name { get; set; } /// /// Gets or sets the alignment that will be applied to the substitute value. /// public string Alignment { get; set; } /// /// Gets or sets the formatting that will be applied to the substitute value. /// public string Formatting { get; set; } /// /// Gets or sets whether variable was found within triple curly braces. /// public bool IsExtension { get; set; } /// /// Gets the context where the placeholder was found. /// public Context[] Context { get; private set; } } /// /// Defines a tag that changes the scope to the object passed as an argument. /// internal sealed class WithTagDefinition : ContentTagDefinition { private const string WithTag = "with"; private const string ContextParameter = "context"; private static readonly TagParameter[] InnerContextTags = { new TagParameter(ContextParameter) { IsRequired = true } }; /// /// Initializes a new instance of a WithTagDefinition. /// public WithTagDefinition() : base(WithTag, true) { } /// /// Gets whether the tag only exists within the scope of its parent. /// protected override bool GetIsContextSensitive() { return false; } /// /// Gets the parameters that can be passed to the tag. /// /// The parameters. protected override IEnumerable GetParameters() => InnerContextTags; /// /// Gets the parameters that are used to create a new child context. /// /// The parameters that are used to create a new child context. public override IEnumerable GetChildContextParameters() => InnerContextTags; /// /// Gets the context to use when building the inner text of the tag. /// /// The text writer passed /// The current key scope. /// The arguments passed to the tag. /// The context scope. /// The scope to use when building the inner text of the tag. public override IEnumerable GetChildContext( TextWriter writer, Scope keyScope, Dictionary arguments, Scope contextScope) { object contextSource = arguments[ContextParameter]; NestedContext context = new NestedContext() { KeyScope = keyScope.CreateChildScope(contextSource), Writer = writer, ContextScope = contextScope.CreateChildScope() }; yield return context; } } public enum OnConfiguration { Configuration, ConnectionString, Omit } /* BidirectionalDictionary Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. Microsoft Open Technologies would like to thank its contributors, a list of whom are at http://aspnetwebstack.codeplex.com/wikipage?title=Contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // // This class provide service for both the singularization and pluralization, it takes the word pairs // in the ctor following the rules that the first one is singular and the second one is plural. // internal class BidirectionalDictionary { internal Dictionary FirstToSecondDictionary { get; set; } internal Dictionary SecondToFirstDictionary { get; set; } internal BidirectionalDictionary() { FirstToSecondDictionary = new Dictionary(); SecondToFirstDictionary = new Dictionary(); } internal BidirectionalDictionary(Dictionary firstToSecondDictionary) : this() { foreach (var key in firstToSecondDictionary.Keys) { AddValue(key, firstToSecondDictionary[key]); } } internal virtual bool ExistsInFirst(TFirst value) { if (FirstToSecondDictionary.ContainsKey(value)) { return true; } return false; } internal virtual bool ExistsInSecond(TSecond value) { if (SecondToFirstDictionary.ContainsKey(value)) { return true; } return false; } internal virtual TSecond GetSecondValue(TFirst value) { if (ExistsInFirst(value)) { return FirstToSecondDictionary[value]; } else { return default(TSecond); } } internal virtual TFirst GetFirstValue(TSecond value) { if (ExistsInSecond(value)) { return SecondToFirstDictionary[value]; } else { return default(TFirst); } } internal void AddValue(TFirst firstValue, TSecond secondValue) { FirstToSecondDictionary.Add(firstValue, secondValue); if (!SecondToFirstDictionary.ContainsKey(secondValue)) { SecondToFirstDictionary.Add(secondValue, firstValue); } } } /* CustomPluralizationEntry Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. Microsoft Open Technologies would like to thank its contributors, a list of whom are at http://aspnetwebstack.codeplex.com/wikipage?title=Contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /// /// Represents a custom pluralization term to be used by the /// public class CustomPluralizationEntry { /// /// Get the singular. /// public string Singular { get; private set; } /// /// Get the plural. /// public string Plural { get; private set; } /// /// Create a new instance /// /// A non null or empty string representing the singular. /// A non null or empty string representing the plural. public CustomPluralizationEntry(string singular, string plural) { if (singular == null) throw new ArgumentNullException("singular"); if (plural == null) throw new ArgumentNullException("plural"); Singular = singular; Plural = plural; } } /* DebugCheck Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. Microsoft Open Technologies would like to thank its contributors, a list of whom are at http://aspnetwebstack.codeplex.com/wikipage?title=Contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ internal class DebugCheck { [Conditional("DEBUG")] public static void NotNull(T value) where T : class { Debug.Assert(value != null); } [Conditional("DEBUG")] public static void NotEmpty(string value) { Debug.Assert(!string.IsNullOrWhiteSpace(value)); } } /* EnglishPluralizationService Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. Microsoft Open Technologies would like to thank its contributors, a list of whom are at http://aspnetwebstack.codeplex.com/wikipage?title=Contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /// /// Default pluralization service implementation to be used by Entity Framework. This pluralization /// service is based on English locale. /// public sealed class EnglishPluralizationService : IPluralizationService { private readonly BidirectionalDictionary _userDictionary; private readonly StringBidirectionalDictionary _irregularPluralsPluralizationService; private readonly StringBidirectionalDictionary _assimilatedClassicalInflectionPluralizationService; private readonly StringBidirectionalDictionary _oSuffixPluralizationService; private readonly StringBidirectionalDictionary _classicalInflectionPluralizationService; private readonly StringBidirectionalDictionary _irregularVerbPluralizationService; private readonly StringBidirectionalDictionary _wordsEndingWithSePluralizationService; private readonly StringBidirectionalDictionary _wordsEndingWithSisPluralizationService; private readonly List _knownSingluarWords; private readonly List _knownPluralWords; private readonly CultureInfo _culture = new CultureInfo("en-US"); private readonly string[] _uninflectiveSuffixes = new[] { "fish", "ois", "sheep", "deer", "pos", "itis", "ism" }; private readonly string[] _uninflectiveWords = new[] { "bison", "flounder", "pliers", "bream", "gallows", "proceedings", "breeches", "graffiti", "rabies", "britches", "headquarters", "salmon", "carp", "herpes", "scissors", "chassis", "high-jinks", "sea-bass", "clippers", "homework", "series", "cod", "innings", "shears", "contretemps", "jackanapes", "species", "corps", "mackerel", "swine", "debris", "measles", "trout", "diabetes", "mews", "tuna", "djinn", "mumps", "whiting", "eland", "news", "wildebeest", "elk", "pincers", "police", "hair", "ice", "chaos", "milk", "cotton", "corn", "millet", "hay", "pneumonoultramicroscopicsilicovolcanoconiosis", "information", "rice", "tobacco", "aircraft", "rabies", "scabies", "diabetes", "traffic", "cotton", "corn", "millet", "rice", "hay", "hemp", "tobacco", "cabbage", "okra", "broccoli", "asparagus", "lettuce", "beef", "pork", "venison", "bison", "mutton", "cattle", "offspring", "molasses", "shambles", "shingles" }; private readonly Dictionary _irregularVerbList = new Dictionary { { "am", "are" }, { "are", "are" }, { "is", "are" }, { "was", "were" }, { "were", "were" }, { "has", "have" }, { "have", "have" } }; private readonly List _pronounList = new List { "I", "we", "you", "he", "she", "they", "it", "me", "us", "him", "her", "them", "myself", "ourselves", "yourself", "himself", "herself", "itself", "oneself", "oneselves", "my", "our", "your", "his", "their", "its", "mine", "yours", "hers", "theirs", "this", "that", "these", "those", "all", "another", "any", "anybody", "anyone", "anything", "both", "each", "other", "either", "everyone", "everybody", "everything", "most", "much", "nothing", "nobody", "none", "one", "others", "some", "somebody", "someone", "something", "what", "whatever", "which", "whichever", "who", "whoever", "whom", "whomever", "whose", }; private readonly Dictionary _irregularPluralsList = new Dictionary { { "brother", "brothers" }, { "child", "children" }, { "cow", "cows" }, { "ephemeris", "ephemerides" }, { "genie", "genies" }, { "money", "moneys" }, { "mongoose", "mongooses" }, { "mythos", "mythoi" }, { "octopus", "octopuses" }, { "ox", "oxen" }, { "soliloquy", "soliloquies" }, { "trilby", "trilbys" }, { "crisis", "crises" }, { "synopsis", "synopses" }, { "rose", "roses" }, { "gas", "gases" }, { "bus", "buses" }, { "axis", "axes" }, { "memo", "memos" }, { "casino", "casinos" }, { "silo", "silos" }, { "stereo", "stereos" }, { "studio", "studios" }, { "lens", "lenses" }, { "alias", "aliases" }, { "pie", "pies" }, { "corpus", "corpora" }, { "viscus", "viscera" }, { "hippopotamus", "hippopotami" }, { "trace", "traces" }, { "person", "people" }, { "chilli", "chillies" }, { "analysis", "analyses" }, { "basis", "bases" }, { "neurosis", "neuroses" }, { "oasis", "oases" }, { "synthesis", "syntheses" }, { "thesis", "theses" }, { "pneumonoultramicroscopicsilicovolcanoconiosis", "pneumonoultramicroscopicsilicovolcanoconioses" }, { "status", "statuses" }, { "prospectus", "prospectuses" }, { "change", "changes" }, { "lie", "lies" }, { "calorie", "calories" }, { "freebie", "freebies" }, { "case", "cases" }, { "house", "houses" }, { "valve", "valves" }, { "cloth", "clothes" }, }; private readonly Dictionary _assimilatedClassicalInflectionList = new Dictionary { { "alumna", "alumnae" }, { "alga", "algae" }, { "vertebra", "vertebrae" }, { "codex", "codices" }, { "murex", "murices" }, { "silex", "silices" }, { "aphelion", "aphelia" }, { "hyperbaton", "hyperbata" }, { "perihelion", "perihelia" }, { "asyndeton", "asyndeta" }, { "noumenon", "noumena" }, { "phenomenon", "phenomena" }, { "criterion", "criteria" }, { "organon", "organa" }, { "prolegomenon", "prolegomena" }, { "agendum", "agenda" }, { "datum", "data" }, { "extremum", "extrema" }, { "bacterium", "bacteria" }, { "desideratum", "desiderata" }, { "stratum", "strata" }, { "candelabrum", "candelabra" }, { "erratum", "errata" }, { "ovum", "ova" }, { "forum", "fora" }, { "addendum", "addenda" }, { "stadium", "stadia" }, { "automaton", "automata" }, { "polyhedron", "polyhedra" }, }; private readonly Dictionary _oSuffixList = new Dictionary { { "albino", "albinos" }, { "generalissimo", "generalissimos" }, { "manifesto", "manifestos" }, { "archipelago", "archipelagos" }, { "ghetto", "ghettos" }, { "medico", "medicos" }, { "armadillo", "armadillos" }, { "guano", "guanos" }, { "octavo", "octavos" }, { "commando", "commandos" }, { "inferno", "infernos" }, { "photo", "photos" }, { "ditto", "dittos" }, { "jumbo", "jumbos" }, { "pro", "pros" }, { "dynamo", "dynamos" }, { "lingo", "lingos" }, { "quarto", "quartos" }, { "embryo", "embryos" }, { "lumbago", "lumbagos" }, { "rhino", "rhinos" }, { "fiasco", "fiascos" }, { "magneto", "magnetos" }, { "stylo", "stylos" } }; private readonly Dictionary _classicalInflectionList = new Dictionary { { "stamen", "stamina" }, { "foramen", "foramina" }, { "lumen", "lumina" }, { "anathema", "anathemata" }, { "enema", "enemata" }, { "oedema", "oedemata" }, { "bema", "bemata" }, { "enigma", "enigmata" }, { "sarcoma", "sarcomata" }, { "carcinoma", "carcinomata" }, { "gumma", "gummata" }, { "schema", "schemata" }, { "charisma", "charismata" }, { "lemma", "lemmata" }, { "soma", "somata" }, { "diploma", "diplomata" }, { "lymphoma", "lymphomata" }, { "stigma", "stigmata" }, { "dogma", "dogmata" }, { "magma", "magmata" }, { "stoma", "stomata" }, { "drama", "dramata" }, { "melisma", "melismata" }, { "trauma", "traumata" }, { "edema", "edemata" }, { "miasma", "miasmata" }, { "abscissa", "abscissae" }, { "formula", "formulae" }, { "medusa", "medusae" }, { "amoeba", "amoebae" }, { "hydra", "hydrae" }, { "nebula", "nebulae" }, { "antenna", "antennae" }, { "hyperbola", "hyperbolae" }, { "nova", "novae" }, { "aurora", "aurorae" }, { "lacuna", "lacunae" }, { "parabola", "parabolae" }, { "apex", "apices" }, { "latex", "latices" }, { "vertex", "vertices" }, { "cortex", "cortices" }, { "pontifex", "pontifices" }, { "vortex", "vortices" }, { "index", "indices" }, { "simplex", "simplices" }, { "iris", "irides" }, { "clitoris", "clitorides" }, { "alto", "alti" }, { "contralto", "contralti" }, { "soprano", "soprani" }, { "basso", "bassi" }, { "crescendo", "crescendi" }, { "tempo", "tempi" }, { "canto", "canti" }, { "solo", "soli" }, { "aquarium", "aquaria" }, { "interregnum", "interregna" }, { "quantum", "quanta" }, { "compendium", "compendia" }, { "lustrum", "lustra" }, { "rostrum", "rostra" }, { "consortium", "consortia" }, { "maximum", "maxima" }, { "spectrum", "spectra" }, { "cranium", "crania" }, { "medium", "media" }, { "speculum", "specula" }, { "curriculum", "curricula" }, { "memorandum", "memoranda" }, { "stadium", "stadia" }, { "dictum", "dicta" }, { "millenium", "millenia" }, { "trapezium", "trapezia" }, { "emporium", "emporia" }, { "minimum", "minima" }, { "ultimatum", "ultimata" }, { "enconium", "enconia" }, { "momentum", "momenta" }, { "vacuum", "vacua" }, { "gymnasium", "gymnasia" }, { "optimum", "optima" }, { "velum", "vela" }, { "honorarium", "honoraria" }, { "phylum", "phyla" }, { "focus", "foci" }, { "nimbus", "nimbi" }, { "succubus", "succubi" }, { "fungus", "fungi" }, { "nucleolus", "nucleoli" }, { "torus", "tori" }, { "genius", "genii" }, { "radius", "radii" }, { "umbilicus", "umbilici" }, { "incubus", "incubi" }, { "stylus", "styli" }, { "uterus", "uteri" }, { "stimulus", "stimuli" }, { "apparatus", "apparatus" }, { "impetus", "impetus" }, { "prospectus", "prospectus" }, { "cantus", "cantus" }, { "nexus", "nexus" }, { "sinus", "sinus" }, { "coitus", "coitus" }, { "plexus", "plexus" }, { "status", "status" }, { "hiatus", "hiatus" }, { "afreet", "afreeti" }, { "afrit", "afriti" }, { "efreet", "efreeti" }, { "cherub", "cherubim" }, { "goy", "goyim" }, { "seraph", "seraphim" }, { "alumnus", "alumni" } }; // this list contains all the plural words that being treated as singluar form, for example, "they" -> "they" private readonly List _knownConflictingPluralList = new List { "they", "them", "their", "have", "were", "yourself", "are" }; // this list contains the words ending with "se" and we special case these words since // we need to add a rule for "ses" singularize to "s" private readonly Dictionary _wordsEndingWithSeList = new Dictionary { { "house", "houses" }, { "case", "cases" }, { "enterprise", "enterprises" }, { "purchase", "purchases" }, { "surprise", "surprises" }, { "release", "releases" }, { "disease", "diseases" }, { "promise", "promises" }, { "refuse", "refuses" }, { "whose", "whoses" }, { "phase", "phases" }, { "noise", "noises" }, { "nurse", "nurses" }, { "rose", "roses" }, { "franchise", "franchises" }, { "supervise", "supervises" }, { "farmhouse", "farmhouses" }, { "suitcase", "suitcases" }, { "recourse", "recourses" }, { "impulse", "impulses" }, { "license", "licenses" }, { "diocese", "dioceses" }, { "excise", "excises" }, { "demise", "demises" }, { "blouse", "blouses" }, { "bruise", "bruises" }, { "misuse", "misuses" }, { "curse", "curses" }, { "prose", "proses" }, { "purse", "purses" }, { "goose", "gooses" }, { "tease", "teases" }, { "poise", "poises" }, { "vase", "vases" }, { "fuse", "fuses" }, { "muse", "muses" }, { "slaughterhouse", "slaughterhouses" }, { "clearinghouse", "clearinghouses" }, { "endonuclease", "endonucleases" }, { "steeplechase", "steeplechases" }, { "metamorphose", "metamorphoses" }, { "intercourse", "intercourses" }, { "commonsense", "commonsenses" }, { "intersperse", "intersperses" }, { "merchandise", "merchandises" }, { "phosphatase", "phosphatases" }, { "summerhouse", "summerhouses" }, { "watercourse", "watercourses" }, { "catchphrase", "catchphrases" }, { "compromise", "compromises" }, { "greenhouse", "greenhouses" }, { "lighthouse", "lighthouses" }, { "paraphrase", "paraphrases" }, { "mayonnaise", "mayonnaises" }, { "racecourse", "racecourses" }, { "apocalypse", "apocalypses" }, { "courthouse", "courthouses" }, { "powerhouse", "powerhouses" }, { "storehouse", "storehouses" }, { "glasshouse", "glasshouses" }, { "hypotenuse", "hypotenuses" }, { "peroxidase", "peroxidases" }, { "pillowcase", "pillowcases" }, { "roundhouse", "roundhouses" }, { "streetwise", "streetwises" }, { "expertise", "expertises" }, { "discourse", "discourses" }, { "warehouse", "warehouses" }, { "staircase", "staircases" }, { "workhouse", "workhouses" }, { "briefcase", "briefcases" }, { "clubhouse", "clubhouses" }, { "clockwise", "clockwises" }, { "concourse", "concourses" }, { "playhouse", "playhouses" }, { "turquoise", "turquoises" }, { "boathouse", "boathouses" }, { "cellulose", "celluloses" }, { "epitomise", "epitomises" }, { "gatehouse", "gatehouses" }, { "grandiose", "grandioses" }, { "menopause", "menopauses" }, { "penthouse", "penthouses" }, { "racehorse", "racehorses" }, { "transpose", "transposes" }, { "almshouse", "almshouses" }, { "customise", "customises" }, { "footloose", "footlooses" }, { "galvanise", "galvanises" }, { "princesse", "princesses" }, { "universe", "universes" }, { "workhorse", "workhorses" } }; private readonly Dictionary _wordsEndingWithSisList = new Dictionary { { "analysis", "analyses" }, { "crisis", "crises" }, { "basis", "bases" }, { "atherosclerosis", "atheroscleroses" }, { "electrophoresis", "electrophoreses" }, { "psychoanalysis", "psychoanalyses" }, { "photosynthesis", "photosyntheses" }, { "amniocentesis", "amniocenteses" }, { "metamorphosis", "metamorphoses" }, { "toxoplasmosis", "toxoplasmoses" }, { "endometriosis", "endometrioses" }, { "tuberculosis", "tuberculoses" }, { "pathogenesis", "pathogeneses" }, { "osteoporosis", "osteoporoses" }, { "parenthesis", "parentheses" }, { "anastomosis", "anastomoses" }, { "peristalsis", "peristalses" }, { "hypothesis", "hypotheses" }, { "antithesis", "antitheses" }, { "apotheosis", "apotheoses" }, { "thrombosis", "thromboses" }, { "diagnosis", "diagnoses" }, { "synthesis", "syntheses" }, { "paralysis", "paralyses" }, { "prognosis", "prognoses" }, { "cirrhosis", "cirrhoses" }, { "sclerosis", "scleroses" }, { "psychosis", "psychoses" }, { "apoptosis", "apoptoses" }, { "symbiosis", "symbioses" } }; /// /// Constructs a new instance of default pluralization service /// used in Entity Framework. /// public EnglishPluralizationService() { _userDictionary = new BidirectionalDictionary(); _irregularPluralsPluralizationService = new StringBidirectionalDictionary(_irregularPluralsList); _assimilatedClassicalInflectionPluralizationService = new StringBidirectionalDictionary(_assimilatedClassicalInflectionList); _oSuffixPluralizationService = new StringBidirectionalDictionary(_oSuffixList); _classicalInflectionPluralizationService = new StringBidirectionalDictionary(_classicalInflectionList); _wordsEndingWithSePluralizationService = new StringBidirectionalDictionary(_wordsEndingWithSeList); _wordsEndingWithSisPluralizationService = new StringBidirectionalDictionary(_wordsEndingWithSisList); // verb _irregularVerbPluralizationService = new StringBidirectionalDictionary(_irregularVerbList); _knownSingluarWords = new List( _irregularPluralsList.Keys.Concat(_assimilatedClassicalInflectionList.Keys).Concat(_oSuffixList.Keys). Concat( _classicalInflectionList.Keys).Concat(_irregularVerbList.Keys).Concat(_uninflectiveWords).Except ( _knownConflictingPluralList)); // see the _knowConflictingPluralList comment above _knownPluralWords = new List( _irregularPluralsList.Values.Concat(_assimilatedClassicalInflectionList.Values).Concat( _oSuffixList.Values).Concat( _classicalInflectionList.Values).Concat(_irregularVerbList.Values).Concat(_uninflectiveWords)); } /// /// Constructs a new instance of default pluralization service /// used in Entity Framework. /// /// /// A collection of user dictionary entries to be used by this service.These inputs /// can customize the service according the user needs. /// public EnglishPluralizationService(IEnumerable userDictionaryEntries) : this() { if (userDictionaryEntries == null) throw new ArgumentNullException("userDictionaryEntries"); foreach (var entry in userDictionaryEntries) { _userDictionary.AddValue(entry.Singular, entry.Plural); } } // CONSIDER optimize the algorithm by collecting all the special cases to one single dictionary /// Returns the plural form of the specified word. /// The plural form of the input parameter. /// The word to be made plural. public string Pluralize(string word) { return Capitalize(word, InternalPluralize); } private string InternalPluralize(string word) { // words that we know of if (_userDictionary.ExistsInFirst(word)) { return _userDictionary.GetSecondValue(word); } if (IsNoOpWord(word)) { return word; } string prefixWord; var suffixWord = GetSuffixWord(word, out prefixWord); // by me -> by me if (IsNoOpWord(suffixWord)) { return prefixWord + suffixWord; } // handle the word that do not inflect in the plural form if (IsUninflective(suffixWord)) { return prefixWord + suffixWord; } // if word is one of the known plural forms, then just return if (_knownPluralWords.Contains(suffixWord.ToLowerInvariant()) || IsPlural(suffixWord)) { return prefixWord + suffixWord; } // handle irregular plurals, e.g. "ox" -> "oxen" if (_irregularPluralsPluralizationService.ExistsInFirst(suffixWord)) { return prefixWord + _irregularPluralsPluralizationService.GetSecondValue(suffixWord); } string newSuffixWord; // handle irregular inflections for common suffixes, e.g. "mouse" -> "mice" if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "man" }, (s) => s.Remove(s.Length - 2, 2) + "en", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "louse", "mouse" }, (s) => s.Remove(s.Length - 4, 4) + "ice", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "tooth" }, (s) => s.Remove(s.Length - 4, 4) + "eeth", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "goose" }, (s) => s.Remove(s.Length - 4, 4) + "eese", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "foot" }, (s) => s.Remove(s.Length - 3, 3) + "eet", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "zoon" }, (s) => s.Remove(s.Length - 3, 3) + "oa", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "cis", "sis", "xis" }, (s) => s.Remove(s.Length - 2, 2) + "es", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } // handle assimilated classical inflections, e.g. vertebra -> vertebrae if (_assimilatedClassicalInflectionPluralizationService.ExistsInFirst(suffixWord)) { return prefixWord + _assimilatedClassicalInflectionPluralizationService.GetSecondValue(suffixWord); } // Handle the classical variants of modern inflections // CONSIDER here is the only place we took the classical variants instead of the anglicized if (_classicalInflectionPluralizationService.ExistsInFirst(suffixWord)) { return prefixWord + _classicalInflectionPluralizationService.GetSecondValue(suffixWord); } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "trix" }, (s) => s.Remove(s.Length - 1, 1) + "ces", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "eau", "ieu" }, (s) => s + "x", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "inx", "anx", "ynx" }, (s) => s.Remove(s.Length - 1, 1) + "ges", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } // [cs]h and ss that take es as plural form if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "ch", "sh", "ss" }, (s) => s + "es", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } // f, fe that take ves as plural form if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "alf", "elf", "olf", "eaf", "arf" }, (s) => s.EndsWith("deaf", true, _culture) ? s : s.Remove(s.Length - 1, 1) + "ves", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "nife", "life", "wife" }, (s) => s.Remove(s.Length - 2, 2) + "ves", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } // y takes ys as plural form if preceded by a vowel, but ies if preceded by a consonant, e.g. stays, skies if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "ay", "ey", "iy", "oy", "uy" }, (s) => s + "s", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } // CONSIDER proper noun handling, Marys, Tonys, ignore for now if (suffixWord.EndsWith("y", true, _culture)) { return prefixWord + suffixWord.Remove(suffixWord.Length - 1, 1) + "ies"; } // handle some of the words o -> os, and [vowel]o -> os, and the rest are o->oes if (_oSuffixPluralizationService.ExistsInFirst(suffixWord)) { return prefixWord + _oSuffixPluralizationService.GetSecondValue(suffixWord); } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "ao", "eo", "io", "oo", "uo" }, (s) => s + "s", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (suffixWord.EndsWith("o", true, _culture)) { return prefixWord + suffixWord + "es"; } if (suffixWord.EndsWith("x", true, _culture)) { return prefixWord + suffixWord + "es"; } // cats, bags, hats, speakers return prefixWord + suffixWord + "s"; } /// Returns the singular form of the specified word. /// The singular form of the input parameter. /// The word to be made singular. public string Singularize(string word) { return Capitalize(word, InternalSingularize); } private string InternalSingularize(string word) { // words that we know of if (_userDictionary.ExistsInSecond(word)) { return _userDictionary.GetFirstValue(word); } if (IsNoOpWord(word)) { return word; } string prefixWord; var suffixWord = GetSuffixWord(word, out prefixWord); if (IsNoOpWord(suffixWord)) { return prefixWord + suffixWord; } // handle the word that is the same as the plural form if (IsUninflective(suffixWord)) { return prefixWord + suffixWord; } // if word is one of the known singular words, then just return if (_knownSingluarWords.Contains(suffixWord.ToLowerInvariant())) { return prefixWord + suffixWord; } // handle simple irregular verbs, e.g. was -> were if (_irregularVerbPluralizationService.ExistsInSecond(suffixWord)) { return prefixWord + _irregularVerbPluralizationService.GetFirstValue(suffixWord); } // handle irregular plurals, e.g. "ox" -> "oxen" if (_irregularPluralsPluralizationService.ExistsInSecond(suffixWord)) { return prefixWord + _irregularPluralsPluralizationService.GetFirstValue(suffixWord); } // handle singluarization for words ending with sis and pluralized to ses, // e.g. "ses" -> "sis" if (_wordsEndingWithSisPluralizationService.ExistsInSecond(suffixWord)) { return prefixWord + _wordsEndingWithSisPluralizationService.GetFirstValue(suffixWord); } // handle words ending with se, e.g. "ses" -> "se" if (_wordsEndingWithSePluralizationService.ExistsInSecond(suffixWord)) { return prefixWord + _wordsEndingWithSePluralizationService.GetFirstValue(suffixWord); } string newSuffixWord; // handle irregular inflections for common suffixes, e.g. "mouse" -> "mice" if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "men" }, (s) => s.Remove(s.Length - 2, 2) + "an", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "lice", "mice" }, (s) => s.Remove(s.Length - 3, 3) + "ouse", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "teeth" }, (s) => s.Remove(s.Length - 4, 4) + "ooth", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "geese" }, (s) => s.Remove(s.Length - 4, 4) + "oose", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "feet" }, (s) => s.Remove(s.Length - 3, 3) + "oot", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "zoa" }, (s) => s.Remove(s.Length - 2, 2) + "oon", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } // [cs]h and ss that take es as plural form, this is being moved up since the sses will be override by the ses if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "ches", "shes", "sses" }, (s) => s.Remove(s.Length - 2, 2), _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } // handle assimilated classical inflections, e.g. vertebra -> vertebrae if (_assimilatedClassicalInflectionPluralizationService.ExistsInSecond(suffixWord)) { return prefixWord + _assimilatedClassicalInflectionPluralizationService.GetFirstValue(suffixWord); } // Handle the classical variants of modern inflections // CONSIDER here is the only place we took the classical variants instead of the anglicized if (_classicalInflectionPluralizationService.ExistsInSecond(suffixWord)) { return prefixWord + _classicalInflectionPluralizationService.GetFirstValue(suffixWord); } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "trices" }, (s) => s.Remove(s.Length - 3, 3) + "x", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "eaux", "ieux" }, (s) => s.Remove(s.Length - 1, 1), _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "inges", "anges", "ynges" }, (s) => s.Remove(s.Length - 3, 3) + "x", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } // f, fe that take ves as plural form if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "alves", "elves", "olves", "eaves", "arves" }, (s) => s.Remove(s.Length - 3, 3) + "f", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "nives", "lives", "wives" }, (s) => s.Remove(s.Length - 3, 3) + "fe", _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } // y takes ys as plural form if preceded by a vowel, but ies if preceded by a consonant, e.g. stays, skies if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "ays", "eys", "iys", "oys", "uys" }, (s) => s.Remove(s.Length - 1, 1), _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } // CONSIDER proper noun handling, Marys, Tonys, ignore for now if (suffixWord.EndsWith("ies", true, _culture)) { return prefixWord + suffixWord.Remove(suffixWord.Length - 3, 3) + "y"; } // handle some of the words o -> os, and [vowel]o -> os, and the rest are o->oes if (_oSuffixPluralizationService.ExistsInSecond(suffixWord)) { return prefixWord + _oSuffixPluralizationService.GetFirstValue(suffixWord); } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "aos", "eos", "ios", "oos", "uos" }, (s) => suffixWord.Remove(suffixWord.Length - 1, 1), _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } // CONSIDER limitation on the lines below, e.g. crisis -> crises -> cris // all the word ending with sis, xis, cis, their plural form cannot be singluarized correctly, // since words ending with c and cis both will get pluralized to ces // after searching the dictionary, the number of cis is just too small(7) that // we treat them as special case if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "ces" }, (s) => s.Remove(s.Length - 1, 1), _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (PluralizationServiceUtil.TryInflectOnSuffixInWord( suffixWord, new List { "ces", "ses", "xes" }, (s) => s.Remove(s.Length - 2, 2), _culture, out newSuffixWord)) { return prefixWord + newSuffixWord; } if (suffixWord.EndsWith("oes", true, _culture)) { return prefixWord + suffixWord.Remove(suffixWord.Length - 2, 2); } if (suffixWord.EndsWith("ss", true, _culture)) { return prefixWord + suffixWord; } if (suffixWord.EndsWith("s", true, _culture)) { return prefixWord + suffixWord.Remove(suffixWord.Length - 1, 1); } // word is a singlar return prefixWord + suffixWord; } private bool IsPlural(string word) { if (_userDictionary.ExistsInSecond(word)) { return true; } if (_userDictionary.ExistsInFirst(word)) { return false; } if (IsUninflective(word) || _knownPluralWords.Contains(word.ToLower(_culture))) { return true; } else { return !Singularize(word).Equals(word); } } #region Utils // // captalize the return word if the parameter is capitalized // if word is "Table", then return "Tables" // private static string Capitalize(string word, Func action) { var result = action(word); if (IsCapitalized(word)) { if (result.Length == 0) return result; var sb = new StringBuilder(result.Length); sb.Append(char.ToUpperInvariant(result[0])); sb.Append(result.Substring(1)); return sb.ToString(); } return result; } // // separate one combine word in to two parts, prefix word and the last word(suffix word) // private static string GetSuffixWord(string word, out string prefixWord) { // use the last space to separate the words var lastSpaceIndex = word.LastIndexOf(' '); prefixWord = word.Substring(0, lastSpaceIndex + 1); return word.Substring(lastSpaceIndex + 1); // CONSIDER(leil): use capital letters to separate the words } private static bool IsCapitalized(string word) { return string.IsNullOrEmpty(word) ? false : char.IsUpper(word, 0); } private static bool IsAlphabets(string word) { // return false when the word is "[\s]*" or leading or tailing with spaces // or contains non alphabetical characters if (string.IsNullOrEmpty(word.Trim()) || !word.Equals(word.Trim()) || Regex.IsMatch(word, "[^a-zA-Z\\s]")) { return false; } else { return true; } } private bool IsUninflective(string word) { DebugCheck.NotEmpty(word); if (PluralizationServiceUtil.DoesWordContainSuffix(word, _uninflectiveSuffixes, _culture) || (!word.ToLower(_culture).Equals(word) && word.EndsWith("ese", false, _culture)) || _uninflectiveWords.Contains(word.ToLowerInvariant())) { return true; } else { return false; } } // // return true when the word is "[\s]*" or leading or tailing with spaces // or contains non alphabetical characters // private bool IsNoOpWord(string word) { if (!IsAlphabets(word) || word.Length <= 1 || _pronounList.Contains(word.ToLowerInvariant())) { return true; } else { return false; } } #endregion } /* IPluralizationService Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. Microsoft Open Technologies would like to thank its contributors, a list of whom are at http://aspnetwebstack.codeplex.com/wikipage?title=Contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /// /// Pluralization services to be used by the EF runtime implement this interface. /// public interface IPluralizationService { /// /// Pluralize a word using the service. /// /// The word to pluralize. /// The pluralized word string Pluralize(string word); /// /// Singularize a word using the service. /// /// The word to singularize. /// The singularized word. string Singularize(string word); } /* PluralizationServiceUtil Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. Microsoft Open Technologies would like to thank its contributors, a list of whom are at http://aspnetwebstack.codeplex.com/wikipage?title=Contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ internal static class PluralizationServiceUtil { internal static bool DoesWordContainSuffix(string word, IEnumerable suffixes, CultureInfo culture) { return suffixes.Any(s => word.EndsWith(s, true, culture)); } internal static bool TryGetMatchedSuffixForWord( string word, IEnumerable suffixes, CultureInfo culture, out string matchedSuffix) { matchedSuffix = null; if (DoesWordContainSuffix(word, suffixes, culture)) { matchedSuffix = suffixes.First(s => word.EndsWith(s, true, culture)); return true; } return false; } internal static bool TryInflectOnSuffixInWord( string word, IEnumerable suffixes, Func operationOnWord, CultureInfo culture, out string newWord) { newWord = null; string matchedSuffixString; if (TryGetMatchedSuffixForWord( word, suffixes, culture, out matchedSuffixString)) { newWord = operationOnWord(word); return true; } return false; } } /* StringBidirectionalDictionary Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. Microsoft Open Technologies would like to thank its contributors, a list of whom are at http://aspnetwebstack.codeplex.com/wikipage?title=Contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ internal class StringBidirectionalDictionary : BidirectionalDictionary { internal StringBidirectionalDictionary() { } internal StringBidirectionalDictionary(Dictionary firstToSecondDictionary) : base(firstToSecondDictionary) { } internal override bool ExistsInFirst(string value) { return base.ExistsInFirst(value.ToLowerInvariant()); } internal override bool ExistsInSecond(string value) { return base.ExistsInSecond(value.ToLowerInvariant()); } internal override string GetFirstValue(string value) { return base.GetFirstValue(value.ToLowerInvariant()); } internal override string GetSecondValue(string value) { return base.GetSecondValue(value.ToLowerInvariant()); } } public class PropertyAndComments { public string Definition; public string Comments; public string PropertyName; public string[] AdditionalDataAnnotations; } public static class DatabaseProvider { public static string GetProvider() { return GetProvider(Settings.DatabaseType); } public static string GetProvider(DatabaseType databaseType) { switch (databaseType) { case DatabaseType.SqlServer: return "System.Data.SqlClient"; case DatabaseType.SqlCe: return "System.Data.SqlServerCe.4.0"; case DatabaseType.SQLite: return "System.Data.SQLite"; case DatabaseType.Plugin: return string.Empty; // Not used case DatabaseType.MySql: return "MySql.Data.MySqlClient"; case DatabaseType.PostgreSQL: return "Npgsql"; case DatabaseType.Oracle: return "Oracle.ManagedDataAccess.Client"; default: throw new ArgumentOutOfRangeException(); } } } public abstract class DatabaseReader { protected readonly DbProviderFactory _factory; protected IDatabaseReaderPlugin DatabaseReaderPlugin; protected readonly StringBuilder DatabaseDetails; protected Dictionary StoredProcedureParameterDbType; // [SQL Data Type] = SqlDbType. (For consistent naming) protected Dictionary DbTypeToPropertyType; // [SQL Data Type] = Language type. protected List SpatialTypes; protected List PrecisionAndScaleTypes; protected List PrecisionTypes; protected string DatabaseEdition, DatabaseEngineEdition, DatabaseProductVersion, DatabaseName; protected int DatabaseProductMajorVersion; public bool IncludeSchema { get; protected set; } public bool DoNotSpecifySizeForMaxLength { get; protected set; } public bool IsSpatialType(string propertyType) => propertyType != null && SpatialTypes != null && SpatialTypes.Contains(propertyType); public bool IsPrecisionAndScaleType(string propertyType) => propertyType != null && PrecisionAndScaleTypes != null && PrecisionAndScaleTypes.Contains(propertyType); public bool IsPrecisionType(string propertyType) => propertyType != null && PrecisionTypes != null && PrecisionTypes.Contains(propertyType); protected abstract string TableSQL(); protected abstract string ForeignKeySQL(); protected abstract string ExtendedPropertySQL(); protected abstract string DoesExtendedPropertyTableExistSQL(); protected abstract string IndexSQL(); public abstract bool CanReadStoredProcedures(); protected abstract string StoredProcedureSQL(); protected abstract string ReadDatabaseEditionSQL(); protected abstract string MultiContextSQL(); protected abstract string EnumSQL(string table, string nameField, string valueField, string groupField); protected abstract string SequenceSQL(); protected abstract string TriggerSQL(); protected abstract string[] MemoryOptimisedSQL(); protected enum MemoryOptimised { CompatibilityLevel, InMemorySupported, TableList, Count // This is always the last value } // Synonym protected abstract string SynonymTableSQLSetup(); protected abstract string SynonymTableSQL(); protected abstract string SynonymForeignKeySQLSetup(); protected abstract string SynonymForeignKeySQL(); protected abstract string SynonymStoredProcedureSQLSetup(); protected abstract string SynonymStoredProcedureSQL(); // Database specific protected abstract string DefaultSchema(DbConnection conn); protected abstract string SpecialQueryFlags(); protected abstract bool HasTemporalTableSupport(); public abstract bool HasIdentityColumnSupport(); // Stored proc return objects public abstract void ReadStoredProcReturnObjects(List procs); protected DatabaseReader(DbProviderFactory factory, IDatabaseToPropertyType databaseToPropertyType) { if (databaseToPropertyType == null) databaseToPropertyType = new SqlServerToCSharp(); // Default. Can be overridden in PluginDatabaseReader DbTypeToPropertyType = databaseToPropertyType.GetMapping(); SpatialTypes = databaseToPropertyType.SpatialTypes(); PrecisionTypes = databaseToPropertyType.PrecisionTypes(); PrecisionAndScaleTypes = databaseToPropertyType.PrecisionAndScaleTypes(); DatabaseEdition = null; DatabaseEngineEdition = null; DatabaseProductVersion = null; _factory = factory; DatabaseReaderPlugin = null; IncludeSchema = true; DoNotSpecifySizeForMaxLength = false; DatabaseDetails = new StringBuilder(255); } // Any special setup required public virtual void Init() { if (!string.IsNullOrEmpty(DatabaseEdition)) return; var sql = ReadDatabaseEditionSQL(); if (string.IsNullOrEmpty(sql)) return; using (var conn = _factory.CreateConnection()) { if (conn == null) return; conn.ConnectionString = Settings.ConnectionString; conn.Open(); var cmd = GetCmd(conn); if (cmd == null) return; cmd.CommandText = sql; using (var rdr = cmd.ExecuteReader()) { if (rdr.Read()) { DatabaseEdition = rdr["Edition"].ToString(); DatabaseEngineEdition = rdr["EngineEdition"].ToString(); DatabaseProductVersion = rdr["ProductVersion"].ToString(); DatabaseProductMajorVersion = 0; if (!string.IsNullOrEmpty(DatabaseEdition)) DatabaseDetails.AppendLine("// Database Edition : " + DatabaseEdition); if (!string.IsNullOrEmpty(DatabaseEngineEdition)) DatabaseDetails.AppendLine("// Database Engine Edition: " + DatabaseEngineEdition); if (!string.IsNullOrEmpty(DatabaseProductVersion)) { DatabaseDetails.AppendLine("// Database Version : " + DatabaseProductVersion); var version = DatabaseProductVersion; if (version.Contains('.')) version = version.Substring(0, version.IndexOf('.')); DatabaseProductMajorVersion = int.Parse(version); } DatabaseDetails.AppendLine("//"); } } Settings.DefaultSchema = DefaultSchema(conn); } } private static readonly Regex ReservedColumnNames = new Regex("^(event|Equals|GetHashCode|GetType|ToString)$", RegexOptions.Compiled); public static readonly List ReservedKeywords = new List { // C# "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "volatile", "void", "while", // .NET "Task" }; public string GetDatabaseDetails() { return DatabaseDetails.ToString(); } // Maps database type to language type. i.e. for C#, would map 'varchar' to 'string' public string GetPropertyType(string dbType) { string propertyType; if (DbTypeToPropertyType.TryGetValue(dbType, out propertyType)) return propertyType; return DbTypeToPropertyType[string.Empty]; // return default, which is usually string } // Type converter public string GetStoredProcedureParameterDbType(string sqlType) { if (StoredProcedureParameterDbType == null) return sqlType; string parameterDbType; if (StoredProcedureParameterDbType.TryGetValue(sqlType, out parameterDbType)) return parameterDbType; return StoredProcedureParameterDbType[string.Empty]; // return default, which is usually VarChar } protected DbCommand GetCmd(DbConnection connection) { if (connection == null) return null; var cmd = connection.CreateCommand(); if (cmd != null && Settings.DatabaseType != DatabaseType.SqlCe) cmd.CommandTimeout = Settings.CommandTimeout; return cmd; } public List ReadTables(bool includeSynonyms) { if (DatabaseReaderPlugin != null) return DatabaseReaderPlugin.ReadTables(); var result = new List(); using (var conn = _factory.CreateConnection()) { if (conn == null) return result; conn.ConnectionString = Settings.ConnectionString; conn.Open(); var cmd = GetCmd(conn); if (cmd == null) return result; string sql; if (includeSynonyms && Settings.DatabaseType != DatabaseType.SqlCe) sql = SynonymTableSQLSetup() + TableSQL() + SynonymTableSQL() + SpecialQueryFlags(); else sql = TableSQL() + SpecialQueryFlags(); if (!HasTemporalTableSupport()) { // Replace the column names (only present in SQL Server 2016 or later) with literal constants so the query works with older versions of SQL Server. sql = sql .Replace("[sc].[generated_always_type]", "0") .Replace("[c].[generated_always_type]", "0") .Replace("[st].[temporal_type]", "0"); } cmd.CommandText = sql; using (var rdr = cmd.ExecuteReader()) { while (rdr.Read()) { var tt = rdr["TableType"].ToString().Trim(); var table = new RawTable( rdr["SchemaName"].ToString().Trim(), rdr["TableName"].ToString().Trim(), string.Compare(tt, "VIEW", StringComparison.OrdinalIgnoreCase) == 0, string.Compare(tt, "SN", StringComparison.OrdinalIgnoreCase) == 0, ChangeType(rdr["Scale"]), rdr["TypeName"].ToString().Trim().ToLower(), ChangeType(rdr["IsNullable"]), ChangeType(rdr["MaxLength"]), ChangeType(rdr["DateTimePrecision"]), ChangeType(rdr["Precision"]), ChangeType(rdr["IsIdentity"]), ChangeType(rdr["IsComputed"]), ChangeType(rdr["IsRowGuid"]), ChangeType(rdr["GeneratedAlwaysType"]), ChangeType(rdr["IsStoreGenerated"]), ChangeType(rdr["PrimaryKeyOrdinal"]), ChangeType(rdr["PrimaryKey"]), ChangeType(rdr["IsForeignKey"]), ChangeType(rdr["SynonymTriggerName"]), ChangeType(rdr["Ordinal"]), rdr["ColumnName"].ToString().Trim(), rdr["Default"].ToString().Trim() ); result.Add(table); } } } return result; } public List ReadForeignKeys(bool includeSynonyms) { if (DatabaseReaderPlugin != null) return DatabaseReaderPlugin.ReadForeignKeys(); var result = new List(); using (var conn = _factory.CreateConnection()) { if (conn == null) return result; conn.ConnectionString = Settings.ConnectionString; conn.Open(); var cmd = GetCmd(conn); if (cmd == null) return result; if (includeSynonyms) cmd.CommandText = SynonymForeignKeySQLSetup() + ForeignKeySQL() + SynonymForeignKeySQL() + SpecialQueryFlags(); else cmd.CommandText = ForeignKeySQL() + SpecialQueryFlags(); using (var rdr = cmd.ExecuteReader()) { while (rdr.Read()) { var fk = new RawForeignKey( rdr["Constraint_Name"].ToString(), null, // ParentName is null, therefore it will be generated null, // ChildName is null, therefore it will be generated rdr["PK_Column"].ToString(), rdr["FK_Column"].ToString(), rdr["pkSchema"].ToString(), rdr["PK_Table"].ToString(), rdr["fkSchema"].ToString(), rdr["FK_Table"].ToString(), ChangeType(rdr["ORDINAL_POSITION"]), ChangeType(rdr["CascadeOnDelete"]) == 1, ChangeType(rdr["IsNotEnforced"]), false ); result.Add(fk); } } } return result; } public List ReadIndexes() { if (DatabaseReaderPlugin != null) return DatabaseReaderPlugin.ReadIndexes(); var result = new List(); using (var conn = _factory.CreateConnection()) { if (conn == null) return result; conn.ConnectionString = Settings.ConnectionString; conn.Open(); var cmd = GetCmd(conn); if (cmd == null) return result; var sql = IndexSQL(); if (string.IsNullOrWhiteSpace(sql)) return result; cmd.CommandText = sql + SpecialQueryFlags(); using (var rdr = cmd.ExecuteReader()) { while (rdr.Read()) { var index = new RawIndex ( rdr["TableSchema"].ToString().Trim(), rdr["TableName"].ToString().Trim(), rdr["IndexName"].ToString().Trim(), ChangeType(rdr["KeyOrdinal"]), rdr["ColumnName"].ToString().Trim(), ChangeType(rdr["ColumnCount"]), ChangeType(rdr["IsUnique"]), ChangeType(rdr["IsPrimaryKey"]), ChangeType(rdr["IsUniqueConstraint"]), ChangeType(rdr["IsClustered"]) == 1 ); result.Add(index); } } } return result; } // Use this on non-string types to guarantee type correctness between different databases private T ChangeType(object o) { if (o.GetType() != typeof(T)) return (T) Convert.ChangeType(o, typeof(T)); return (T) o; } public List ReadExtendedProperties() { if (DatabaseReaderPlugin != null) return DatabaseReaderPlugin.ReadExtendedProperties(); var result = new List(); using (var conn = _factory.CreateConnection()) { if (conn == null) return result; conn.ConnectionString = Settings.ConnectionString; conn.Open(); var cmd = GetCmd(conn); if (cmd == null) return result; var extendedPropertySQL = ExtendedPropertySQL(); if (string.IsNullOrEmpty(extendedPropertySQL)) return result; // Check if any SQL is returned. If so, run it. (Specific to SqlCE) var doesExtendedPropertyTableExistSQL = DoesExtendedPropertyTableExistSQL(); if (!string.IsNullOrEmpty(doesExtendedPropertyTableExistSQL)) { cmd.CommandText = doesExtendedPropertyTableExistSQL; var obj = cmd.ExecuteScalar(); if (obj == null) return result; // No extended properties table } cmd.CommandText = extendedPropertySQL + SpecialQueryFlags(); using (var rdr = cmd.ExecuteReader()) { while (rdr.Read()) { var extendedProperty = rdr["property"].ToString().Trim(); if (string.IsNullOrEmpty(extendedProperty)) continue; var rep = new RawExtendedProperty ( rdr["schema"].ToString().Trim(), rdr["table"].ToString().Trim(), rdr["column"].ToString().Trim(), extendedProperty ); result.Add(rep); } } } return result; } public List ReadStoredProcs(bool includeSynonyms) { if (DatabaseReaderPlugin != null) return DatabaseReaderPlugin.ReadStoredProcs(); var result = new List(); using (var conn = _factory.CreateConnection()) { if (conn == null) return result; conn.ConnectionString = Settings.ConnectionString; conn.Open(); var storedProcedureSQL = StoredProcedureSQL(); if (string.IsNullOrEmpty(storedProcedureSQL)) return result; var cmd = GetCmd(conn); if (cmd == null) return result; if (includeSynonyms) cmd.CommandText = SynonymStoredProcedureSQLSetup() + storedProcedureSQL + SynonymStoredProcedureSQL() + SpecialQueryFlags(); else cmd.CommandText = storedProcedureSQL + SpecialQueryFlags(); using (var rdr = cmd.ExecuteReader()) { var lastName = string.Empty; var emptyParamNumber = 1; while (rdr.Read()) { var rawDataType = rdr["DATA_TYPE"]; var schema = rdr["SPECIFIC_SCHEMA"].ToString().Trim(); var name = rdr["SPECIFIC_NAME"].ToString().Trim(); var routineType = rdr["ROUTINE_TYPE"].ToString().Trim().ToLower(); var returnDataType = rdr["RETURN_DATA_TYPE"].ToString().Trim().ToLower(); var dataType = rawDataType.ToString().Trim().ToLower(); var parameterMode = rdr["PARAMETER_MODE"].ToString().Trim().ToLower(); if (name != lastName) { lastName = name; emptyParamNumber = 1; } var isTableValuedFunction = (routineType == "function" && returnDataType == "table"); var isScalarValuedFunction = (routineType == "function" && returnDataType != "table"); var isStoredProcedure = (routineType == "procedure"); StoredProcedureParameter parameter = null; if (rawDataType != DBNull.Value) { parameter = new StoredProcedureParameter { Ordinal = ChangeType(rdr["ORDINAL_POSITION"]), Name = rdr["PARAMETER_NAME"].ToString().Trim(), SqlDbType = GetStoredProcedureParameterDbType(dataType), ReturnSqlDbType = GetStoredProcedureParameterDbType(returnDataType), PropertyType = GetPropertyType(dataType), ReturnPropertyType = GetPropertyType(returnDataType), DateTimePrecision = ChangeType(rdr["DATETIME_PRECISION"]), MaxLength = ChangeType(rdr["CHARACTER_MAXIMUM_LENGTH"]), Precision = ChangeType(rdr["NUMERIC_PRECISION"]), Scale = ChangeType(rdr["NUMERIC_SCALE"]), UserDefinedTypeName = rdr["USER_DEFINED_TYPE"].ToString().Trim(), IsSpatial = IsSpatialType(dataType) }; if (string.IsNullOrEmpty(parameter.Name)) parameter.Name = "p" + emptyParamNumber++; switch (parameterMode) { case "in": parameter.Mode = StoredProcedureParameterMode.In; break; case "out": parameter.Mode = StoredProcedureParameterMode.Out; break; default: parameter.Mode = StoredProcedureParameterMode.InOut; break; } var clean = CleanUp(parameter.Name.Replace("@", string.Empty)); if (!string.IsNullOrEmpty(clean)) { parameter.NameHumanCase = Inflector.MakeInitialLower((Settings.UsePascalCase ? Inflector.ToTitleCase(clean) : clean).Replace(" ", "")); if (ReservedKeywords.Contains(parameter.NameHumanCase)) parameter.NameHumanCase = "@" + parameter.NameHumanCase; } } var rsp = new RawStoredProcedure(schema, name, isTableValuedFunction, isScalarValuedFunction, isStoredProcedure, parameter); result.Add(rsp); } } } return result; } public List ReadMultiContextSettings() { var result = new List(); using (var conn = _factory.CreateConnection()) { if (conn == null) return result; conn.ConnectionString = string.IsNullOrWhiteSpace(Settings.MultiContextSettingsConnectionString) ? Settings.ConnectionString : Settings.MultiContextSettingsConnectionString; conn.Open(); var cmd = GetCmd(conn); if (cmd == null) return result; var sql = MultiContextSQL(); if (string.IsNullOrWhiteSpace(sql)) return result; cmd.CommandText = MultiContextSQL(); var contextMap = new Dictionary(); var tableMap = new Dictionary(); using (var rdr = cmd.ExecuteReader()) { // Contexts while (rdr.Read()) { var contextId = GetReaderInt(rdr, "Id"); if (!contextId.HasValue) continue; // Cannot use context var c = new MultiContextSettings { // Store standard fields Name = GetReaderString(rdr, "Name"), Namespace = GetReaderString(rdr, "Namespace"), Description = GetReaderString(rdr, "Description"), BaseSchema = GetReaderString(rdr, "BaseSchema"), TemplatePath = GetReaderString(rdr, "TemplatePath"), Filename = GetReaderString(rdr, "Filename"), AllFields = ReadAllFields(rdr), Tables = new List(), StoredProcedures = new List(), Enumerations = new List(), Functions = new List(), ForeignKeys = new List() }; contextMap.Add(contextId.Value, c); result.Add(c); } if (!result.Any()) return result; // Tables rdr.NextResult(); MultiContextSettings context; while (rdr.Read()) { var tableId = GetReaderInt(rdr, "Id"); if (!tableId.HasValue) continue; // Cannot use table var contextId = GetReaderInt(rdr, "ContextId"); if (!contextId.HasValue) continue; // No context if (!contextMap.ContainsKey(contextId.Value)) continue; // Context not found context = contextMap[contextId.Value]; var t = new MultiContextTableSettings { Name = GetReaderString(rdr, "Name"), Description = GetReaderString(rdr, "Description"), PluralName = GetReaderString(rdr, "PluralName"), DbName = GetReaderString(rdr, "DbName"), Attributes = GetReaderString(rdr, "Attributes"), DbSetModifier = GetReaderString(rdr, "DbSetModifier"), AllFields = ReadAllFields(rdr), Columns = new List() }; tableMap.Add(tableId.Value, t); context.Tables.Add(t); } // Columns rdr.NextResult(); while (rdr.Read()) { var tableId = GetReaderInt(rdr, "TableId"); if (tableId == null) continue; // Cannot use column as not associated to a table if (!tableMap.ContainsKey(tableId.Value)) continue; // Table not found var table = tableMap[tableId.Value]; var col = new MultiContextColumnSettings { Name = GetReaderString(rdr, "Name"), DbName = GetReaderString(rdr, "DbName"), IsPrimaryKey = GetReaderBool(rdr, "IsPrimaryKey"), OverrideModifier = GetReaderBool(rdr, "OverrideModifier"), EnumType = GetReaderString(rdr, "EnumType"), Attributes = GetReaderString(rdr, "Attributes"), PropertyType = GetReaderString(rdr, "PropertyType"), IsNullable = GetReaderBool(rdr, "IsNullable"), AllFields = ReadAllFields(rdr) }; table.Columns.Add(col); } // Stored Procedures rdr.NextResult(); while (rdr.Read()) { var contextId = GetReaderInt(rdr, "ContextId"); if (!contextId.HasValue) continue; // No context if (!contextMap.ContainsKey(contextId.Value)) continue; // Context not found context = contextMap[contextId.Value]; var sp = new MultiContextStoredProcedureSettings { Name = GetReaderString(rdr, "Name"), DbName = GetReaderString(rdr, "DbName"), ReturnModel = GetReaderString(rdr, "ReturnModel"), AllFields = ReadAllFields(rdr) }; context.StoredProcedures.Add(sp); } // Functions rdr.NextResult(); while (rdr.Read()) { var contextId = GetReaderInt(rdr, "ContextId"); if (!contextId.HasValue) continue; // No context if (!contextMap.ContainsKey(contextId.Value)) continue; // Context not found context = contextMap[contextId.Value]; var f = new MultiContextFunctionSettings { Name = GetReaderString(rdr, "Name"), DbName = GetReaderString(rdr, "DbName"), AllFields = ReadAllFields(rdr) }; context.Functions.Add(f); } // Enumerations rdr.NextResult(); while (rdr.Read()) { var contextId = GetReaderInt(rdr, "ContextId"); if (!contextId.HasValue) continue; // No context if (!contextMap.ContainsKey(contextId.Value)) continue; // Context not found context = contextMap[contextId.Value]; var e = new EnumerationSettings { Name = GetReaderString(rdr, "Name"), Table = GetReaderString(rdr, "Table"), NameField = GetReaderString(rdr, "NameField"), ValueField = GetReaderString(rdr, "ValueField"), GroupField = GetReaderString(rdr, "GroupField"), AllFields = ReadAllFields(rdr) }; context.Enumerations.Add(e); } // Foreign keys rdr.NextResult(); while (rdr.Read()) { var contextId = GetReaderInt(rdr, "ContextId"); if (!contextId.HasValue) continue; // No context if (!contextMap.ContainsKey(contextId.Value)) continue; // Context not found context = contextMap[contextId.Value]; var fk = new MultiContextForeignKeySettings { ConstraintName = GetReaderString(rdr, "ConstraintName"), ParentName = GetReaderString(rdr, "ParentName"), ChildName = GetReaderString(rdr, "ChildName"), PkSchema = GetReaderString(rdr, "PkSchema") ?? context.BaseSchema, PkTableName = GetReaderString(rdr, "PkTableName"), PkColumn = GetReaderString(rdr, "PkColumn"), FkSchema = GetReaderString(rdr, "FkSchema") ?? context.BaseSchema, FkTableName = GetReaderString(rdr, "FkTableName"), FkColumn = GetReaderString(rdr, "FkColumn"), Ordinal = GetReaderInt(rdr, "Ordinal") ?? 0, CascadeOnDelete = GetReaderBool(rdr, "CascadeOnDelete") ?? false, IsNotEnforced = GetReaderBool(rdr, "IsNotEnforced") ?? false, HasUniqueConstraint = GetReaderBool(rdr, "HasUniqueConstraint") ?? false }; context.ForeignKeys.Add(fk); } } } return result; } private Dictionary ReadAllFields(DbDataReader rdr) { var result = new Dictionary(); for (var n = 0; n < rdr.FieldCount; ++n) { var o = rdr.GetValue(n); if (o != DBNull.Value) result.Add(rdr.GetName(n), rdr.GetValue(n)); } return result; } public List ReadEnums(List enums) { var result = new List(); using (var conn = _factory.CreateConnection()) { if (conn == null) return result; conn.ConnectionString = Settings.ConnectionString; conn.Open(); var cmd = GetCmd(conn); if (cmd == null) return result; foreach (var e in enums) { var sql = EnumSQL(e.Table, e.NameField, e.ValueField, e.GroupField); var enumDict = new Dictionary>(); if (string.IsNullOrEmpty(sql)) continue; cmd.CommandText = sql; try { using (var rdr = cmd.ExecuteReader()) { while (rdr.Read()) { var name = rdr["NameField"].ToString().Trim(); if (string.IsNullOrEmpty(name)) continue; var group = string.Empty; if (!String.IsNullOrEmpty(e.GroupField)) { group = rdr["GroupField"].ToString().Trim(); group = RemoveNonAlphaNumeric.Replace(group, string.Empty); group = (Settings.UsePascalCaseForEnumMembers ? Inflector.ToTitleCase(group) : group).Replace(" ", string.Empty).Trim(); } if (!enumDict.ContainsKey(group)) { enumDict.Add(group, new List()); } name = RemoveNonAlphaNumeric.Replace(name, string.Empty); name = (Settings.UsePascalCaseForEnumMembers ? Inflector.ToTitleCase(name) : name).Replace(" ", string.Empty).Trim(); if (string.IsNullOrEmpty(name)) continue; var value = rdr["ValueField"].ToString().Trim(); if (string.IsNullOrEmpty(value)) continue; var allValues = new Dictionary(); for (var n = 2; n < rdr.FieldCount; ++n) { var o = rdr.GetValue(n); allValues.Add(rdr.GetName(n), o != DBNull.Value ? o : null); } enumDict[group].Add(new EnumerationMember(name, value, allValues)); } foreach (var v in enumDict) { if (v.Value.Any()) { result.Add(new Enumeration(e.Name.Replace("{GroupField}", v.Key), v.Value)); } } } } catch (Exception) { // Enum table does not exist in database, skip } } } return result; } public List ReadSequences() { if (DatabaseReaderPlugin != null) return DatabaseReaderPlugin.ReadSequences(); var result = new List(); using (var conn = _factory.CreateConnection()) { if (conn == null) return result; conn.ConnectionString = Settings.ConnectionString; conn.Open(); var cmd = GetCmd(conn); if (cmd == null) return result; var sql = SequenceSQL(); if (string.IsNullOrWhiteSpace(sql)) return result; cmd.CommandText = sql; RawSequence rs = null; using (var rdr = cmd.ExecuteReader()) { // Sequences while (rdr.Read()) { var dataType = rdr["DataType"].ToString().Trim().ToLower(); var schema = rdr["Schema"].ToString().Trim(); var name = rdr["Name"].ToString().Trim(); if (rs == null || rs.Schema != schema || rs.Name != name) { rs = new RawSequence ( schema, name, GetPropertyType(dataType), rdr["StartValue"].ToString(), rdr["IncrementValue"].ToString(), rdr["MinValue"].ToString(), rdr["MaxValue"].ToString(), GetReaderBool(rdr, "IsCycleEnabled") ?? false ); result.Add(rs); } rs.TableMapping.Add(new RawSequenceTableMapping( rdr["TableSchema"].ToString().Trim(), rdr["TableName"].ToString().Trim())); } if (!result.Any()) return result; } } return result; } public List ReadTriggers() { if (DatabaseReaderPlugin != null) return DatabaseReaderPlugin.ReadTriggers(); var result = new List(); using (var conn = _factory.CreateConnection()) { if (conn == null) return result; conn.ConnectionString = Settings.ConnectionString; conn.Open(); var cmd = GetCmd(conn); if (cmd == null) return result; var sql = TriggerSQL(); if (string.IsNullOrWhiteSpace(sql)) return result; cmd.CommandText = sql; using (var rdr = cmd.ExecuteReader()) { while (rdr.Read()) { var index = new RawTrigger ( rdr["SchemaName"].ToString().Trim(), rdr["TableName"].ToString().Trim(), rdr["TriggerName"].ToString().Trim() ); result.Add(index); } } } return result; } public List ReadMemoryOptimisedTables() { if (DatabaseReaderPlugin != null) return DatabaseReaderPlugin.ReadMemoryOptimisedTables(); var result = new List(); try { using (var conn = _factory.CreateConnection()) { if (conn == null) return result; conn.ConnectionString = Settings.ConnectionString; conn.Open(); var cmd = GetCmd(conn); if (cmd == null) return result; var sql = MemoryOptimisedSQL(); if (sql == null || sql.Length != (int) MemoryOptimised.Count) return result; cmd.CommandText = sql[(int) MemoryOptimised.CompatibilityLevel]; var compatibilityLevel = Convert.ToInt16(cmd.ExecuteScalar()); if (compatibilityLevel < 130) return result; cmd.CommandText = sql[(int) MemoryOptimised.InMemorySupported]; var inMemorySupported = Convert.ToBoolean(cmd.ExecuteScalar()); if (!inMemorySupported) return result; cmd.CommandText = sql[(int) MemoryOptimised.TableList]; using (var rdr = cmd.ExecuteReader()) { while (rdr.Read()) { var index = new RawMemoryOptimisedTable ( rdr["SchemaName"].ToString().Trim(), rdr["TableName"].ToString().Trim() ); result.Add(index); } } } } catch (Exception) { // Memory optimised tables are not supported } return result; } private static string GetReaderString(DbDataReader rdr, string name) { try { return rdr[name].ToString().Trim(); } catch (Exception) { return null; } } private static int? GetReaderInt(DbDataReader rdr, string name) { try { return (int) rdr[name]; } catch (Exception) { return null; } } private static bool? GetReaderBool(DbDataReader rdr, string name) { try { return (bool) rdr[name]; } catch (Exception) { return null; } } public Column CreateColumn(RawTable rt, Table table, IDbContextFilter filter) { if (!string.IsNullOrEmpty(rt.SynonymTriggerName) && string.IsNullOrEmpty(table.TriggerName)) table.TriggerName = rt.SynonymTriggerName; var col = new Column { Scale = rt.Scale, PropertyType = GetPropertyType(rt.TypeName), SqlPropertyType = rt.TypeName, IsNullable = rt.IsNullable, MaxLength = rt.MaxLength, DateTimePrecision = rt.DateTimePrecision, Precision = rt.Precision, IsIdentity = rt.IsIdentity, IsComputed = rt.IsComputed, IsRowGuid = rt.IsRowGuid, GeneratedAlwaysType = (ColumnGeneratedAlwaysType) rt.GeneratedAlwaysType, IsStoreGenerated = rt.IsStoreGenerated, PrimaryKeyOrdinal = rt.PrimaryKeyOrdinal, IsPrimaryKey = rt.PrimaryKey, IsForeignKey = rt.IsForeignKey, IsSpatial = rt.TypeName == "geography" || rt.TypeName == "geometry", Ordinal = rt.Ordinal, DbName = rt.ColumnName, Default = rt.Default, ParentTable = table, ExistsInBaseClass = false }; if (col.IsPrimaryKey) col.IsNullable = false; if (col.MaxLength == -1 && (col.SqlPropertyType.EndsWith("varchar", StringComparison.InvariantCultureIgnoreCase) || col.SqlPropertyType.EndsWith("varbinary", StringComparison.InvariantCultureIgnoreCase))) { col.SqlPropertyType += "(max)"; } if (col.IsPrimaryKey && !col.IsIdentity && col.IsStoreGenerated && rt.TypeName == "uniqueidentifier") { col.IsStoreGenerated = false; col.IsIdentity = true; } if (!col.IsPrimaryKey && filter.IsExcluded(col)) col.Hidden = true; col.IsFixedLength = (rt.TypeName == "char" || rt.TypeName == "nchar"); col.IsUnicode = !(rt.TypeName == "char" || rt.TypeName == "varchar" || rt.TypeName == "text"); col.IsMaxLength = (rt.TypeName == "ntext"); col.IsRowVersion = col.IsStoreGenerated && rt.TypeName == "timestamp"; if (col.IsRowVersion) col.MaxLength = 8; if (rt.TypeName == "hierarchyid") col.MaxLength = 0; col.CleanUpDefault(); col.NameHumanCase = CleanUp(col.DbName); if (string.IsNullOrWhiteSpace(col.NameHumanCase)) { col.NameHumanCase = "Unknown"; col.Hidden = true; } col.NameHumanCase = ReservedColumnNames.Replace(col.NameHumanCase, "_$1"); if (ReservedKeywords.Contains(col.NameHumanCase)) col.NameHumanCase = "@" + col.NameHumanCase; col.DisplayName = Column.ToDisplayName(col.DbName); var titleCase = (Settings.UsePascalCase ? Inflector.ToTitleCase(col.NameHumanCase) : col.NameHumanCase).Replace(" ", string.Empty); if (titleCase != string.Empty) col.NameHumanCase = titleCase; // Make sure property name doesn't clash with class name if (col.NameHumanCase == table.NameHumanCase) col.NameHumanCase += "_"; if (char.IsDigit(col.NameHumanCase[0])) col.NameHumanCase = "_" + col.NameHumanCase; table.HasNullableColumns = col.IsColumnNullable(); // If PropertyType is empty, return null. Most likely ignoring a column due to legacy (such as OData not supporting spatial types) if (string.IsNullOrEmpty(col.PropertyType)) return null; return col; } private static readonly Regex RemoveNonAlphaNumeric = new Regex(@"[^\w\d\s_-]", RegexOptions.Compiled); private static readonly Regex RemoveTrailingSymbols = new Regex(@"[$-/:-?{-~!""^_`\[\]]+$", RegexOptions.Compiled); public static readonly Func CleanUp = (str) => { // Replace punctuation and symbols in variable names as these are not allowed. if (string.IsNullOrEmpty(str)) return string.Empty; if (str.Any(char.IsLetterOrDigit)) str = RemoveTrailingSymbols.Replace(str.Replace('-', '_').Replace('.', '_'), string.Empty); var len = str.Length; if (len == 0) return string.Empty; var sb = new StringBuilder(len + 20); var replacedCharacter = false; for (var n = 0; n < len; ++n) { var c = str[n]; if (c != '_' && c != '-' && (char.IsSymbol(c) || char.IsPunctuation(c))) { int ascii = c; sb.AppendFormat("{0}", ascii); replacedCharacter = true; continue; } sb.Append(c); } if (replacedCharacter) str = sb.ToString(); str = RemoveNonAlphaNumeric.Replace(str, string.Empty); if (char.IsDigit(str[0])) str = "C" + str; return str; }; } public static class DatabaseReaderFactory { public static DatabaseReader Create(DbProviderFactory factory) { var databaseToPropertyType = DatabaseToPropertyTypeFactory.Create(); switch (Settings.DatabaseType) { case DatabaseType.SqlServer: return new SqlServerDatabaseReader(factory, databaseToPropertyType); case DatabaseType.SQLite: return new SqLiteDatabaseReader(factory, databaseToPropertyType); case DatabaseType.SqlCe: return new SqlServerCeDatabaseReader(factory, databaseToPropertyType); case DatabaseType.Plugin: if(string.IsNullOrWhiteSpace(Settings.DatabaseReaderPlugin)) throw new ArgumentOutOfRangeException(); return new PluginDatabaseReader(null); case DatabaseType.MySql: return new MySqlDatabaseReader(factory, databaseToPropertyType); case DatabaseType.PostgreSQL: return new PostgreSqlDatabaseReader(factory, databaseToPropertyType); case DatabaseType.Oracle: return new OracleDatabaseReader(factory, databaseToPropertyType); default: throw new ArgumentOutOfRangeException(); } } } public interface IDatabaseReaderPlugin { List ReadTables(); List ReadForeignKeys(); List ReadIndexes(); List ReadExtendedProperties(); List ReadStoredProcs(); List ReadSequences(); List ReadTriggers(); List ReadMemoryOptimisedTables(); IDatabaseToPropertyType GetDatabaseToPropertyTypeMapping(); } public interface IMultiContextSettingsPlugin { List ReadSettings(); } public static class MinMaxValueCache { private static readonly Dictionary minValues = new Dictionary { { "sbyte", sbyte .MinValue.ToString() }, { "byte", byte .MinValue.ToString() }, { "short", short .MinValue.ToString() }, { "ushort", ushort .MinValue.ToString() }, { "int", int .MinValue.ToString() }, { "uint", uint .MinValue.ToString() }, { "long", long .MinValue.ToString() }, { "ulong", ulong .MinValue.ToString() }, { "float", float .MinValue.ToString() }, { "double", double .MinValue.ToString() }, { "decimal", decimal.MinValue.ToString() } }; private static readonly Dictionary maxValues = new Dictionary { { "sbyte", sbyte .MaxValue.ToString() }, { "byte", byte .MaxValue.ToString() }, { "short", short .MaxValue.ToString() }, { "ushort", ushort .MaxValue.ToString() }, { "int", int .MaxValue.ToString() }, { "uint", uint .MaxValue.ToString() }, { "long", long .MaxValue.ToString() }, { "ulong", ulong .MaxValue.ToString() }, { "float", float .MaxValue.ToString() }, { "double", double .MaxValue.ToString() }, { "decimal", decimal.MaxValue.ToString() } }; public static string GetMinValue(string type) { string value; return minValues.TryGetValue(type, out value) ? value : null; } public static string GetMaxValue(string type) { string value; return maxValues.TryGetValue(type, out value) ? value : null; } } public class MySqlDatabaseReader : DatabaseReader { public MySqlDatabaseReader(DbProviderFactory factory, IDatabaseToPropertyType databaseToPropertyType) : base(factory, databaseToPropertyType) { } protected override string TableSQL() { return string.Empty; } protected override string ForeignKeySQL() { return string.Empty; } protected override string ExtendedPropertySQL() { return string.Empty; } protected override string DoesExtendedPropertyTableExistSQL() { return string.Empty; } protected override string IndexSQL() { return string.Empty; } public override bool CanReadStoredProcedures() { return false; } protected override string StoredProcedureSQL() { return string.Empty; } protected override string ReadDatabaseEditionSQL() { return string.Empty; } protected override string MultiContextSQL() { return string.Empty; } protected override string EnumSQL(string table, string nameField, string valueField, string groupField) { return string.Empty; } protected override string SequenceSQL() { return string.Empty; } protected override string TriggerSQL() { return string.Empty; } protected override string[] MemoryOptimisedSQL() { return null; } protected override string SynonymTableSQLSetup() { return string.Empty; } protected override string SynonymTableSQL() { return string.Empty; } protected override string SynonymForeignKeySQLSetup() { return string.Empty; } protected override string SynonymForeignKeySQL() { return string.Empty; } protected override string SynonymStoredProcedureSQLSetup() { return string.Empty; } protected override string SynonymStoredProcedureSQL() { return string.Empty; } protected override string DefaultSchema(DbConnection conn) { return "mydb"; } protected override string SpecialQueryFlags() { return string.Empty; } protected override bool HasTemporalTableSupport() { return false; } public override bool HasIdentityColumnSupport() { return true; } public override void ReadStoredProcReturnObjects(List procs) { throw new System.NotImplementedException(); } public override void Init() { base.Init(); } } public class OracleDatabaseReader : DatabaseReader { public OracleDatabaseReader(DbProviderFactory factory, IDatabaseToPropertyType databaseToPropertyType) : base(factory, databaseToPropertyType) { } protected override string TableSQL() { return string.Empty; } protected override string ForeignKeySQL() { return string.Empty; } protected override string ExtendedPropertySQL() { return string.Empty; } protected override string DoesExtendedPropertyTableExistSQL() { return string.Empty; } protected override string IndexSQL() { return string.Empty; } public override bool CanReadStoredProcedures() { return false; } protected override string StoredProcedureSQL() { return string.Empty; } protected override string ReadDatabaseEditionSQL() { return "SELECT BANNER AS Edition, EDITION AS EngineEdition, VERSION AS ProductVersion FROM V$VERSION CROSS JOIN V$INSTANCE WHERE BANNER LIKE 'Oracle%'"; } protected override string MultiContextSQL() { return string.Empty; } protected override string EnumSQL(string table, string nameField, string valueField, string groupField) { return string.Empty; } protected override string SequenceSQL() { return string.Empty; } protected override string TriggerSQL() { return string.Empty; } protected override string[] MemoryOptimisedSQL() { return null; } protected override string SynonymTableSQLSetup() { return string.Empty; } protected override string SynonymTableSQL() { return string.Empty; } protected override string SynonymForeignKeySQLSetup() { return string.Empty; } protected override string SynonymForeignKeySQL() { return string.Empty; } protected override string SynonymStoredProcedureSQLSetup() { return string.Empty; } protected override string SynonymStoredProcedureSQL() { return string.Empty; } protected override string DefaultSchema(DbConnection conn) { var cmd = GetCmd(conn); if (cmd != null) { cmd.CommandText = "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL"; using (var rdr = cmd.ExecuteReader()) { if (rdr.Read()) { return rdr[0].ToString(); } } } return "system"; } protected override string SpecialQueryFlags() { return string.Empty; } protected override bool HasTemporalTableSupport() { return false; } public override bool HasIdentityColumnSupport() { return true; } public override void ReadStoredProcReturnObjects(List procs) { throw new System.NotImplementedException(); } public override void Init() { base.Init(); } } // Leave this class empty. All tables, etc will come from a plugin specified in Settings.DatabaseReaderPlugin public class PluginDatabaseReader : DatabaseReader { public PluginDatabaseReader(DbProviderFactory factory) : base(factory, null) { DatabaseReaderPlugin = (IDatabaseReaderPlugin) AssemblyHelper.LoadPlugin(Settings.DatabaseReaderPlugin); var databaseToPropertyType = DatabaseReaderPlugin.GetDatabaseToPropertyTypeMapping(); if(databaseToPropertyType != null) DbTypeToPropertyType = databaseToPropertyType.GetMapping(); // Override default with plugin version } protected override string TableSQL() { return string.Empty; } protected override string ForeignKeySQL() { return string.Empty; } protected override string ExtendedPropertySQL() { return string.Empty; } protected override string DoesExtendedPropertyTableExistSQL() { return string.Empty; } protected override string IndexSQL() { return string.Empty; } public override bool CanReadStoredProcedures() { return false; } protected override string StoredProcedureSQL() { return string.Empty; } protected override string ReadDatabaseEditionSQL() { return string.Empty; } protected override string MultiContextSQL() { return string.Empty; } protected override string EnumSQL(string table, string nameField, string valueField, string groupField) { return string.Empty; } protected override string SequenceSQL() { return string.Empty; } protected override string TriggerSQL() { return string.Empty; } protected override string[] MemoryOptimisedSQL() { return null; } protected override string SynonymTableSQLSetup() { return string.Empty; } protected override string SynonymTableSQL() { return string.Empty; } protected override string SynonymForeignKeySQLSetup() { return string.Empty; } protected override string SynonymForeignKeySQL() { return string.Empty; } protected override string SynonymStoredProcedureSQLSetup() { return string.Empty; } protected override string SynonymStoredProcedureSQL() { return string.Empty; } protected override string DefaultSchema(DbConnection conn) { return "dbo"; } protected override string SpecialQueryFlags() { return string.Empty; } protected override bool HasTemporalTableSupport() { return false; } public override bool HasIdentityColumnSupport() { return true; } public override void ReadStoredProcReturnObjects(List procs) { } public override void Init() { base.Init(); } } public class PostgreSqlDatabaseReader : DatabaseReader { public PostgreSqlDatabaseReader(DbProviderFactory factory, IDatabaseToPropertyType databaseToPropertyType) : base(factory, databaseToPropertyType) { StoredProcedureParameterDbType = null; } protected override string TableSQL() { return @" SELECT T.TABLE_SCHEMA AS ""SchemaName"", T.TABLE_NAME AS ""TableName"", T.TABLE_TYPE AS ""TableType"", CAST(0 AS SMALLINT) AS ""TableTemporalType"", C.ORDINAL_POSITION AS ""Ordinal"", C.COLUMN_NAME AS ""ColumnName"", CASE WHEN LOWER(C.IS_NULLABLE) = 'yes' THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END AS ""IsNullable"", C.DATA_TYPE AS ""TypeName"", COALESCE(C.CHARACTER_MAXIMUM_LENGTH, 0) AS ""MaxLength"", CAST(COALESCE(C.NUMERIC_PRECISION, 0) AS INT) AS ""Precision"", COALESCE(C.COLUMN_DEFAULT, '') AS ""Default"", CAST(COALESCE(C.DATETIME_PRECISION, 0) AS INT) AS ""DateTimePrecision"", COALESCE(C.NUMERIC_SCALE, 0) AS ""Scale"", CASE WHEN LOWER(C.is_identity) = 'yes' THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)END AS ""IsIdentity"", CAST(0 AS BIT) AS ""IsRowGuid"", CASE WHEN LOWER(C.is_generated) = 'never' THEN CAST(0 AS BIT) ELSE CAST(1 AS BIT) END AS ""IsComputed"", CAST(0 AS SMALLINT) AS ""GeneratedAlwaysType"", CAST(CASE WHEN LOWER(C.is_identity) = 'yes' OR LOWER(C.is_generated) <> 'never' THEN 1 ELSE 0 END AS BIT) AS ""IsStoreGenerated"", CAST(CASE WHEN pk.ordinal_position > 0 THEN 1 ELSE 0 END AS bit) as ""PrimaryKey"", COALESCE(pk.ordinal_position, 0) ""PrimaryKeyOrdinal"", CAST(CASE WHEN EXISTS (select 1 from INFORMATION_SCHEMA.TABLE_CONSTRAINTS tcfk INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE fk ON tcfk.TABLE_SCHEMA = fk.TABLE_SCHEMA AND tcfk.TABLE_NAME = fk.TABLE_NAME AND tcfk.CONSTRAINT_NAME = fk.CONSTRAINT_NAME AND tcfk.CONSTRAINT_TYPE = 'FOREIGN KEY' AND C.TABLE_SCHEMA = fk.TABLE_SCHEMA AND C.TABLE_NAME = fk.TABLE_NAME AND C.COLUMN_NAME = fk.COLUMN_NAME) THEN 1 ELSE 0 END AS bit) AS ""IsForeignKey"", NULL AS ""SynonymTriggerName"" FROM INFORMATION_SCHEMA.TABLES T INNER JOIN INFORMATION_SCHEMA.COLUMNS C ON T.TABLE_SCHEMA = C.TABLE_SCHEMA AND C.TABLE_NAME = T.TABLE_NAME AND C.table_catalog = T.table_catalog LEFT OUTER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE pk ON tc.CONSTRAINT_SCHEMA = pk.TABLE_SCHEMA AND tc.TABLE_NAME = pk.TABLE_NAME AND tc.CONSTRAINT_NAME = pk.CONSTRAINT_NAME AND LOWER(tc.CONSTRAINT_TYPE) = 'primary key' ON pk.TABLE_SCHEMA = C.TABLE_SCHEMA AND pk.TABLE_NAME = C.TABLE_NAME AND pk.COLUMN_NAME = C.COLUMN_NAME WHERE (LOWER(T.TABLE_TYPE) = 'base table' OR LOWER(T.TABLE_TYPE) = 'view') AND (LOWER(T.TABLE_SCHEMA) NOT IN ('pg_catalog', 'information_schema')) AND (LOWER(T.TABLE_NAME) NOT IN ('edmmetadata', '__migrationhistory', '__efmigrationshistory', '__refactorlog')) ORDER BY T.TABLE_NAME, C.COLUMN_NAME, C.ORDINAL_POSITION;"; } protected override string ForeignKeySQL() { return @" SELECT tc.TABLE_NAME AS ""FK_Table"", kcu.COLUMN_NAME AS ""FK_Column"", ccu.TABLE_NAME AS ""PK_Table"", ccu.COLUMN_NAME AS ""PK_Column"", tc.CONSTRAINT_NAME AS ""Constraint_Name"", ccu.TABLE_SCHEMA AS ""fkSchema"", tc.TABLE_SCHEMA AS ""pkSchema"", ccu.COLUMN_NAME as ""primarykey"", kcu.ORDINAL_POSITION AS ""ORDINAL_POSITION"", CASE WHEN LOWER(fk.DELETE_RULE) = 'cascade' THEN 1 ELSE 0 END AS ""CascadeOnDelete"", CAST(0 AS BIT) AS ""IsNotEnforced"" FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND tc.TABLE_SCHEMA = kcu.TABLE_SCHEMA INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu ON ccu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME AND ccu.TABLE_SCHEMA = tc.TABLE_SCHEMA INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS fk ON fk.CONSTRAINT_CATALOG = ccu.CONSTRAINT_CATALOG AND fk.CONSTRAINT_SCHEMA = ccu.CONSTRAINT_SCHEMA AND fk.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME WHERE LOWER(tc.CONSTRAINT_TYPE) = 'foreign key'"; } protected override string ExtendedPropertySQL() { return @" SELECT c.TABLE_SCHEMA as ""schema"", c.TABLE_NAME as ""table"", c.COLUMN_NAME as ""column"", pgd.description as ""property"" FROM pg_catalog.pg_statio_all_tables st INNER JOIN pg_catalog.pg_description pgd ON (pgd.objoid = st.relid) INNER JOIN INFORMATION_SCHEMA.COLUMNS c ON ( pgd.objsubid = c.ORDINAL_POSITION AND c.TABLE_SCHEMA = st.schemaname AND c.TABLE_NAME = st.relname );"; } protected override string DoesExtendedPropertyTableExistSQL() { return string.Empty; } protected override string IndexSQL() { return @" SELECT n.nspname AS ""TableSchema"", t.relname AS ""TableName"", i.relname AS ""IndexName"", a.attnum AS ""KeyOrdinal"", a.attname AS ""ColumnName"", ix.indisunique AS ""IsUnique"", ix.indisprimary AS ""IsPrimaryKey"", 0 AS ""IsUniqueConstraint"", ix.indisclustered AS ""IsClustered"", ix.indnatts AS ""ColumnCount"" FROM pg_index ix INNER JOIN pg_class t ON t.oid = ix.indrelid AND LOWER(t.relkind) = 'r' INNER JOIN pg_class i ON i.oid = ix.indexrelid INNER JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) INNER JOIN pg_namespace n ON n.oid = t.relnamespace WHERE LOWER(n.nspname) NOT IN ('pg_catalog', 'information_schema') AND LOWER(t.relname) NOT IN ('edmmetadata', '__migrationhistory', '__efmigrationshistory', '__refactorlog') AND ix.indcheckxmin = false AND ix.indisvalid = true ORDER BY t.relname, i.relname;"; } public override bool CanReadStoredProcedures() { return true; } protected override string StoredProcedureSQL() { return @" SELECT R.specific_schema AS ""SPECIFIC_SCHEMA"", R.routine_name AS ""SPECIFIC_NAME"", R.routine_type AS ""ROUTINE_TYPE"", R.data_type AS ""RETURN_DATA_TYPE"", P.ordinal_position AS ""ORDINAL_POSITION"", P.parameter_mode AS ""PARAMETER_MODE"", P.parameter_name AS ""PARAMETER_NAME"", P.data_type AS ""DATA_TYPE"", COALESCE(P.character_maximum_length, 0) AS ""CHARACTER_MAXIMUM_LENGTH"", COALESCE(P.numeric_precision, 0) AS ""NUMERIC_PRECISION"", COALESCE(P.numeric_scale, 0) AS ""NUMERIC_SCALE"", COALESCE(P.datetime_precision, 0) AS ""DATETIME_PRECISION"", CASE WHEN LOWER(P.udt_schema) <> 'pg_catalog' THEN P.udt_schema || '.' || P.udt_name ELSE P.udt_name END AS ""USER_DEFINED_TYPE"" FROM information_schema.routines R LEFT JOIN information_schema.parameters P ON R.specific_schema = P.specific_schema AND R.specific_name = P.specific_name WHERE LOWER(R.routine_schema) NOT IN ('pg_catalog', 'information_schema') AND LOWER(R.routine_type) IN ('procedure','function') ORDER BY R.specific_schema, R.routine_name, R.routine_type;"; } protected override string ReadDatabaseEditionSQL() { return @"SELECT version() as ""Edition"", '' as ""EngineEdition"", '' as ""ProductVersion"";"; } protected override string MultiContextSQL() { return string.Empty; } protected override string EnumSQL(string table, string nameField, string valueField, string groupField) { return string.Format(@"SELECT ""{0}"" as ""NameField"", ""{1}"" as ""ValueField"", ""{2}"" as ""GroupField"", * FROM ""{3}"";", nameField, valueField, !string.IsNullOrEmpty(groupField) ? groupField : string.Empty, table); } protected override string SequenceSQL() { return string.Empty; } protected override string TriggerSQL() { return @" SELECT DISTINCT event_object_schema AS SchemaName, event_object_table AS TableName, trigger_name AS TriggerName FROM INFORMATION_SCHEMA.triggers ORDER BY SchemaName, TableName, TriggerName;"; } protected override string[] MemoryOptimisedSQL() { return null; } protected override string SynonymTableSQLSetup() { return string.Empty; } protected override string SynonymTableSQL() { return string.Empty; } protected override string SynonymForeignKeySQLSetup() { return string.Empty; } protected override string SynonymForeignKeySQL() { return string.Empty; } protected override string SynonymStoredProcedureSQLSetup() { return string.Empty; } protected override string SynonymStoredProcedureSQL() { return string.Empty; } protected override string DefaultSchema(DbConnection conn) { return "public"; } protected override string SpecialQueryFlags() { return string.Empty; } protected override bool HasTemporalTableSupport() { return false; } public override bool HasIdentityColumnSupport() { return true; } public override void ReadStoredProcReturnObjects(List procs) { using (var conn = _factory.CreateConnection()) { if (conn == null) return; conn.ConnectionString = Settings.ConnectionString; conn.Open(); var cmd = GetCmd(conn); if (cmd == null) return; // Only functions return result sets in PostgreSQL foreach (var sp in procs.Where(x => !x.IsStoredProcedure && !x.IsScalarValuedFunction)) ReadFunctionReturnObject(cmd, sp); // Tidy up cmd.CommandText = "DROP TABLE IF EXISTS efrpg_temp_table;"; if (cmd.Connection.State != ConnectionState.Open) cmd.Connection.Open(); cmd.ExecuteNonQuery(); cmd.Connection.Close(); } } private void ReadFunctionReturnObject(DbCommand cmd, StoredProcedure proc) { try { const string structured = "Structured"; var sb = new StringBuilder(255); sb.AppendLine("DO $$"); foreach (var param in proc.Parameters.Where(x => x.SqlDbType.Equals(structured, StringComparison.InvariantCultureIgnoreCase))) { sb.AppendLine(string.Format("DECLARE {0} {1};", param.Name, param.UserDefinedTypeName)); } sb.AppendLine("BEGIN"); sb.AppendLine(" DROP TABLE IF EXISTS efrpg_temp_table;"); sb.AppendLine(" CREATE TEMPORARY TABLE efrpg_temp_table AS"); sb.Append(string.Format(" SELECT * FROM \"{0}\".\"{1}\"(", proc.Schema.DbName, proc.DbName)); foreach (var param in proc.Parameters) { sb.Append(string.Format("{0}, ", param.SqlDbType.Equals(structured, StringComparison.InvariantCultureIgnoreCase) ? param.Name : "default")); } if (proc.Parameters.Count > 0) sb.Length -= 2; sb.AppendLine(");"); sb.AppendLine("END $$;"); sb.AppendLine("SELECT * FROM efrpg_temp_table;"); var ds = new DataSet(); using (var sqlAdapter = _factory.CreateDataAdapter()) { if (sqlAdapter == null) return; try { cmd.CommandText = sb.ToString(); sqlAdapter.SelectCommand = cmd; if(cmd.Connection.State != ConnectionState.Open) cmd.Connection.Open(); sqlAdapter.SelectCommand.ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo); cmd.Connection.Close(); sqlAdapter.FillSchema(ds, SchemaType.Source, "MyTable"); } catch { // ignored } } // Tidy up parameters foreach (var p in proc.Parameters) p.NameHumanCase = Regex.Replace(p.NameHumanCase, @"[^A-Za-z0-9@\s]*", string.Empty); for (var count = 0; count < ds.Tables.Count; count++) { proc.ReturnModels.Add(ds.Tables[count].Columns.Cast().ToList()); } proc.MergeModelsIfAllSame(); } catch (Exception) { // Function does not have a return type } } public override void Init() { base.Init(); } } public class RawExtendedProperty { public readonly string SchemaName; public readonly string TableName; public readonly string ColumnName; public readonly string ExtendedProperty; public readonly bool TableLevelExtendedComment; public RawExtendedProperty(string schemaName, string tableName, string columnName, string extendedProperty) { SchemaName = schemaName; TableName = tableName; ColumnName = columnName; ExtendedProperty = extendedProperty; TableLevelExtendedComment = string.IsNullOrEmpty(columnName); } } public class RawForeignKey { public readonly string ConstraintName; public readonly string ParentName; public readonly string ChildName; public readonly string PkColumn; public readonly string FkColumn; public readonly string PkSchema; public readonly string PkTableName; public readonly string FkSchema; public readonly string FkTableName; public readonly int Ordinal; public readonly bool CascadeOnDelete; public readonly bool IsNotEnforced; public bool HasUniqueConstraint; // Can also be changed later //public byte SortOrder; public RawForeignKey( string constraintName, string parentName, string childName, string pkColumn, string fkColumn, string pkSchema, string pkTableName, string fkSchema, string fkTableName, int ordinal, bool cascadeOnDelete, bool isNotEnforced, bool hasUniqueConstraint) { ConstraintName = constraintName; ParentName = parentName; ChildName = childName; PkColumn = pkColumn; FkColumn = fkColumn; PkSchema = pkSchema; PkTableName = pkTableName; FkSchema = fkSchema; FkTableName = fkTableName; Ordinal = ordinal; CascadeOnDelete = cascadeOnDelete; IsNotEnforced = isNotEnforced; HasUniqueConstraint = hasUniqueConstraint; } public string Dump() { var parentName = "null"; if (!string.IsNullOrWhiteSpace(ParentName)) parentName = string.Format("\"{0}\"", ParentName); var childName = "null"; if (!string.IsNullOrWhiteSpace(ChildName)) childName = string.Format("\"{0}\"", ChildName); return string.Format("new RawForeignKey(\"{0}\", {1}, {2}, \"{3}\", \"{4}\", \"{5}\", \"{6}\", \"{7}\", \"{8}\", {9}, {10}, {11}, {12}),", ConstraintName, parentName, childName, PkColumn, FkColumn, PkSchema, PkTableName, FkSchema, FkTableName, Ordinal, CascadeOnDelete ? "true" : "false", IsNotEnforced ? "true" : "false", HasUniqueConstraint ? "true" : "false"); } } public class RawIndex { public readonly string Schema; public readonly string TableName; public readonly string IndexName; public readonly byte KeyOrdinal; public readonly string ColumnName; public readonly int ColumnCount; public readonly bool IsUnique; public readonly bool IsPrimaryKey; public readonly bool IsUniqueConstraint; public readonly bool IsClustered; public RawIndex(string schema, string tableName, string indexName, byte keyOrdinal, string columnName, int columnCount, bool isUnique, bool isPrimaryKey, bool isUniqueConstraint, bool isClustered) { Schema = schema; TableName = tableName; IndexName = indexName; KeyOrdinal = keyOrdinal; ColumnName = columnName; ColumnCount = columnCount; IsUnique = isUnique; IsPrimaryKey = isPrimaryKey; IsUniqueConstraint = isUniqueConstraint; IsClustered = isClustered; } public string Dump() { return string.Format("new RawIndex(\"{0}\", \"{1}\", \"{2}\", {3}, \"{4}\", {5}, {6}, {7}, {8}, {9}),", Schema, TableName, IndexName, KeyOrdinal, ColumnName, ColumnCount, IsUnique ? "true" : "false", IsPrimaryKey ? "true" : "false", IsUniqueConstraint ? "true" : "false", IsClustered ? "true" : "false"); } } public class RawMemoryOptimisedTable { public readonly string SchemaName; public readonly string TableName; public RawMemoryOptimisedTable(string schemaName, string tableName) { SchemaName = schemaName; TableName = tableName; } } public class RawSequence { public readonly string Schema; public readonly string Name; public readonly string DataType; public readonly string StartValue; public readonly string IncrementValue; public readonly string MinValue; public readonly string MaxValue; public readonly string IsCycleEnabled; public readonly bool hasMinValue; public readonly bool hasMaxValue; public List TableMapping; public RawSequence(string schema, string name, string dataType, string startValue, string incrementValue, string minValue, string maxValue, bool isCycleEnabled) { Schema = schema; Name = name; DataType = dataType; StartValue = startValue; IncrementValue = incrementValue; MinValue = minValue; MaxValue = maxValue; IsCycleEnabled = isCycleEnabled ? "true" : "false"; TableMapping = new List(); hasMinValue = MinMaxValueCache.GetMinValue(dataType) != minValue; hasMaxValue = MinMaxValueCache.GetMaxValue(dataType) != maxValue; } } public class RawSequenceTableMapping { public readonly string TableSchema; public readonly string TableName; public RawSequenceTableMapping(string tableSchema, string tableName) { TableSchema = tableSchema; TableName = tableName; } } public class RawStoredProcedure { public readonly string Schema; public readonly string Name; public readonly bool IsTableValuedFunction; public readonly bool IsScalarValuedFunction; public readonly bool IsStoredProcedure; public readonly StoredProcedureParameter Parameter; public RawStoredProcedure( string schema, string name, bool isTableValuedFunction, bool isScalarValuedFunction, bool isStoredProcedure, StoredProcedureParameter parameter) { Schema = schema; Name = name; IsTableValuedFunction = isTableValuedFunction; IsScalarValuedFunction = isScalarValuedFunction; IsStoredProcedure = isStoredProcedure; Parameter = parameter; } } public class RawTable { // Table public readonly string SchemaName; public readonly string TableName; public readonly bool IsView; public readonly bool IsSynonym; // Column public readonly int Scale; public readonly string TypeName; public readonly bool IsNullable; public readonly int MaxLength; public readonly int DateTimePrecision; public readonly int Precision; public readonly bool IsIdentity; public readonly bool IsComputed; public readonly bool IsRowGuid; public readonly byte GeneratedAlwaysType; public readonly bool IsStoreGenerated; public readonly int PrimaryKeyOrdinal; public readonly bool PrimaryKey; public readonly bool IsForeignKey; public readonly string SynonymTriggerName; public readonly int Ordinal; public readonly string ColumnName; public readonly string Default; public RawTable(string schemaName, string tableName, bool isView, bool isSynonym, int scale, string typeName, bool isNullable, int maxLength, int dateTimePrecision, int precision, bool isIdentity, bool isComputed, bool isRowGuid, byte generatedAlwaysType, bool isStoreGenerated, int primaryKeyOrdinal, bool primaryKey, bool isForeignKey, string synonymTriggerName, int ordinal, string columnName, string @default) { // Table SchemaName = schemaName; TableName = tableName; IsView = isView; IsSynonym = isSynonym; SynonymTriggerName = synonymTriggerName; // Column Scale = scale; TypeName = typeName; IsNullable = isNullable; MaxLength = maxLength; DateTimePrecision = dateTimePrecision; Precision = precision; IsIdentity = isIdentity; IsComputed = isComputed; IsRowGuid = isRowGuid; GeneratedAlwaysType = generatedAlwaysType; IsStoreGenerated = isStoreGenerated; PrimaryKeyOrdinal = primaryKeyOrdinal; PrimaryKey = primaryKey; IsForeignKey = isForeignKey; Ordinal = ordinal; ColumnName = columnName; Default = @default; } } public class RawTrigger { public readonly string SchemaName; public readonly string TableName; public readonly string TriggerName; public RawTrigger(string schemaName, string tableName, string triggerName) { SchemaName = schemaName; TableName = tableName; TriggerName = triggerName; } } public class SqLiteDatabaseReader : DatabaseReader { public SqLiteDatabaseReader(DbProviderFactory factory, IDatabaseToPropertyType databaseToPropertyType) : base(factory, databaseToPropertyType) { StoredProcedureParameterDbType = new Dictionary { { string.Empty, "TEXT" }, // default { "byte", "INTEGER" }, { "ByteEnum", "INTEGER" }, { "DateTime", "TEXT" }, { "DateTimeOffset", "TEXT" }, { "decimal", "TEXT" }, { "double", "REAL" }, { "float", "REAL" }, { "Guid", "TEXT" }, { "int", "INTEGER" }, { "IntEnum", "INTEGER" }, { "long", "INTEGER" }, { "LongEnum", "INTEGER" }, { "sbyte", "INTEGER" }, { "SByteEnum", "INTEGER" }, { "short", "INTEGER" }, { "ShortEnum", "INTEGER" }, { "string", "TEXT" }, { "TimeSpan", "TEXT" }, { "uint", "INTEGER" }, { "UIntEnum", "INTEGER" }, { "ulong", "INTEGER" }, { "ULongEnum", "INTEGER" }, { "ushort", "INTEGER" }, { "UShortEnum", "INTEGER" } }; } protected override string TableSQL() { return @" SELECT 'main' AS SchemaName, TableName, TableType, 0 AS TableTemporalType, Ordinal, ColumnName, IsNullable, REPLACE(type, '(' || length || ')', '') AS TypeName, length AS [MaxLength], Precision, [Default], 0 AS DateTimePrecision, Scale, pk AS IsIdentity, 0 AS IsRowGuid, 0 AS IsComputed, 0 AS GeneratedAlwaysType, 0 AS IsStoreGenerated, pk AS PrimaryKey, pk AS PrimaryKeyOrdinal, 0 AS IsForeignKey, NULL AS SynonymTriggerName FROM (SELECT m.tbl_name AS TableName, CASE WHEN m.type = 'table' THEN 'BASE TABLE' ELSE 'VIEW' END AS TableType, c.cid AS Ordinal, c.name AS ColumnName, CASE WHEN INSTR(c.type, '(') > 0 THEN SUBSTR(LOWER(c.type), 0, INSTR(c.type, '(')) ELSE LOWER(c.type) END AS type, 1 - c.""notnull"" AS IsNullable, c.dflt_value AS [Default], c.pk AS pk, CAST(SUBSTR(c.type, INSTR(c.type, '(') + 1, INSTR(c.type, ')') - INSTR(c.type, '(') - 1) AS INTEGER) AS length, CAST(SUBSTR(c.type, INSTR(c.type, '(') + 1, INSTR(c.type, ',') - INSTR(c.type, '(') - 1) AS INTEGER) AS Precision, CAST(SUBSTR(c.type, INSTR(c.type, ',') + 1, INSTR(c.type, ')') - INSTR(c.type, ',') - 1) AS INTEGER) AS Scale FROM sqlite_master m JOIN PRAGMA_TABLE_INFO(m.name) c WHERE m.type IN ('table', 'view') AND m.name NOT LIKE 'sqlite_%') ORDER BY TableName, Ordinal;"; } protected override string ForeignKeySQL() { return @" SELECT m.name AS FK_Table, p.[from] AS FK_Column, p.[table] AS PK_Table, p.[to] AS PK_Column, 'FK_' || TRIM(m.name) || '_' || TRIM(p.[from]) || '_' || TRIM(p.[to]) as Constraint_Name, 'main' as fkSchema, 'main' as pkSchema, p.[to] AS primarykey, p.id + 1 as ORDINAL_POSITION, case when P.on_delete is 'CASCADE' then 1 else 0 end as CascadeOnDelete, 0 as IsNotEnforced FROM sqlite_master m JOIN PRAGMA_FOREIGN_KEY_LIST(m.name) p ON m.name != p.[table] WHERE m.type = 'table' ORDER BY m.name;"; } protected override string ExtendedPropertySQL() { return string.Empty; } protected override string DoesExtendedPropertyTableExistSQL() { return string.Empty; } protected override string IndexSQL() { return @" WITH split(TableName, IndexName, KeyOrdinal, ColumnName, csv) AS ( SELECT TableName, IndexName, KeyOrdinal, '', column_name || ',' FROM (SELECT TableName, IndexName, 0 AS KeyOrdinal, SUBSTR(sql, INSTR(sql, '(') + 1, INSTR(sql, ')') - INSTR(sql, '(') - 1) AS column_name FROM (SELECT tbl_name AS TableName, name AS IndexName, REPLACE(REPLACE(sql, ']', ''), '[', '') AS sql FROM sqlite_master WHERE type = 'index' AND name NOT LIKE 'sqlite_%')) UNION ALL SELECT TableName, IndexName, KeyOrdinal + 1, SUBSTR(csv, 0, INSTR(csv, ',')), SUBSTR(csv, INSTR(csv, ',') + 1) FROM split WHERE csv != '') SELECT 'main' AS TableSchema, s.TableName, s.IndexName, s.KeyOrdinal, TRIM(s.ColumnName) as ColumnName, (SELECT MAX(KeyOrdinal) FROM split x WHERE x.TableName = s.TableName AND x.IndexName = s.IndexName) AS ColumnCount, 0 AS IsUnique, 0 AS IsPrimaryKey, 0 AS IsUniqueConstraint, 0 as IsClustered FROM split s WHERE ColumnName != ''"; } public override bool CanReadStoredProcedures() { return false; } protected override string StoredProcedureSQL() { return string.Empty; } protected override string MultiContextSQL() { return string.Empty; } protected override string EnumSQL(string table, string nameField, string valueField, string groupField) { return string.Format("SELECT {0} as NameField, {1} as ValueField, {2} as GroupField, * FROM {3};", nameField, valueField, !string.IsNullOrEmpty(groupField) ? groupField : "''", table); } protected override string SequenceSQL() { return string.Empty; } protected override string TriggerSQL() { return @" SELECT 'main' AS SchemaName, tbl_name AS TableName, name AS TriggerName FROM sqlite_master WHERE type = 'trigger'"; } protected override string[] MemoryOptimisedSQL() { return null; } protected override string SynonymTableSQLSetup() { return string.Empty; } protected override string SynonymTableSQL() { return string.Empty; } protected override string SynonymForeignKeySQLSetup() { return string.Empty; } protected override string SynonymForeignKeySQL() { return string.Empty; } protected override string SynonymStoredProcedureSQLSetup() { return string.Empty; } protected override string SynonymStoredProcedureSQL() { return string.Empty; } protected override string DefaultSchema(DbConnection conn) { return "main"; } protected override string SpecialQueryFlags() { return string.Empty; } protected override bool HasTemporalTableSupport() { return false; } public override bool HasIdentityColumnSupport() { return false; } protected override string ReadDatabaseEditionSQL() { return "SELECT 'SQLite' AS Edition, '' AS EngineEdition, '' AS ProductVersion;"; } public override void ReadStoredProcReturnObjects(List procs) { throw new NotImplementedException(); } public override void Init() { base.Init(); Settings.PrependSchemaName = false; IncludeSchema = false; DoNotSpecifySizeForMaxLength = true; } } public class SqlServerCeDatabaseReader : DatabaseReader { public SqlServerCeDatabaseReader(DbProviderFactory factory, IDatabaseToPropertyType databaseToPropertyType) : base(factory, databaseToPropertyType) { StoredProcedureParameterDbType = new Dictionary { { string.Empty, "VarChar" }, // default { "hierarchyid", "VarChar" }, { "bigint", "BigInt" }, { "binary", "Binary" }, { "bit", "Bit" }, { "char", "Char" }, { "datetime", "DateTime" }, { "decimal", "Decimal" }, { "numeric", "Decimal" }, { "float", "Float" }, { "image", "Image" }, { "int", "Int" }, { "money", "Money" }, { "nchar", "NChar" }, { "ntext", "NText" }, { "nvarchar", "NVarChar" }, { "real", "Real" }, { "uniqueidentifier", "UniqueIdentifier" }, { "smalldatetime", "SmallDateTime" }, { "smallint", "SmallInt" }, { "smallmoney", "SmallMoney" }, { "text", "Text" }, { "timestamp", "Timestamp" }, { "tinyint", "TinyInt" }, { "varbinary", "VarBinary" }, { "varchar", "VarChar" }, { "variant", "Variant" }, { "xml", "Xml" }, { "udt", "Udt" }, { "table type", "Structured" }, { "structured", "Structured" }, { "date", "Date" }, { "time", "Time" }, { "datetime2", "DateTime2" }, { "datetimeoffset", "DateTimeOffset" } }; } protected override string TableSQL() { return @" SELECT '' AS SchemaName, c.TABLE_NAME AS TableName, 'BASE TABLE' AS TableType, CONVERT( tinyint, 0 ) AS TableTemporalType, c.ORDINAL_POSITION AS Ordinal, c.COLUMN_NAME AS ColumnName, CAST(CASE WHEN c.IS_NULLABLE = N'YES' THEN 1 ELSE 0 END AS BIT) AS IsNullable, CASE WHEN c.DATA_TYPE = N'rowversion' THEN 'timestamp' ELSE c.DATA_TYPE END AS TypeName, CASE WHEN c.CHARACTER_MAXIMUM_LENGTH IS NOT NULL THEN c.CHARACTER_MAXIMUM_LENGTH ELSE 0 END AS MaxLength, CASE WHEN c.NUMERIC_PRECISION IS NOT NULL THEN c.NUMERIC_PRECISION ELSE 0 END AS Precision, c.COLUMN_DEFAULT AS [Default], CASE WHEN c.DATA_TYPE = N'datetime' THEN 0 ELSE 0 END AS DateTimePrecision, CASE WHEN c.DATA_TYPE = N'datetime' THEN 0 WHEN c.NUMERIC_SCALE IS NOT NULL THEN c.NUMERIC_SCALE ELSE 0 END AS Scale, CAST(CASE WHEN c.AUTOINC_INCREMENT > 0 THEN 1 ELSE 0 END AS BIT) AS IsIdentity, CONVERT( bit, 0 ) as IsComputed, CONVERT( bit, 0 ) as IsRowGuid, CONVERT( tinyint, 0 ) AS GeneratedAlwaysType, CAST(CASE WHEN c.DATA_TYPE = N'rowversion' THEN 1 ELSE 0 END AS BIT) AS IsStoreGenerated, 0 AS PrimaryKeyOrdinal, CAST(CASE WHEN u.TABLE_NAME IS NULL THEN 0 ELSE 1 END AS BIT) AS PrimaryKey, CONVERT( bit, 0 ) as IsForeignKey, NULL as SynonymTriggerName FROM INFORMATION_SCHEMA.COLUMNS c INNER JOIN INFORMATION_SCHEMA.TABLES t ON c.TABLE_NAME = t.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS cons ON cons.TABLE_NAME = c.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS u ON cons.CONSTRAINT_NAME = u.CONSTRAINT_NAME AND u.TABLE_NAME = c.TABLE_NAME AND u.COLUMN_NAME = c.COLUMN_NAME WHERE t.TABLE_TYPE <> N'SYSTEM TABLE' AND cons.CONSTRAINT_TYPE = 'PRIMARY KEY' ORDER BY c.TABLE_NAME, c.COLUMN_NAME, c.ORDINAL_POSITION"; } protected override string ForeignKeySQL() { return @" SELECT DISTINCT FK.TABLE_NAME AS FK_Table, FK.COLUMN_NAME AS FK_Column, PK.TABLE_NAME AS PK_Table, PK.COLUMN_NAME AS PK_Column, FK.CONSTRAINT_NAME AS Constraint_Name, '' AS fkSchema, '' AS pkSchema, PT.COLUMN_NAME AS primarykey, FK.ORDINAL_POSITION, CASE WHEN C.DELETE_RULE = 'CASCADE' THEN 1 ELSE 0 END AS CascadeOnDelete, CAST(0 AS BIT) AS IsNotEnforced FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS C INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS FK ON FK.CONSTRAINT_NAME = C.CONSTRAINT_NAME INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS PK ON PK.CONSTRAINT_NAME = C.UNIQUE_CONSTRAINT_NAME AND PK.ORDINAL_POSITION = FK.ORDINAL_POSITION INNER JOIN ( SELECT i1.TABLE_NAME, i2.COLUMN_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1 INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY' ) PT ON PT.TABLE_NAME = PK.TABLE_NAME WHERE PT.COLUMN_NAME = PK.COLUMN_NAME ORDER BY FK.TABLE_NAME, FK.COLUMN_NAME"; } protected override string ExtendedPropertySQL() { return @" SELECT '' AS [schema], [ObjectName] AS [column], [ParentName] AS [table], [Value] AS [property] FROM [__ExtendedProperties]"; } protected override string DoesExtendedPropertyTableExistSQL() { return @" SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '__ExtendedProperties'"; } protected override string IndexSQL() { return string.Empty; } public override bool CanReadStoredProcedures() { return false; } protected override string StoredProcedureSQL() { return string.Empty; } protected override string ReadDatabaseEditionSQL() { return string.Empty; } protected override string MultiContextSQL() { // You can add extra fields to these tables and they will be read in and stored in a Dictionary() for you to access and process. // Therefore, using an * for the fields as we want to read in all the fields. return @" SELECT * FROM MultiContext.Context; SELECT * FROM MultiContext.[Table]; SELECT * FROM MultiContext.[Column]; SELECT * FROM MultiContext.StoredProcedure; SELECT * FROM MultiContext.[Function]; SELECT * FROM MultiContext.Enumeration; SELECT * FROM MultiContext.ForeignKey;"; } protected override string EnumSQL(string table, string nameField, string valueField, string groupField) { return string.Format("SELECT {0} as NameField, {1} as ValueField, {2} as GroupField, * FROM {3};", nameField, valueField, (!string.IsNullOrEmpty(groupField) ? groupField : "''"), table); } protected override string SequenceSQL() { return string.Empty; } protected override string TriggerSQL() { return string.Empty; } protected override string[] MemoryOptimisedSQL() { return null; } protected override string SynonymTableSQLSetup() { return string.Empty; } protected override string SynonymTableSQL() { return string.Empty; } protected override string SynonymForeignKeySQLSetup() { return string.Empty; } protected override string SynonymForeignKeySQL() { return string.Empty; } protected override string SynonymStoredProcedureSQLSetup() { return string.Empty; } protected override string SynonymStoredProcedureSQL() { return string.Empty; } protected override string DefaultSchema(DbConnection conn) { return "dbo"; } protected override string SpecialQueryFlags() { return string.Empty; } protected override bool HasTemporalTableSupport() { return false; } public override bool HasIdentityColumnSupport() { return true; } public override void ReadStoredProcReturnObjects(List procs) { throw new System.NotImplementedException(); } public override void Init() { base.Init(); Settings.PrependSchemaName = false; IncludeSchema = false; DoNotSpecifySizeForMaxLength = true; } } // Used for both SQL Server and SQL Azure public class SqlServerDatabaseReader : DatabaseReader { public SqlServerDatabaseReader(DbProviderFactory factory, IDatabaseToPropertyType databaseToPropertyType) : base(factory, databaseToPropertyType) { StoredProcedureParameterDbType = new Dictionary { { string.Empty, "VarChar" }, // default { "hierarchyid", "VarChar" }, { "bigint", "BigInt" }, { "binary", "Binary" }, { "bit", "Bit" }, { "char", "Char" }, { "datetime", "DateTime" }, { "decimal", "Decimal" }, { "numeric", "Decimal" }, { "float", "Float" }, { "image", "Image" }, { "int", "Int" }, { "money", "Money" }, { "nchar", "NChar" }, { "ntext", "NText" }, { "nvarchar", "NVarChar" }, { "real", "Real" }, { "uniqueidentifier", "UniqueIdentifier" }, { "smalldatetime", "SmallDateTime" }, { "smallint", "SmallInt" }, { "smallmoney", "SmallMoney" }, { "text", "Text" }, { "timestamp", "Timestamp" }, { "tinyint", "TinyInt" }, { "varbinary", "VarBinary" }, { "varchar", "VarChar" }, { "variant", "Variant" }, { "xml", "Xml" }, { "udt", "Udt" }, { "table type", "Structured" }, { "structured", "Structured" }, { "date", "Date" }, { "time", "Time" }, { "datetime2", "DateTime2" }, { "datetimeoffset", "DateTimeOffset" } }; } protected override string TableSQL() { return @" SET NOCOUNT ON; IF OBJECT_ID('tempdb..#Columns') IS NOT NULL DROP TABLE #Columns; IF OBJECT_ID('tempdb..#PrimaryKeys') IS NOT NULL DROP TABLE #PrimaryKeys; IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL DROP TABLE #ForeignKeys; SELECT c.TABLE_SCHEMA, c.TABLE_NAME, c.COLUMN_NAME, c.ORDINAL_POSITION, c.COLUMN_DEFAULT, sc.IS_NULLABLE, c.DATA_TYPE, c.CHARACTER_MAXIMUM_LENGTH, c.NUMERIC_PRECISION, c.NUMERIC_SCALE, c.DATETIME_PRECISION, ss.schema_id, st.object_id AS table_object_id, sv.object_id AS view_object_id, sc.is_identity, sc.is_rowguidcol, sc.is_computed, -- Computed columns are read-only, do not confuse it with a column with a DEFAULT expression (which can be re-assigned). See the IsStoreGenerated attribute. CONVERT( tinyint, [sc].[generated_always_type] ) AS generated_always_type -- SQL Server 2016 (13.x) or later. 0 = Not generated, 1 = AS_ROW_START, 2 = AS_ROW_END INTO #Columns FROM INFORMATION_SCHEMA.COLUMNS c INNER JOIN sys.schemas AS ss ON c.TABLE_SCHEMA = ss.[name] LEFT OUTER JOIN sys.tables AS st ON st.schema_id = ss.schema_id AND st.[name] = c.TABLE_NAME LEFT OUTER JOIN sys.views AS sv ON sv.schema_id = ss.schema_id AND sv.[name] = c.TABLE_NAME INNER JOIN sys.all_columns AS sc ON sc.object_id = COALESCE( st.object_id, sv.object_id ) AND c.COLUMN_NAME = sc.[name] WHERE c.TABLE_NAME NOT IN ('EdmMetadata', '__MigrationHistory', '__EFMigrationsHistory', '__RefactorLog', 'sysdiagrams') CREATE NONCLUSTERED INDEX IX_EfPoco_Columns ON dbo.#Columns (TABLE_NAME) INCLUDE ( TABLE_SCHEMA,COLUMN_NAME,ORDINAL_POSITION,COLUMN_DEFAULT,IS_NULLABLE,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,NUMERIC_PRECISION,NUMERIC_SCALE,DATETIME_PRECISION, schema_id, table_object_id, view_object_id, is_identity,is_rowguidcol,is_computed,generated_always_type ); ----------- SELECT u.TABLE_SCHEMA, u.TABLE_NAME, u.COLUMN_NAME, u.ORDINAL_POSITION INTO #PrimaryKeys FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE u INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ON u.TABLE_SCHEMA COLLATE SQL_Latin1_General_CP1_CI_AS = tc.CONSTRAINT_SCHEMA COLLATE SQL_Latin1_General_CP1_CI_AS AND u.TABLE_NAME = tc.TABLE_NAME AND u.CONSTRAINT_NAME = tc.CONSTRAINT_NAME WHERE CONSTRAINT_TYPE = 'PRIMARY KEY'; SELECT DISTINCT u.TABLE_SCHEMA, u.TABLE_NAME, u.COLUMN_NAME INTO #ForeignKeys FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE u INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ON u.TABLE_SCHEMA COLLATE SQL_Latin1_General_CP1_CI_AS = tc.CONSTRAINT_SCHEMA COLLATE SQL_Latin1_General_CP1_CI_AS AND u.TABLE_NAME = tc.TABLE_NAME AND u.CONSTRAINT_NAME = tc.CONSTRAINT_NAME WHERE CONSTRAINT_TYPE = 'FOREIGN KEY'; -------------------------- SELECT c.TABLE_SCHEMA AS SchemaName, c.TABLE_NAME AS TableName, t.TABLE_TYPE AS TableType, CONVERT( tinyint, ISNULL( tt.temporal_type, 0 ) ) AS TableTemporalType, c.ORDINAL_POSITION AS Ordinal, c.COLUMN_NAME AS ColumnName, c.IS_NULLABLE AS IsNullable, DATA_TYPE AS TypeName, ISNULL(CHARACTER_MAXIMUM_LENGTH, 0) AS [MaxLength], CAST(ISNULL(NUMERIC_PRECISION, 0) AS INT) AS [Precision], ISNULL(COLUMN_DEFAULT, '') AS [Default], CAST(ISNULL(DATETIME_PRECISION, 0) AS INT) AS DateTimePrecision, ISNULL(NUMERIC_SCALE, 0) AS Scale, c.is_identity AS IsIdentity, c.is_rowguidcol AS IsRowGuid, c.is_computed AS IsComputed, c.generated_always_type AS GeneratedAlwaysType, CONVERT( bit, CASE WHEN c.is_identity = 1 OR c.is_rowguidcol = 1 OR c.is_computed = 1 OR c.generated_always_type <> 0 OR c.DATA_TYPE IN ( 'rowversion', 'timestamp' ) OR ( c.DATA_TYPE = 'uniqueidentifier' AND c.COLUMN_DEFAULT LIKE '%newsequentialid%' ) THEN 1 ELSE 0 END ) AS IsStoreGenerated, CONVERT( bit, ISNULL( pk.ORDINAL_POSITION, 0 ) ) AS PrimaryKey, ISNULL(pk.ORDINAL_POSITION, 0) PrimaryKeyOrdinal, CONVERT( bit, CASE WHEN fk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END ) AS IsForeignKey, NULL AS SynonymTriggerName FROM #Columns c LEFT OUTER JOIN #PrimaryKeys pk ON c.TABLE_SCHEMA = pk.TABLE_SCHEMA AND c.TABLE_NAME = pk.TABLE_NAME AND c.COLUMN_NAME = pk.COLUMN_NAME LEFT OUTER JOIN #ForeignKeys fk ON c.TABLE_SCHEMA = fk.TABLE_SCHEMA AND c.TABLE_NAME = fk.TABLE_NAME AND c.COLUMN_NAME = fk.COLUMN_NAME INNER JOIN INFORMATION_SCHEMA.TABLES t ON c.TABLE_SCHEMA COLLATE SQL_Latin1_General_CP1_CI_AS = t.TABLE_SCHEMA COLLATE SQL_Latin1_General_CP1_CI_AS AND c.TABLE_NAME COLLATE SQL_Latin1_General_CP1_CI_AS = t.TABLE_NAME COLLATE SQL_Latin1_General_CP1_CI_AS LEFT OUTER JOIN ( SELECT st.object_id, [st].[temporal_type] AS temporal_type FROM sys.tables AS st ) AS tt ON c.table_object_id = tt.object_id "; } protected override string ForeignKeySQL() { return @" SELECT fkData.FK_Table, fkData.FK_Column, fkData.PK_Table, fkData.PK_Column, fkData.Constraint_Name, fkData.fkSchema, fkData.pkSchema, fkData.primarykey, fkData.ORDINAL_POSITION, fkData.CascadeOnDelete, fkData.IsNotEnforced FROM (SELECT FK.name AS FK_Table, FkCol.name AS FK_Column, PK.name AS PK_Table, PkCol.name AS PK_Column, OBJECT_NAME(f.object_id) AS Constraint_Name, SCHEMA_NAME(FK.schema_id) AS fkSchema, SCHEMA_NAME(PK.schema_id) AS pkSchema, PkCol.name AS primarykey, k.constraint_column_id AS ORDINAL_POSITION, CASE WHEN f.delete_referential_action = 1 THEN 1 ELSE 0 END AS CascadeOnDelete, f.is_disabled AS IsNotEnforced, ROW_NUMBER() OVER (PARTITION BY FK.name, FkCol.name, PK.name, PkCol.name, SCHEMA_NAME(FK.schema_id), SCHEMA_NAME(PK.schema_id) ORDER BY f.object_id) AS n FROM sys.objects AS PK INNER JOIN sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS k ON k.constraint_object_id = f.object_id INNER JOIN sys.indexes AS i ON f.referenced_object_id = i.object_id AND f.key_index_id = i.index_id ON PK.object_id = f.referenced_object_id INNER JOIN sys.objects AS FK ON f.parent_object_id = FK.object_id INNER JOIN sys.columns AS PkCol ON f.referenced_object_id = PkCol.object_id AND k.referenced_column_id = PkCol.column_id INNER JOIN sys.columns AS FkCol ON f.parent_object_id = FkCol.object_id AND k.parent_column_id = FkCol.column_id) fkData WHERE fkData.n = 1 -- Remove duplicate foreign keys"; } protected override string ExtendedPropertySQL() { if (IsAzure()) return string.Empty; return @" SELECT s.name AS [schema], t.name AS [table], c.name AS [column], value AS [property] FROM sys.extended_properties AS ep INNER JOIN sys.tables AS t ON ep.major_id = t.object_id INNER JOIN sys.schemas AS s ON s.schema_id = t.schema_id LEFT JOIN sys.columns AS c ON ep.major_id = c.object_id AND ep.minor_id = c.column_id WHERE class = 1 ORDER BY t.name"; } protected override string DoesExtendedPropertyTableExistSQL() { return string.Empty; } protected override string IndexSQL() { return @" SELECT SCHEMA_NAME(t.schema_id) AS TableSchema, t.name AS TableName, ind.name AS IndexName, ic.key_ordinal AS KeyOrdinal, col.name AS ColumnName, ind.is_unique AS IsUnique, ind.is_primary_key AS IsPrimaryKey, ind.is_unique_constraint AS IsUniqueConstraint, CASE WHEN ind.[type] = 1 AND ind.is_primary_key = 1 THEN 1 ELSE 0 END AS IsClustered, ( SELECT COUNT(1) FROM sys.index_columns i WHERE i.object_id = ind.object_id AND i.index_id = ind.index_id ) AS ColumnCount FROM sys.tables t INNER JOIN sys.indexes ind ON ind.object_id = t.object_id INNER JOIN sys.index_columns ic ON ind.object_id = ic.object_id AND ind.index_id = ic.index_id INNER JOIN sys.columns col ON ic.object_id = col.object_id AND ic.column_id = col.column_id WHERE t.is_ms_shipped = 0 AND ind.ignore_dup_key = 0 AND ic.key_ordinal > 0 AND t.name NOT LIKE 'sysdiagram%'"; } public override bool CanReadStoredProcedures() { return true; } protected override string StoredProcedureSQL() { if (IsAzure()) return @" SELECT R.SPECIFIC_SCHEMA, R.SPECIFIC_NAME, R.ROUTINE_TYPE, R.DATA_TYPE as RETURN_DATA_TYPE, P.ORDINAL_POSITION, P.PARAMETER_MODE, P.PARAMETER_NAME, P.DATA_TYPE, ISNULL(P.CHARACTER_MAXIMUM_LENGTH, 0) AS CHARACTER_MAXIMUM_LENGTH, ISNULL(P.NUMERIC_PRECISION, 0) AS NUMERIC_PRECISION, ISNULL(P.NUMERIC_SCALE, 0) AS NUMERIC_SCALE, ISNULL(P.DATETIME_PRECISION, 0) AS DATETIME_PRECISION, P.USER_DEFINED_TYPE_SCHEMA + '.' + P.USER_DEFINED_TYPE_NAME AS USER_DEFINED_TYPE FROM INFORMATION_SCHEMA.ROUTINES R LEFT OUTER JOIN INFORMATION_SCHEMA.PARAMETERS P ON P.SPECIFIC_SCHEMA = R.SPECIFIC_SCHEMA AND P.SPECIFIC_NAME = R.SPECIFIC_NAME WHERE R.ROUTINE_TYPE = 'PROCEDURE' AND ( P.IS_RESULT = 'NO' OR P.IS_RESULT IS NULL ) AND R.SPECIFIC_SCHEMA + R.SPECIFIC_NAME IN ( SELECT SCHEMA_NAME(sp.schema_id) + sp.name FROM sys.all_objects AS sp LEFT OUTER JOIN sys.all_sql_modules AS sm ON sm.object_id = sp.object_id WHERE sp.type = 'P' AND sp.is_ms_shipped = 0) UNION ALL SELECT R.SPECIFIC_SCHEMA, R.SPECIFIC_NAME, R.ROUTINE_TYPE, R.DATA_TYPE as RETURN_DATA_TYPE, P.ORDINAL_POSITION, P.PARAMETER_MODE, P.PARAMETER_NAME, P.DATA_TYPE, ISNULL(P.CHARACTER_MAXIMUM_LENGTH, 0) AS CHARACTER_MAXIMUM_LENGTH, ISNULL(P.NUMERIC_PRECISION, 0) AS NUMERIC_PRECISION, ISNULL(P.NUMERIC_SCALE, 0) AS NUMERIC_SCALE, ISNULL(P.DATETIME_PRECISION, 0) AS DATETIME_PRECISION, P.USER_DEFINED_TYPE_SCHEMA + '.' + P.USER_DEFINED_TYPE_NAME AS USER_DEFINED_TYPE FROM INFORMATION_SCHEMA.ROUTINES R LEFT OUTER JOIN INFORMATION_SCHEMA.PARAMETERS P ON P.SPECIFIC_SCHEMA = R.SPECIFIC_SCHEMA AND P.SPECIFIC_NAME = R.SPECIFIC_NAME WHERE R.ROUTINE_TYPE = 'FUNCTION'"; return @" SELECT R.SPECIFIC_SCHEMA, R.SPECIFIC_NAME, R.ROUTINE_TYPE, R.DATA_TYPE as RETURN_DATA_TYPE, P.ORDINAL_POSITION, P.PARAMETER_MODE, P.PARAMETER_NAME, P.DATA_TYPE, ISNULL(P.CHARACTER_MAXIMUM_LENGTH, 0) AS CHARACTER_MAXIMUM_LENGTH, ISNULL(P.NUMERIC_PRECISION, 0) AS NUMERIC_PRECISION, ISNULL(P.NUMERIC_SCALE, 0) AS NUMERIC_SCALE, ISNULL(P.DATETIME_PRECISION, 0) AS DATETIME_PRECISION, P.USER_DEFINED_TYPE_SCHEMA + '.' + P.USER_DEFINED_TYPE_NAME AS USER_DEFINED_TYPE FROM INFORMATION_SCHEMA.ROUTINES R LEFT OUTER JOIN INFORMATION_SCHEMA.PARAMETERS P ON P.SPECIFIC_SCHEMA = R.SPECIFIC_SCHEMA AND P.SPECIFIC_NAME = R.SPECIFIC_NAME WHERE R.ROUTINE_TYPE = 'PROCEDURE' AND ( P.IS_RESULT = 'NO' OR P.IS_RESULT IS NULL ) AND R.SPECIFIC_SCHEMA + R.SPECIFIC_NAME IN ( SELECT SCHEMA_NAME(sp.schema_id) + sp.name FROM sys.all_objects AS sp LEFT OUTER JOIN sys.all_sql_modules AS sm ON sm.object_id = sp.object_id WHERE sp.type = 'P' AND (CAST(CASE WHEN sp.is_ms_shipped = 1 THEN 1 WHEN ( SELECT major_id FROM sys.extended_properties WHERE major_id = sp.object_id AND minor_id = 0 AND class = 1 AND name = N'microsoft_database_tools_support' ) IS NOT NULL THEN 1 ELSE 0 END AS BIT) = 0)) UNION ALL SELECT R.SPECIFIC_SCHEMA, R.SPECIFIC_NAME, R.ROUTINE_TYPE, R.DATA_TYPE as RETURN_DATA_TYPE, P.ORDINAL_POSITION, P.PARAMETER_MODE, P.PARAMETER_NAME, P.DATA_TYPE, ISNULL(P.CHARACTER_MAXIMUM_LENGTH, 0) AS CHARACTER_MAXIMUM_LENGTH, ISNULL(P.NUMERIC_PRECISION, 0) AS NUMERIC_PRECISION, ISNULL(P.NUMERIC_SCALE, 0) AS NUMERIC_SCALE, ISNULL(P.DATETIME_PRECISION, 0) AS DATETIME_PRECISION, P.USER_DEFINED_TYPE_SCHEMA + '.' + P.USER_DEFINED_TYPE_NAME AS USER_DEFINED_TYPE FROM INFORMATION_SCHEMA.ROUTINES R LEFT OUTER JOIN INFORMATION_SCHEMA.PARAMETERS P ON P.SPECIFIC_SCHEMA = R.SPECIFIC_SCHEMA AND P.SPECIFIC_NAME = R.SPECIFIC_NAME WHERE R.ROUTINE_TYPE = 'FUNCTION'"; } protected override string MultiContextSQL() { return @" SELECT * FROM MultiContext.Context; SELECT * FROM MultiContext.[Table]; SELECT * FROM MultiContext.[Column]; SELECT * FROM MultiContext.StoredProcedure; SELECT * FROM MultiContext.[Function]; SELECT * FROM MultiContext.Enumeration; SELECT * FROM MultiContext.ForeignKey;"; } protected override string EnumSQL(string table, string nameField, string valueField, string groupField) { return string.Format("SELECT {0} as NameField, {1} as ValueField, {2} as GroupField, * FROM {3};", nameField, valueField, !string.IsNullOrEmpty(groupField) ? groupField : "''", table); } protected override string SequenceSQL() { return @" SELECT SCHEMA_NAME(seq.schema_id) [Schema], seq.name Name, usrt.name DataType, ISNULL(seq.start_value, N'') StartValue, ISNULL(seq.increment, N'') IncrementValue, ISNULL(seq.minimum_value, N'') MinValue, ISNULL(seq.maximum_value, N'') MaxValue, ISNULL(CAST(seq.is_cycling AS BIT), 0) IsCycleEnabled, seq.cache_size CacheSize, OBJECT_SCHEMA_NAME(o.parent_object_id) TableSchema, OBJECT_NAME(o.parent_object_id) TableName FROM sys.sequences seq LEFT OUTER JOIN sys.types usrt ON usrt.user_type_id = seq.user_type_id CROSS APPLY sys.dm_sql_referencing_entities(OBJECT_SCHEMA_NAME(seq.object_id) + '.' + seq.name, 'OBJECT') r JOIN sys.objects o ON o.object_id = r.referencing_id ORDER BY seq.schema_id, seq.name;"; } protected override string TriggerSQL() { return @" SELECT S.name SchemaName, O.name TableName, T.name TriggerName FROM sys.triggers T LEFT JOIN sys.all_objects O ON T.parent_id = O.object_id LEFT JOIN sys.schemas S ON S.schema_id = O.schema_id WHERE T.type = 'TR' AND T.is_disabled = 0 AND S.name IS NOT NULL AND O.name IS NOT NULL ORDER BY SchemaName, TableName, TriggerName;"; } protected override string[] MemoryOptimisedSQL() { return new string[] { "SELECT compatibility_level FROM sys.databases WHERE name = DB_NAME();", "SELECT CAST(SERVERPROPERTY(N'IsXTPSupported') AS BIT) AS IsXTPSupported;", "SELECT SCHEMA_NAME(schema_id) SchemaName, name TableName FROM sys.tables WHERE is_memory_optimized = 1;" }; } protected override string SynonymTableSQLSetup() { return @" SET NOCOUNT ON; IF OBJECT_ID('tempdb..#SynonymDetails') IS NOT NULL DROP TABLE #SynonymDetails; IF OBJECT_ID('tempdb..#SynonymTargets') IS NOT NULL DROP TABLE #SynonymTargets; -- Synonyms -- Create the #SynonymDetails temp table structure for later use SELECT TOP (0) sc.name AS SchemaName, sn.name AS TableName, 'SN' AS TableType, CONVERT( tinyint, 0 ) AS TableTemporalType, COLUMNPROPERTY(c.object_id, c.name, 'ordinal') AS Ordinal, c.name AS ColumnName, c.is_nullable AS IsNullable, ISNULL(TYPE_NAME(c.system_type_id), t.name) AS TypeName, ISNULL(COLUMNPROPERTY(c.object_id, c.name, 'charmaxlen'), 0) AS MaxLength, CAST(ISNULL(CONVERT(TINYINT, CASE WHEN c.system_type_id IN (48, 52, 56, 59, 60, 62, 106, 108, 122, 127) THEN c.precision END), 0) AS INT) AS Precision, ISNULL(CONVERT(NVARCHAR(4000), OBJECT_DEFINITION(c.default_object_id)), '') AS [Default], CAST(ISNULL(CONVERT(SMALLINT, CASE WHEN c.system_type_id IN (40, 41, 42, 43, 58, 61) THEN ODBCSCALE(c.system_type_id, c.scale) END), 0) AS INT) AS DateTimePrecision, ISNULL(CONVERT(INT, CASE WHEN c.system_type_id IN (40, 41, 42, 43, 58, 61) THEN NULL ELSE ODBCSCALE(c.system_type_id, c.scale) END), 0) AS Scale, c.is_identity AS IsIdentity, c.is_rowguidcol AS IsRowGuid, c.is_computed AS IsComputed, CONVERT( tinyint, [c].[generated_always_type] ) AS GeneratedAlwaysType, CAST(CASE WHEN COLUMNPROPERTY(OBJECT_ID(QUOTENAME(sc.NAME) + '.' + QUOTENAME(o.NAME)), c.NAME, 'IsIdentity') = 1 THEN 1 WHEN COLUMNPROPERTY(OBJECT_ID(QUOTENAME(sc.NAME) + '.' + QUOTENAME(o.NAME)), c.NAME, 'IsComputed') = 1 THEN 1 WHEN COLUMNPROPERTY(OBJECT_ID(QUOTENAME(sc.NAME) + '.' + QUOTENAME(o.NAME)), c.NAME, 'GeneratedAlwaysType') > 0 THEN 1 WHEN ISNULL(TYPE_NAME(c.system_type_id), t.NAME) = 'TIMESTAMP' THEN 1 WHEN ISNULL(TYPE_NAME(c.system_type_id), t.NAME) = 'UNIQUEIDENTIFIER' AND LOWER(ISNULL(CONVERT(NVARCHAR(4000), OBJECT_DEFINITION(c.default_object_id)), '')) LIKE '%newsequentialid%' THEN 1 ELSE 0 END AS BIT) AS IsStoreGenerated, CAST(CASE WHEN pk.ORDINAL_POSITION IS NULL THEN 0 ELSE 1 END AS BIT) AS PrimaryKey, ISNULL(pk.ORDINAL_POSITION, 0) PrimaryKeyOrdinal, CAST(CASE WHEN fk.COLUMN_NAME IS NULL THEN 0 ELSE 1 END AS BIT) AS IsForeignKey, ( SELECT TOP(1) T.name AS TriggerName FROM sys.triggers T LEFT JOIN sys.all_objects TOBJ ON T.parent_id = TOBJ.object_id LEFT JOIN sys.schemas TSCH ON TSCH.schema_id = TOBJ.schema_id WHERE T.type = 'TR' AND T.is_disabled = 0 AND TSCH.name IS NOT NULL AND TOBJ.name IS NOT NULL AND sc.NAME = TSCH.name AND sn.name = TOBJ.name) AS SynonymTriggerName INTO #SynonymDetails FROM sys.synonyms sn INNER JOIN sys.COLUMNS c ON c.[object_id] = OBJECT_ID(sn.base_object_name) INNER JOIN sys.schemas sc ON sc.[schema_id] = sn.[schema_id] LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id INNER JOIN sys.objects o ON c.[object_id] = o.[object_id] LEFT OUTER JOIN ( SELECT u.TABLE_SCHEMA, u.TABLE_NAME, u.COLUMN_NAME, u.ORDINAL_POSITION FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE u INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ON u.TABLE_SCHEMA = tc.CONSTRAINT_SCHEMA AND u.TABLE_NAME = tc.TABLE_NAME AND u.CONSTRAINT_NAME = tc.CONSTRAINT_NAME WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' ) pk ON sc.NAME = pk.TABLE_SCHEMA AND sn.name = pk.TABLE_NAME AND c.name = pk.COLUMN_NAME LEFT OUTER JOIN ( SELECT DISTINCT u.TABLE_SCHEMA, u.TABLE_NAME, u.COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE u INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ON u.TABLE_SCHEMA = tc.CONSTRAINT_SCHEMA AND u.TABLE_NAME = tc.TABLE_NAME AND u.CONSTRAINT_NAME = tc.CONSTRAINT_NAME WHERE CONSTRAINT_TYPE = 'FOREIGN KEY' ) fk ON sc.NAME = fk.TABLE_SCHEMA AND sn.name = fk.TABLE_NAME AND c.name = fk.COLUMN_NAME; DECLARE @synonymDetailsQueryTemplate nvarchar(max) = 'USE [@synonmymDatabaseName]; INSERT INTO #SynonymDetails ( SchemaName, TableName, TableType, TableTemporalType, Ordinal, ColumnName, IsNullable, TypeName, [MaxLength], [Precision], [Default], DateTimePrecision, Scale, IsIdentity, IsRowGuid, IsComputed, GeneratedAlwaysType, IsStoreGenerated, PrimaryKey, PrimaryKeyOrdinal, IsForeignKey, SynonymTriggerName ) SELECT st.SynonymSchemaName AS SchemaName, st.SynonymName AS TableName, ''SN'' AS TableType, CONVERT( tinyint, ISNULL( tt.temporal_type, 0 ) ) AS TableTemporalType, COLUMNPROPERTY(c.object_id, c.name, ''ordinal'') AS Ordinal, c.name AS ColumnName, c.is_nullable AS IsNullable, ISNULL(TYPE_NAME(c.system_type_id), t.name) AS TypeName, ISNULL(COLUMNPROPERTY(c.object_id, c.name, ''charmaxlen''), 0) AS [MaxLength], CAST(ISNULL(CONVERT(TINYINT, CASE WHEN c.system_type_id IN (48, 52, 56, 59, 60, 62, 106, 108, 122, 127) THEN c.precision END), 0) AS INT) AS [Precision], ISNULL(CONVERT(NVARCHAR(4000), OBJECT_DEFINITION(c.default_object_id)), '''') AS [Default], CAST(ISNULL(CONVERT(SMALLINT, CASE WHEN c.system_type_id IN (40, 41, 42, 43, 58, 61) THEN ODBCSCALE(c.system_type_id, c.scale) END), 0) AS INT) AS DateTimePrecision, ISNULL(CONVERT(INT, CASE WHEN c.system_type_id IN (40, 41, 42, 43, 58, 61) THEN NULL ELSE ODBCSCALE(c.system_type_id, c.scale) END), 0) AS Scale, c.is_identity AS IsIdentity, c.is_rowguidcol AS IsRowGuid, c.is_computed AS IsComputed, CONVERT( tinyint, [c].[generated_always_type] ) AS GeneratedAlwaysType, CONVERT( bit, CASE WHEN c.is_identity = 1 OR c.is_rowguidcol = 1 OR c.is_computed = 1 OR [c].[generated_always_type] <> 0 OR t.name IN ( ''rowversion'', ''timestamp'' ) OR ( t.name = ''uniqueidentifier'' AND sd.definition LIKE ''%newsequentialid%'' ) THEN 1 ELSE 0 END ) AS IsStoreGenerated, CAST(CASE WHEN pk.ORDINAL_POSITION IS NULL THEN 0 ELSE 1 END AS BIT) AS PrimaryKey, ISNULL(pk.ORDINAL_POSITION, 0) PrimaryKeyOrdinal, CAST(CASE WHEN fk.COLUMN_NAME IS NULL THEN 0 ELSE 1 END AS BIT) AS IsForeignKey, ( SELECT TOP(1) T.name AS TriggerName FROM sys.triggers T LEFT JOIN sys.all_objects TOBJ ON T.parent_id = TOBJ.object_id LEFT JOIN sys.schemas TSCH ON TSCH.schema_id = TOBJ.schema_id WHERE T.type = ''TR'' AND T.is_disabled = 0 AND TSCH.name IS NOT NULL AND TOBJ.name IS NOT NULL AND st.SynonymSchemaName = TSCH.name AND st.SynonymName = TOBJ.name) AS SynonymTriggerName FROM #SynonymTargets st INNER JOIN sys.columns c ON c.[object_id] = st.base_object_id LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id LEFT OUTER JOIN sys.default_constraints sd ON c.default_object_id = sd.object_id INNER JOIN sys.objects o ON c.[object_id] = o.[object_id] LEFT OUTER JOIN ( SELECT u.TABLE_SCHEMA, u.TABLE_NAME, u.COLUMN_NAME, u.ORDINAL_POSITION FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE u INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ON u.TABLE_SCHEMA = tc.CONSTRAINT_SCHEMA AND u.TABLE_NAME = tc.TABLE_NAME AND u.CONSTRAINT_NAME = tc.CONSTRAINT_NAME WHERE CONSTRAINT_TYPE = ''PRIMARY KEY'' ) AS pk ON st.SchemaName COLLATE SQL_Latin1_General_CP1_CI_AS = pk.TABLE_SCHEMA COLLATE SQL_Latin1_General_CP1_CI_AS AND st.ObjectName COLLATE SQL_Latin1_General_CP1_CI_AS = pk.TABLE_NAME COLLATE SQL_Latin1_General_CP1_CI_AS AND c.name COLLATE SQL_Latin1_General_CP1_CI_AS = pk.COLUMN_NAME COLLATE SQL_Latin1_General_CP1_CI_AS LEFT OUTER JOIN ( SELECT DISTINCT u.TABLE_SCHEMA, u.TABLE_NAME, u.COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE u INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ON u.TABLE_SCHEMA = tc.CONSTRAINT_SCHEMA AND u.TABLE_NAME = tc.TABLE_NAME AND u.CONSTRAINT_NAME = tc.CONSTRAINT_NAME WHERE CONSTRAINT_TYPE = ''FOREIGN KEY'' ) AS fk ON st.SchemaName COLLATE SQL_Latin1_General_CP1_CI_AS = fk.TABLE_SCHEMA COLLATE SQL_Latin1_General_CP1_CI_AS AND st.ObjectName COLLATE SQL_Latin1_General_CP1_CI_AS = fk.TABLE_NAME COLLATE SQL_Latin1_General_CP1_CI_AS AND c.name COLLATE SQL_Latin1_General_CP1_CI_AS = fk.COLUMN_NAME COLLATE SQL_Latin1_General_CP1_CI_AS LEFT OUTER JOIN ( SELECT st.object_id, [st].[temporal_type] AS temporal_type FROM sys.tables AS st ) AS tt ON c.object_id = tt.object_id WHERE st.DatabaseName = @synonmymDatabaseName; ' -- Pull details about the synonym target from each database being referenced SELECT sc.name AS SynonymSchemaName, sn.name AS SynonymName, sn.object_id, sn.base_object_name, OBJECT_ID(sn.base_object_name) AS base_object_id, PARSENAME(sn.base_object_name, 1) AS ObjectName, ISNULL(PARSENAME(sn.base_object_name, 2), sc.name) AS SchemaName, ISNULL(PARSENAME(sn.base_object_name, 3), DB_NAME()) AS DatabaseName, PARSENAME(sn.base_object_name, 4) AS ServerName INTO #SynonymTargets FROM sys.synonyms sn INNER JOIN sys.schemas sc ON sc.schema_id = sn.schema_id WHERE ISNULL(PARSENAME(sn.base_object_name, 4), @@SERVERNAME) = @@SERVERNAME; -- Only populate info from current server -- Loop through synonyms and populate #SynonymDetails DECLARE @synonmymDatabaseName sysname = (SELECT TOP (1) DatabaseName FROM #SynonymTargets) DECLARE @synonmymDetailsSelect nvarchar(max) WHILE ( @synonmymDatabaseName IS NOT NULL) BEGIN SET @synonmymDetailsSelect = REPLACE(@synonymDetailsQueryTemplate, '[@synonmymDatabaseName]', '[' + @synonmymDatabaseName + ']') --SELECT @synonmymDetailsSelect EXEC sp_executesql @stmt=@synonmymDetailsSelect, @params=N'@synonmymDatabaseName sysname', @synonmymDatabaseName=@synonmymDatabaseName DELETE FROM #SynonymTargets WHERE DatabaseName = @synonmymDatabaseName SET @synonmymDatabaseName = (SELECT TOP (1) DatabaseName FROM #SynonymTargets) END SET NOCOUNT OFF; "; } protected override string SynonymTableSQL() { return @" UNION -- Synonyms SELECT SchemaName COLLATE SQL_Latin1_General_CP1_CI_AS, TableName COLLATE SQL_Latin1_General_CP1_CI_AS, TableType COLLATE SQL_Latin1_General_CP1_CI_AS, CONVERT( tinyint, 0 ) AS TableTemporalType, Ordinal, ColumnName COLLATE SQL_Latin1_General_CP1_CI_AS, IsNullable, TypeName COLLATE SQL_Latin1_General_CP1_CI_AS, [MaxLength], [Precision], [Default] COLLATE SQL_Latin1_General_CP1_CI_AS, DateTimePrecision, Scale, IsIdentity, IsRowGuid, IsComputed, GeneratedAlwaysType, IsStoreGenerated, PrimaryKey, PrimaryKeyOrdinal, IsForeignKey, SynonymTriggerName FROM #SynonymDetails"; } protected override string SynonymForeignKeySQLSetup() { return @" SET NOCOUNT ON; IF OBJECT_ID('tempdb..#SynonymFkDetails') IS NOT NULL DROP TABLE #SynonymFkDetails; IF OBJECT_ID('tempdb..#SynonymTargets') IS NOT NULL DROP TABLE #SynonymTargets; -- Create the #SynonymFkDetails temp table structure for later use SELECT FK.name AS FK_Table, FkCol.name AS FK_Column, PK.name AS PK_Table, PkCol.name AS PK_Column, OBJECT_NAME(f.object_id) AS Constraint_Name, SCHEMA_NAME(FK.schema_id) AS fkSchema, SCHEMA_NAME(PK.schema_id) AS pkSchema, PkCol.name AS primarykey, k.constraint_column_id AS ORDINAL_POSITION, CASE WHEN f.delete_referential_action = 1 THEN 1 ELSE 0 END as CascadeOnDelete, f.is_disabled AS IsNotEnforced INTO #SynonymFkDetails FROM sys.objects AS PK INNER JOIN sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS k ON k.constraint_object_id = f.object_id INNER JOIN sys.indexes AS i ON f.referenced_object_id = i.object_id AND f.key_index_id = i.index_id ON PK.object_id = f.referenced_object_id INNER JOIN sys.objects AS FK ON f.parent_object_id = FK.object_id INNER JOIN sys.columns AS PkCol ON f.referenced_object_id = PkCol.object_id AND k.referenced_column_id = PkCol.column_id INNER JOIN sys.columns AS FkCol ON f.parent_object_id = FkCol.object_id AND k.parent_column_id = FkCol.column_id ORDER BY FK_Table, FK_Column -- Get all databases referenced by synonyms. SELECT DISTINCT PARSENAME(sn.base_object_name, 3) AS DatabaseName INTO #SynonymTargets FROM sys.synonyms sn WHERE PARSENAME(sn.base_object_name, 3) <> DB_NAME() AND ISNULL(PARSENAME(sn.base_object_name, 4), @@SERVERNAME) = @@SERVERNAME -- Only populate info from current server ORDER BY PARSENAME(sn.base_object_name, 3) -- Create a query to execute for each referenced database DECLARE @synonymFkDetailsQuery nvarchar(max) = ' INSERT INTO #SynonymFkDetails (FK_Table, FK_Column, PK_Table, PK_Column, Constraint_Name, fkSchema, pkSchema, primarykey, ORDINAL_POSITION, CascadeOnDelete, IsNotEnforced) SELECT FK.name AS FK_Table, FkCol.name AS FK_Column, PK.name AS PK_Table, PkCol.name AS PK_Column, OBJECT_NAME(f.object_id) AS Constraint_Name, SCHEMA_NAME(FK.schema_id) AS fkSchema, SCHEMA_NAME(PK.schema_id) AS pkSchema, PkCol.name AS primarykey, k.constraint_column_id AS ORDINAL_POSITION, CASE WHEN f.delete_referential_action = 1 THEN 1 ELSE 0 END as CascadeOnDelete, f.is_disabled AS IsNotEnforced FROM sys.objects AS PK INNER JOIN sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS k ON k.constraint_object_id = f.object_id INNER JOIN sys.indexes AS i ON f.referenced_object_id = i.object_id AND f.key_index_id = i.index_id ON PK.object_id = f.referenced_object_id INNER JOIN sys.objects AS FK ON f.parent_object_id = FK.object_id INNER JOIN sys.columns AS PkCol ON f.referenced_object_id = PkCol.object_id AND k.referenced_column_id = PkCol.column_id INNER JOIN sys.columns AS FkCol ON f.parent_object_id = FkCol.object_id AND k.parent_column_id = FkCol.column_id ORDER BY FK_Table, FK_Column; ' -- Loop through referenced databases and populate #SynonymFkDetails DECLARE @synonmymDatabaseName sysname = (SELECT TOP (1) DatabaseName FROM #SynonymTargets) DECLARE @synonymFkDetailsQueryWithDb nvarchar(max) WHILE (@synonmymDatabaseName IS NOT NULL) BEGIN SET @synonymFkDetailsQueryWithDb = 'USE [' + @synonmymDatabaseName + '] ' + @synonymFkDetailsQuery EXEC sp_executesql @stmt=@synonymFkDetailsQueryWithDb DELETE FROM #SynonymTargets WHERE DatabaseName = @synonmymDatabaseName SET @synonmymDatabaseName = (SELECT TOP (1) DatabaseName FROM #SynonymTargets) END SET NOCOUNT OFF; "; } protected override string SynonymForeignKeySQL() { return @" UNION -- Synonyms SELECT FK_Table, FK_Column, PK_Table, PK_Column, Constraint_Name, fkSchema, pkSchema, primarykey, ORDINAL_POSITION, CascadeOnDelete, IsNotEnforced FROM #SynonymFkDetails"; } protected override string SynonymStoredProcedureSQLSetup() { return @" SET NOCOUNT ON; IF OBJECT_ID('tempdb..#SynonymStoredProcedureDetails') IS NOT NULL DROP TABLE #SynonymStoredProcedureDetails; IF OBJECT_ID('tempdb..#SynonymTargets') IS NOT NULL DROP TABLE #SynonymTargets; -- Create the #SynonymStoredProcedureDetails temp table structure for later use SELECT TOP (0) R.SPECIFIC_CATALOG, R.SPECIFIC_SCHEMA, R.SPECIFIC_NAME, R.ROUTINE_TYPE, R.DATA_TYPE as RETURN_DATA_TYPE, P.ORDINAL_POSITION, P.PARAMETER_MODE, P.PARAMETER_NAME, P.DATA_TYPE, ISNULL(P.CHARACTER_MAXIMUM_LENGTH, 0) AS CHARACTER_MAXIMUM_LENGTH, ISNULL(P.NUMERIC_PRECISION, 0) AS NUMERIC_PRECISION, ISNULL(P.NUMERIC_SCALE, 0) AS NUMERIC_SCALE, ISNULL(P.DATETIME_PRECISION, 0) AS DATETIME_PRECISION, P.USER_DEFINED_TYPE_SCHEMA + '.' + P.USER_DEFINED_TYPE_NAME AS USER_DEFINED_TYPE INTO #SynonymStoredProcedureDetails FROM INFORMATION_SCHEMA.ROUTINES R LEFT OUTER JOIN INFORMATION_SCHEMA.PARAMETERS P ON P.SPECIFIC_SCHEMA = R.SPECIFIC_SCHEMA AND P.SPECIFIC_NAME = R.SPECIFIC_NAME WHERE R.ROUTINE_TYPE = 'PROCEDURE' AND ( P.IS_RESULT = 'NO' OR P.IS_RESULT IS NULL ) AND R.SPECIFIC_SCHEMA + R.SPECIFIC_NAME IN ( SELECT SCHEMA_NAME(sp.schema_id) + sp.name FROM sys.all_objects AS sp LEFT OUTER JOIN sys.all_sql_modules AS sm ON sm.object_id = sp.object_id WHERE sp.type = 'P' AND (CAST(CASE WHEN sp.is_ms_shipped = 1 THEN 1 WHEN ( SELECT major_id FROM sys.extended_properties WHERE major_id = sp.object_id AND minor_id = 0 AND class = 1 AND name = N'microsoft_database_tools_support' ) IS NOT NULL THEN 1 ELSE 0 END AS BIT) = 0)) -- Get all databases referenced by synonyms. SELECT DISTINCT PARSENAME(sn.base_object_name, 3) AS DatabaseName, sn.base_object_name AS BaseObjectName INTO #SynonymTargets FROM sys.synonyms sn WHERE PARSENAME(sn.base_object_name, 3) <> DB_NAME() AND ISNULL(PARSENAME(sn.base_object_name, 4), @@SERVERNAME) = @@SERVERNAME -- Only populate info from current server ORDER BY PARSENAME(sn.base_object_name, 3) -- Create a query to execute for each referenced database DECLARE @synonymStoredProcedureDetailsQuery nvarchar(max) = ' INSERT INTO #SynonymStoredProcedureDetails (SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ROUTINE_TYPE, RETURN_DATA_TYPE, ORDINAL_POSITION, PARAMETER_MODE, PARAMETER_NAME, DATA_TYPE , CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, DATETIME_PRECISION, USER_DEFINED_TYPE) SELECT R.SPECIFIC_CATALOG, R.SPECIFIC_SCHEMA, R.SPECIFIC_NAME, R.ROUTINE_TYPE, R.DATA_TYPE as RETURN_DATA_TYPE, P.ORDINAL_POSITION, P.PARAMETER_MODE, P.PARAMETER_NAME, P.DATA_TYPE, ISNULL(P.CHARACTER_MAXIMUM_LENGTH, 0) AS CHARACTER_MAXIMUM_LENGTH, ISNULL(P.NUMERIC_PRECISION, 0) AS NUMERIC_PRECISION, ISNULL(P.NUMERIC_SCALE, 0) AS NUMERIC_SCALE, ISNULL(P.DATETIME_PRECISION, 0) AS DATETIME_PRECISION, P.USER_DEFINED_TYPE_SCHEMA + ''.'' + P.USER_DEFINED_TYPE_NAME AS USER_DEFINED_TYPE FROM INFORMATION_SCHEMA.ROUTINES R LEFT OUTER JOIN INFORMATION_SCHEMA.PARAMETERS P ON P.SPECIFIC_SCHEMA = R.SPECIFIC_SCHEMA AND P.SPECIFIC_NAME = R.SPECIFIC_NAME WHERE R.ROUTINE_TYPE = ''PROCEDURE'' AND ( P.IS_RESULT = ''NO'' OR P.IS_RESULT IS NULL ) AND R.SPECIFIC_SCHEMA + R.SPECIFIC_NAME IN ( SELECT SCHEMA_NAME(sp.schema_id) + sp.name FROM sys.all_objects AS sp LEFT OUTER JOIN sys.all_sql_modules AS sm ON sm.object_id = sp.object_id WHERE sp.type = ''P'' AND (CAST(CASE WHEN sp.is_ms_shipped = 1 THEN 1 WHEN ( SELECT major_id FROM sys.extended_properties WHERE major_id = sp.object_id AND minor_id = 0 AND class = 1 AND name = N''microsoft_database_tools_support'' ) IS NOT NULL THEN 1 ELSE 0 END AS BIT) = 0)) UNION ALL SELECT R.SPECIFIC_CATALOG, R.SPECIFIC_SCHEMA, R.SPECIFIC_NAME, R.ROUTINE_TYPE, R.DATA_TYPE as RETURN_DATA_TYPE, P.ORDINAL_POSITION, P.PARAMETER_MODE, P.PARAMETER_NAME, P.DATA_TYPE, ISNULL(P.CHARACTER_MAXIMUM_LENGTH, 0) AS CHARACTER_MAXIMUM_LENGTH, ISNULL(P.NUMERIC_PRECISION, 0) AS NUMERIC_PRECISION, ISNULL(P.NUMERIC_SCALE, 0) AS NUMERIC_SCALE, ISNULL(P.DATETIME_PRECISION, 0) AS DATETIME_PRECISION, P.USER_DEFINED_TYPE_SCHEMA + ''.'' + P.USER_DEFINED_TYPE_NAME AS USER_DEFINED_TYPE FROM INFORMATION_SCHEMA.ROUTINES R LEFT OUTER JOIN INFORMATION_SCHEMA.PARAMETERS P ON P.SPECIFIC_SCHEMA = R.SPECIFIC_SCHEMA AND P.SPECIFIC_NAME = R.SPECIFIC_NAME WHERE R.ROUTINE_TYPE = ''FUNCTION'' AND R.DATA_TYPE = ''TABLE'' ORDER BY R.SPECIFIC_SCHEMA, R.SPECIFIC_NAME, P.ORDINAL_POSITION ' -- Loop through referenced databases and populate #SynonymStoredProcedureDetails DECLARE @synonmymDatabaseName NVARCHAR(128) DECLARE lcsr CURSOR LOCAL FAST_FORWARD READ_ONLY FOR SELECT DISTINCT DatabaseName FROM #SynonymTargets; OPEN lcsr; FETCH NEXT FROM lcsr INTO @synonmymDatabaseName; DECLARE @synonymStoredProcedureDetailsQueryWithDb nvarchar(max) WHILE @@FETCH_STATUS = 0 BEGIN SET @synonymStoredProcedureDetailsQueryWithDb = 'USE [' + @synonmymDatabaseName + ']; ' + @synonymStoredProcedureDetailsQuery; EXEC sp_executesql @stmt=@synonymStoredProcedureDetailsQueryWithDb; FETCH NEXT FROM lcsr INTO @synonmymDatabaseName; END CLOSE lcsr; DEALLOCATE lcsr; -- Remove stored procedures not referenced by synonyms DELETE FROM #SynonymStoredProcedureDetails WHERE '[' + SPECIFIC_CATALOG + '].' + '[' + SPECIFIC_SCHEMA + '].' + '[' + SPECIFIC_NAME + ']' NOT IN ( SELECT BaseObjectName FROM #SynonymTargets ); "; } protected override string SynonymStoredProcedureSQL() { return @" UNION -- Synonyms SELECT SPECIFIC_SCHEMA, SPECIFIC_NAME, ROUTINE_TYPE, RETURN_DATA_TYPE, ORDINAL_POSITION, PARAMETER_MODE, PARAMETER_NAME, DATA_TYPE , CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, DATETIME_PRECISION, USER_DEFINED_TYPE FROM #SynonymStoredProcedureDetails"; } protected override string DefaultSchema(DbConnection conn) { try { var cmd = GetCmd(conn); if (cmd != null) { cmd.CommandText = "SELECT SCHEMA_NAME()"; using (var rdr = cmd.ExecuteReader()) { if (rdr.Read()) { return rdr[0].ToString(); } } } } catch { // Ignored } return "dbo"; } protected override string SpecialQueryFlags() { if (Settings.IncludeQueryTraceOn9481Flag) return @" OPTION (QUERYTRACEON 9481)"; return string.Empty; } protected override bool HasTemporalTableSupport() { return DatabaseProductMajorVersion >= 13; } public override bool HasIdentityColumnSupport() { return true; } protected override string ReadDatabaseEditionSQL() { return @" SELECT SERVERPROPERTY('Edition') AS Edition, CASE SERVERPROPERTY('EngineEdition') WHEN 1 THEN 'Personal' WHEN 2 THEN 'Standard' WHEN 3 THEN 'Enterprise' WHEN 4 THEN 'Express' WHEN 5 THEN 'Azure' ELSE 'Unknown' END AS EngineEdition, SERVERPROPERTY('productversion') AS ProductVersion;"; } private bool IsAzure() { return DatabaseEngineEdition == "Azure"; } public override void ReadStoredProcReturnObjects(List procs) { using (var sqlConnection = new SqlConnection(Settings.ConnectionString)) { foreach (var sp in procs.Where(x => !x.IsScalarValuedFunction)) ReadStoredProcReturnObject(sqlConnection, sp); } } private void ReadStoredProcReturnObject(SqlConnection sqlConnection, StoredProcedure proc) { try { const string structured = "Structured"; var sb = new StringBuilder(255); sb.AppendLine(); sb.AppendLine("SET FMTONLY OFF; SET FMTONLY ON;"); if (proc.IsTableValuedFunction) { foreach (var param in proc.Parameters.Where(x => x.SqlDbType.Equals(structured, StringComparison.InvariantCultureIgnoreCase))) { sb.AppendLine(string.Format("DECLARE {0} {1};", param.Name, param.UserDefinedTypeName)); } sb.Append(string.Format("SELECT * FROM [{0}].[{1}](", proc.Schema.DbName, proc.DbName)); foreach (var param in proc.Parameters) { sb.Append(string.Format("{0}, ", param.SqlDbType.Equals(structured, StringComparison.InvariantCultureIgnoreCase) ? param.Name : "default")); } if (proc.Parameters.Count > 0) sb.Length -= 2; sb.AppendLine(");"); } else { foreach (var param in proc.Parameters) { sb.AppendLine(string.Format("DECLARE {0} {1};", param.Name, param.SqlDbType.Equals(structured, StringComparison.InvariantCultureIgnoreCase) ? param.UserDefinedTypeName : param.SqlDbType)); } sb.Append(string.Format("exec [{0}].[{1}] ", proc.Schema.DbName, proc.DbName)); foreach (var param in proc.Parameters) sb.Append(string.Format("{0}, ", param.Name)); if (proc.Parameters.Count > 0) sb.Length -= 2; sb.AppendLine(";"); } sb.AppendLine("SET FMTONLY OFF; SET FMTONLY OFF;"); var ds = new DataSet(); using (var sqlAdapter = new SqlDataAdapter(sb.ToString(), sqlConnection)) { try { if (sqlConnection.State != ConnectionState.Open) sqlConnection.Open(); sqlAdapter.SelectCommand.ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo); sqlConnection.Close(); sqlAdapter.FillSchema(ds, SchemaType.Source, "MyTable"); } catch { // ignored } } // Tidy up parameters foreach (var p in proc.Parameters) p.NameHumanCase = Regex.Replace(p.NameHumanCase, @"[^A-Za-z0-9@\s]*", string.Empty); for (var count = 0; count < ds.Tables.Count; count++) { proc.ReturnModels.Add(ds.Tables[count].Columns.Cast().ToList()); } proc.MergeModelsIfAllSame(); } catch (Exception) { // Stored procedure does not have a return type } } public override void Init() { base.Init(); } } public enum Relationship { OneToOne, OneToMany, ManyToOne, ManyToMany, DoNotUse } public class Schema : EntityName { public Schema(string dbName) { DbName = dbName; NameHumanCase = dbName; } } public class StoredProcedure : EntityName { public Schema Schema; public List Parameters; public List> ReturnModels; // A list of return models, containing a list of return columns public bool IsTableValuedFunction; public bool IsScalarValuedFunction; public bool IsStoredProcedure; public bool HasSpatialParameter; public bool HasSpatialReturnModel; public StoredProcedure() { Parameters = new List(); ReturnModels = new List>(); } public static bool IsNullable(DataColumn col) { return col.DataType.Namespace != null && col.AllowDBNull && !( Column.StoredProcedureNotNullable.Contains(col.DataType.Name.ToLower()) || Column.StoredProcedureNotNullable.Contains(col.DataType.Namespace.ToLower() + "." + col.DataType.Name.ToLower()) ); } public static string WrapTypeIfNullable(string propertyType, DataColumn col) { return !IsNullable(col) ? propertyType : string.Format(Settings.NullableShortHand ? "{0}?" : "Nullable<{0}>", propertyType); } public void MergeModelsIfAllSame() { if(Settings.MergeMultipleStoredProcModelsIfAllSame && ReturnModels.Count < 2) return; if (ReturnModels.Select(x => x.Count).Distinct().Count() != 1) return; // Column count varies between models var modelsCount = ReturnModels.Count; var colCount = ReturnModels[0].Count; for (var i = 1; i < modelsCount; ++i) { for (var n = 0; n < colCount; n++) { var col = ReturnModels[0][n]; var other = ReturnModels[i][n]; if (col.ColumnName != other.ColumnName || col.DataType.FullName != other.DataType.FullName) return; // Columns differ } } ReturnModels.RemoveRange(1, modelsCount - 1); } public string WriteStoredProcFunctionName(IDbContextFilter filter) { var name = filter.StoredProcedureRename(this); return !string.IsNullOrEmpty(name) ? name : NameHumanCase; } public bool StoredProcHasOutParams() { return Parameters.Any(x => x.Mode != StoredProcedureParameterMode.In); } public bool StoredProcCanExecuteAsync() { return !StoredProcHasOutParams() && !IsTableValuedFunction && ReturnModels.Count <= 1; } public string WriteStoredProcFunctionParams(bool includeProcResult, bool forInterface, bool includeCancellationToken = false) { var willIncludeProcResult = includeProcResult && ReturnModels.Count > 0 && ReturnModels.First().Count > 0; var sb = new StringBuilder(255); var data = Parameters.Where(x => x.Mode != StoredProcedureParameterMode.Out).OrderBy(x => x.Ordinal).ToList(); var minNullableParameter = WhichTailEndParametersCanBeNullable(data, willIncludeProcResult, forInterface); if (minNullableParameter == 0) minNullableParameter = 9999; var count = 0; foreach (var p in data) { ++count; var notNullable = Column.StoredProcedureNotNullable.Contains(p.PropertyType.ToLower()); var isInParam = p.Mode == StoredProcedureParameterMode.In; sb.AppendFormat("{0}{1}{2} {3}{4}, ", isInParam ? string.Empty : "out ", p.PropertyType, notNullable ? string.Empty : "?", p.NameHumanCase, (notNullable || forInterface || !isInParam || count < minNullableParameter) ? string.Empty : " = null"); } if (willIncludeProcResult) sb.Append("out int procResult, "); if (includeCancellationToken) sb.Append("CancellationToken cancellationToken = default(CancellationToken), "); if (includeCancellationToken || willIncludeProcResult || sb.Length > 2) sb.Length -= 2; return sb.ToString(); } // Returns the minimum nullable parameter, counting from 1. 0 Means no column is nullable. public int WhichTailEndParametersCanBeNullable(List data, bool includeProcResult, bool forInterface) { if (forInterface || includeProcResult || !data.Any()) return 0; var dataCount = data.Count; var parameterNumber = dataCount; foreach (var parameter in data.OrderByDescending(x => x.Ordinal)) { if (parameter.Mode == StoredProcedureParameterMode.InOut || Column.StoredProcedureNotNullable.Contains(parameter.PropertyType.ToLower())) { return parameterNumber == dataCount ? 0 : parameterNumber + 1; } --parameterNumber; } return 1; } public string WriteStoredProcFunctionOverloadCall() { var sb = new StringBuilder(255); foreach (var p in Parameters.OrderBy(x => x.Ordinal)) { sb.AppendFormat("{0}{1}, ", p.Mode == StoredProcedureParameterMode.In ? string.Empty : "out ", p.NameHumanCase); } sb.Append("out procResult"); return sb.ToString(); } public string WriteStoredProcFunctionSqlAtParams() { var sb = new StringBuilder(255); var n = 1; var count = Parameters.Count; foreach (var p in Parameters.OrderBy(x => x.Ordinal)) { sb.AppendFormat("{0}{1}{2}", p.Name, p.Mode == StoredProcedureParameterMode.In ? string.Empty : " OUTPUT", n++ < count ? ", " : string.Empty); } return sb.ToString(); } public string WriteNetCoreTableValuedFunctionsSqlAtParams() { var sb = new StringBuilder(255); var count = Parameters.Count; for(var n = 0; n < count; n++) { sb.AppendFormat("{{{0}}}{1}", n, (n + 1) < count ? ", " : string.Empty); } return sb.ToString(); } public string WriteStoredProcSqlParameterName(StoredProcedureParameter p) { return p.NameHumanCase + "Param"; } public string WriteStoredProcFunctionDeclareSqlParameter(bool includeProcResult) { var sb = new StringBuilder(1024); foreach (var p in Parameters.OrderBy(x => x.Ordinal)) { var isNullable = !Column.StoredProcedureNotNullable.Contains(p.PropertyType.ToLower()); var getValueOrDefault = isNullable ? ".GetValueOrDefault()" : string.Empty; var isGeography = p.PropertyType == "DbGeography"; sb.AppendLine( string.Format(" var {0} = new {1}", WriteStoredProcSqlParameterName(p), Settings.SqlParameter()) + string.Format(" {{ ParameterName = \"{0}\", ", p.Name) + (isGeography ? "UdtTypeName = \"geography\"" : string.Format("SqlDbType = SqlDbType.{0}", p.SqlDbType)) + ", Direction = ParameterDirection." + (p.Mode == StoredProcedureParameterMode.In ? "Input" : "Output") + (p.Mode == StoredProcedureParameterMode.In ? ", Value = " + (isGeography ? string.Format("Microsoft.SqlServer.Types.SqlGeography.Parse({0}.AsText())", p.NameHumanCase) : p.NameHumanCase + getValueOrDefault) : string.Empty) + (p.MaxLength != 0 ? ", Size = " + p.MaxLength : string.Empty) + ((p.Precision > 0 || p.Scale > 0) ? ", Precision = " + p.Precision + ", Scale = " + p.Scale : string.Empty) + (p.PropertyType.ToLower().Contains("datatable") ? ", TypeName = \"" + p.UserDefinedTypeName + "\"" : string.Empty) + " };"); if (p.Mode == StoredProcedureParameterMode.In) { sb.AppendFormat( isNullable ? " if (!{0}.HasValue){1} {0}Param.Value = DBNull.Value;{1}{1}" : " if ({0}Param.Value == null){1} {0}Param.Value = DBNull.Value;{1}{1}", p.NameHumanCase, Environment.NewLine); } } if (includeProcResult && ReturnModels.Count < 2) { sb.Append(" var procResultParam = new "); sb.Append(Settings.SqlParameter()); sb.AppendLine(" { ParameterName = \"@procResult\", SqlDbType = SqlDbType.Int, Direction = ParameterDirection.Output };"); } return sb.ToString(); } public string WriteTableValuedFunctionDeclareSqlParameter() { var sb = new StringBuilder(1024); foreach (var p in Parameters.OrderBy(x => x.Ordinal)) { sb.AppendLine(string.Format( " var {0}Param = new ObjectParameter(\"{1}\", typeof({2})) {{ Value = (object){3} }};", p.NameHumanCase, p.Name.Substring(1), p.PropertyType, p.NameHumanCase + (p.Mode == StoredProcedureParameterMode.In && Column.StoredProcedureNotNullable.Contains(p.PropertyType.ToLowerInvariant()) ? string.Empty : " ?? DBNull.Value"))); } return sb.ToString(); } public string WriteStoredProcFunctionSqlParameterAnonymousArray(bool includeProcResultParam, bool appendParam, bool includeCancellationToken = false, bool isEfCore3Plus = false) { var sb = new StringBuilder(255); var parameters = Parameters.OrderBy(x => x.Ordinal).ToList(); var hasParam = parameters.Any(); if (!isEfCore3Plus && includeCancellationToken) sb.Append(", cancellationToken"); if (hasParam || includeProcResultParam) sb.Append(", "); if (isEfCore3Plus && (hasParam || includeProcResultParam)) sb.Append(" new[] {"); foreach (var p in Parameters.OrderBy(x => x.Ordinal)) { sb.Append(string.Format("{0}{1}, ", p.NameHumanCase, appendParam ? "Param" : string.Empty)); hasParam = true; } if (includeProcResultParam) sb.Append("procResultParam"); else if (hasParam) sb.Remove(sb.Length - 2, 2); if (isEfCore3Plus && (hasParam || includeProcResultParam)) sb.Append("}"); if (isEfCore3Plus && includeCancellationToken) sb.Append(", cancellationToken"); return sb.ToString(); } public string WriteTableValuedFunctionSqlParameterAnonymousArray() { if (Parameters.Count == 0) return "new ObjectParameter[] { }"; var sb = new StringBuilder(255); foreach (var p in Parameters.OrderBy(x => x.Ordinal)) { sb.Append(string.Format("{0}Param, ", p.NameHumanCase)); } return sb.ToString().Substring(0, sb.Length - 2); } public string WriteStoredProcFunctionSetSqlParameters(bool isFake) { var sb = new StringBuilder(255); foreach (var p in Parameters .Where(x => x.Mode != StoredProcedureParameterMode.In) .OrderBy(x => x.Ordinal)) { var Default = string.Format("default({0})", p.PropertyType); var notNullable = Column.StoredProcedureNotNullable.Contains(p.PropertyType.ToLower()); if (isFake) { sb.AppendLine(string.Format(" {0} = {1};", p.NameHumanCase, Default)); } else { sb.AppendLine(string.Format(" if (IsSqlParameterNull({0}Param))", p.NameHumanCase)); sb.AppendLine(string.Format(" {0} = {1};", p.NameHumanCase, notNullable ? Default : "null")); sb.AppendLine(" else"); sb.AppendLine(string.Format(" {0} = ({1}) {2}Param.Value;", p.NameHumanCase, p.PropertyType, p.NameHumanCase)); sb.AppendLine(); } } return sb.ToString(); } public string WriteStoredProcReturnModelName(IDbContextFilter filter) { if (Settings.StoredProcedureReturnTypes.ContainsKey(NameHumanCase)) return Settings.StoredProcedureReturnTypes[NameHumanCase]; if (Settings.StoredProcedureReturnTypes.ContainsKey(DbName)) return Settings.StoredProcedureReturnTypes[DbName]; var name = string.Format("{0}ReturnModel", NameHumanCase); var customName = filter.StoredProcedureReturnModelRename(name, this); if (!string.IsNullOrEmpty(customName)) name = customName; return name; } public string WriteStoredProcReturnColumn(DataColumn col) { var columnName = DatabaseReader.ReservedKeywords.Contains(col.ColumnName) ? "@" + col.ColumnName : col.ColumnName; // Replace return column name that start with either JSON_ or XML_ with a GUID // This does not change the if the regex does not match columnName = Regex.Replace(columnName, "^(?JSON|XML)_([0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12})", "${prefix}_Value", RegexOptions.IgnoreCase); return string.Format("public {0} {1} {{ get; set; }}", WrapTypeIfNullable(ConvertDataColumnType(col.DataType), col), columnName); } private string ConvertDataColumnType(Type type) { var isEfCore5Plus = Settings.IsEfCore6Plus(); if (type.Name.Equals("SqlHierarchyId")) return isEfCore5Plus ? "HierarchyId" : "Microsoft.SqlServer.Types.SqlHierarchyId"; var typeNamespace = type.Namespace + "."; if (type.Namespace?.ToLower() == "system") typeNamespace = string.Empty; var typeName = type.Name; var isArray = typeName.EndsWith("[]"); if (isArray) typeName = typeName.Replace("[]", string.Empty); switch (typeName.ToLower()) { case "int16": typeName = "short"; break; case "int32": typeName = "int"; break; case "int64": typeName = "long"; break; case "uint16": typeName = "ushort"; break; case "uint32": typeName = "uint"; break; case "uint64": typeName = "ulong"; break; case "string": typeName = "string"; break; case "decimal": typeName = "decimal"; break; case "double": typeName = "double"; break; case "float": typeName = "float"; break; case "byte": typeName = "byte"; break; case "boolean": typeName = "bool"; break; } if (isEfCore5Plus) { switch (typeName.ToLower()) { case "microsoft.sqlserver.types.sqlgeography": case "sqlgeography": typeNamespace = "NetTopologySuite.Geometries."; typeName = "Point"; break; case "microsoft.sqlserver.types.sqlgeometry": case "sqlgeometry": typeNamespace = "NetTopologySuite.Geometries."; typeName = "Geometry"; break; } } if (isArray) typeName += "[]"; return typeNamespace + typeName; } public string WriteStoredProcReturnType(IDbContextFilter filter) { var returnModelCount = ReturnModels.Count; if (returnModelCount == 0) return "int"; var spReturnClassName = WriteStoredProcReturnModelName(filter); return returnModelCount == 1 ? string.Format("List<{0}>", spReturnClassName) : spReturnClassName; } } public class StoredProcedureParameter { public int Ordinal; public StoredProcedureParameterMode Mode; public string Name; public string NameHumanCase; public string SqlDbType; public string ReturnSqlDbType; public string PropertyType; public string ReturnPropertyType; public string UserDefinedTypeName; public int DateTimePrecision; public int MaxLength; public int Precision; public int Scale; public bool IsSpatial; } public enum StoredProcedureParameterMode { In, InOut, Out }; public class Table : EntityName { public Schema Schema; public string Type; public string Suffix; public List ExtendedProperty; public bool IsMapping; public bool IsView; public bool IsSynonym; public bool HasForeignKey; public bool HasNullableColumns; public bool UsesDictionary; public bool HasPrimaryKey; public bool RemoveTable; public bool IsMemoryOptimised; public string AdditionalComment; public string PluralNameOverride; public string DbSetModifier = "public"; public string BaseClasses; public string TriggerName; public List Columns; public List ReverseNavigationProperty; public List MappingConfiguration; public List ReverseNavigationCtor; public List Indexes; public List Attributes = new List(); // List of attributes to add to this table private readonly IDbContextFilter _filter; private readonly IForeignKeyNamingStrategy _foreignKeyNamingStrategy; public Table(IDbContextFilter filter, Schema schema, string dbName, bool isView) { _filter = filter; Schema = schema; DbName = dbName; IsView = isView; Columns = new List(); _foreignKeyNamingStrategy = ForeignKeyNamingStrategyFactory.Create(filter, this); ResetNavigationProperties(); ExtendedProperty = new List(); UsesDictionary = false; } internal static string GetLazyLoadingMarker() { return Settings.UseLazyLoading ? "virtual " : string.Empty; } public string NameHumanCaseWithSuffix() { return NameHumanCase + Suffix; } public void ResetNavigationProperties() { _foreignKeyNamingStrategy.ResetNavigationProperties(); MappingConfiguration = new List(); ReverseNavigationProperty = new List(); ReverseNavigationCtor = new List(); foreach (var col in Columns) col.ResetNavigationProperties(); } public void SetPrimaryKeys() { HasPrimaryKey = Columns.Any(x => x.IsPrimaryKey); if (HasPrimaryKey) return; // Table has at least one primary key if (IsView && Settings.IsEfCore3Plus()) return; // EfCore 3 supports views by use of .HasNoKey() and .ToView("view name"); // This table is not allowed in EntityFramework v6 / EfCore 2 as it does not have a primary key. // Therefore generate a composite key from all non-null fields. foreach (var col in Columns.Where(x => !x.IsNullable && !x.Hidden)) { col.IsPrimaryKey = true; HasPrimaryKey = true; } } public IEnumerable PrimaryKeys { get { return Columns .Where(x => x.IsPrimaryKey) .OrderBy(x => x.PrimaryKeyOrdinal) .ThenBy(x => x.Ordinal) .ToList(); } } public string PrimaryKeyNameHumanCase() { var data = PrimaryKeys.Select(x => "x." + x.NameHumanCase).ToList(); var n = data.Count; if (n == 0) return string.Empty; if (n == 1) return "x => " + data.First(); // More than one primary key return string.Format("x => new {{ {0} }}", string.Join(", ", data)); } public Column this[string columnName] { get { return GetColumn(columnName); } } public Column GetColumn(string columnName) { return Columns.SingleOrDefault(x => string.Compare(x.DbName, columnName, StringComparison.OrdinalIgnoreCase) == 0); } public string GetUniqueForeignKeyName(bool isParent, string tableNameHumanCase, ForeignKey foreignKey, bool checkForFkNameClashes, bool makeSingular, Relationship relationship) { // For unit testing /*if (tableNameHumanCase.StartsWith("Burak") || tableNameHumanCase.StartsWith("Car") || tableNameHumanCase.StartsWith("User")) { var s = $"[TestCase(\"00\", \"{foreignKey.FkTableName}\", \"{NameHumanCase}\", \"{string.Join("|", Columns.Select(c => c.NameHumanCase))}\", {isParent}, \"{tableNameHumanCase}\", {checkForFkNameClashes}, {makeSingular}, Relationship.{relationship}, \"{foreignKey.FkTableName}\", \"{foreignKey.PkTableName}\", {foreignKey.IncludeReverseNavigation}, \"{foreignKey.FkColumn}\")]{Environment.NewLine}"; System.IO.File.AppendAllText("c:/temp/unit.txt", s); }*/ return _foreignKeyNamingStrategy.GetUniqueForeignKeyName(isParent, tableNameHumanCase, foreignKey, checkForFkNameClashes, makeSingular, relationship); } public void AddReverseNavigation(Relationship relationship, Table fkTable, string propName, string constraint, List fks, Table mappingTable = null) { var fkNames = ""; switch (relationship) { case Relationship.OneToOne: case Relationship.OneToMany: case Relationship.ManyToOne: fkNames = (fks.Count > 1 ? "(" : "") + string.Join(", ", fks.Select(x => "[" + x.FkColumn + "]").Distinct().ToArray()) + (fks.Count > 1 ? ")" : ""); break; case Relationship.ManyToMany: break; } var accessModifier = fks != null && fks.FirstOrDefault() != null ? (fks.FirstOrDefault().AccessModifier ?? "public") : "public"; switch (relationship) { case Relationship.OneToOne: ReverseNavigationProperty.Add( new PropertyAndComments { AdditionalDataAnnotations = _filter.ForeignKeyAnnotationsProcessing(fkTable, this, propName, string.Empty), PropertyName = propName, Definition = string.Format("{0} {1}{2} {3} {{ get; set; }}{4}", accessModifier, GetLazyLoadingMarker(), fkTable.NameHumanCaseWithSuffix(), propName, Settings.IncludeComments != CommentsStyle.None ? " // " + constraint : string.Empty), Comments = string.Format("Parent (One-to-One) {0} pointed by [{1}].{2} ({3})", NameHumanCaseWithSuffix(), fkTable.DbName, fkNames, fks.First().ConstraintName) } ); break; case Relationship.OneToMany: ReverseNavigationProperty.Add( new PropertyAndComments { AdditionalDataAnnotations = _filter.ForeignKeyAnnotationsProcessing(fkTable, this, propName, string.Empty), PropertyName = propName, Definition = string.Format("{0} {1}{2} {3} {{ get; set; }}{4}", accessModifier, GetLazyLoadingMarker(), fkTable.NameHumanCaseWithSuffix(), propName, Settings.IncludeComments != CommentsStyle.None ? " // " + constraint : string.Empty), Comments = string.Format("Parent {0} pointed by [{1}].{2} ({3})", NameHumanCaseWithSuffix(), fkTable.DbName, fkNames, fks.First().ConstraintName) } ); break; case Relationship.ManyToOne: var initialisation1 = string.Empty; if (Settings.UsePropertyInitialisers) initialisation1 = string.Format(" = new {0}<{1}>();", Settings.CollectionType, fkTable.NameHumanCaseWithSuffix()); ReverseNavigationProperty.Add( new PropertyAndComments { AdditionalDataAnnotations = _filter.ForeignKeyAnnotationsProcessing(fkTable, this, propName, string.Empty), PropertyName = propName, Definition = string.Format("{0} {1}{2}<{3}> {4} {{ get; set; }}{5}{6}", accessModifier, GetLazyLoadingMarker(), Settings.CollectionInterfaceType, fkTable.NameHumanCaseWithSuffix(), propName, initialisation1, Settings.IncludeComments != CommentsStyle.None ? " // " + constraint : string.Empty), Comments = string.Format("Child {0} where [{1}].{2} point to this entity ({3})", Inflector.MakePlural(fkTable.NameHumanCase), fkTable.DbName, fkNames, fks.First().ConstraintName) } ); ReverseNavigationCtor.Add(string.Format("{0} = new {1}<{2}>();", propName, Settings.CollectionType, fkTable.NameHumanCaseWithSuffix())); break; case Relationship.ManyToMany: var initialisation2 = string.Empty; if (Settings.UsePropertyInitialisers) initialisation2 = string.Format(" = new {0}<{1}>();", Settings.CollectionType, fkTable.NameHumanCaseWithSuffix()); ReverseNavigationProperty.Add( new PropertyAndComments { AdditionalDataAnnotations = _filter.ForeignKeyAnnotationsProcessing(fkTable, this, propName, string.Empty), PropertyName = propName, Definition = string.Format("{0} {1}{2}<{3}> {4} {{ get; set; }}{5}{6}", accessModifier, GetLazyLoadingMarker(), Settings.CollectionInterfaceType, fkTable.NameHumanCaseWithSuffix(), propName, initialisation2, Settings.IncludeComments != CommentsStyle.None ? " // Many to many mapping" : string.Empty), Comments = string.Format("Child {0} (Many-to-Many) mapped by table [{1}]", Inflector.MakePlural(fkTable.NameHumanCase), mappingTable == null ? string.Empty : mappingTable.DbName) } ); ReverseNavigationCtor.Add(string.Format("{0} = new {1}<{2}>();", propName, Settings.CollectionType, fkTable.NameHumanCaseWithSuffix())); break; default: throw new ArgumentOutOfRangeException("relationship"); } } public void IdentifyMappingTable(List fkList, Tables tables, bool checkForFkNameClashes, bool includeSchema) { IsMapping = false; var nonReadOnlyColumns = Columns .Where(c => !c.IsIdentity && !c.IsRowVersion && !c.IsStoreGenerated && !c.Hidden) .ToList(); // Ignoring read-only columns, it must have only 2 columns to be a mapping table if (nonReadOnlyColumns.Count != 2) return; // Must have 2 primary keys if (nonReadOnlyColumns.Count(x => x.IsPrimaryKey) != 2) return; // No columns should be nullable if (nonReadOnlyColumns.Any(x => x.IsNullable)) return; // Find the foreign keys for this table var foreignKeys = fkList.Where(x => string.Compare(x.FkTableName, DbName, StringComparison.OrdinalIgnoreCase) == 0 && string.Compare(x.FkSchema, Schema.DbName, StringComparison.OrdinalIgnoreCase) == 0) .ToList(); // Each column must have a foreign key, therefore check column and foreign key counts match if (foreignKeys.Select(x => x.FkColumn).Distinct().Count() != 2) return; var left = foreignKeys[0]; var right = foreignKeys[1]; if (!left.IncludeReverseNavigation || !right.IncludeReverseNavigation) return; var leftTable = tables.GetTable(left.PkTableName, left.PkSchema); if (leftTable == null) return; var rightTable = tables.GetTable(right.PkTableName, right.PkSchema); if (rightTable == null) return; var leftPropName = leftTable.GetUniqueForeignKeyName(true, rightTable.NameHumanCase, right, checkForFkNameClashes, false, Relationship.ManyToOne); // relationship from the mapping table to each side is Many-to-One leftPropName = _filter.MappingTableRename(DbName, leftTable.NameHumanCase, leftPropName); var rightPropName = rightTable.GetUniqueForeignKeyName(false, leftTable.NameHumanCase, left, checkForFkNameClashes, false, Relationship.ManyToOne); // relationship from the mapping table to each side is Many-to-One rightPropName = _filter.MappingTableRename(DbName, rightTable.NameHumanCase, rightPropName); leftTable.AddMappingConfiguration(left, right, leftPropName, rightPropName, includeSchema, leftTable.NameHumanCase, rightTable.NameHumanCase); IsMapping = true; rightTable.AddReverseNavigation(Relationship.ManyToMany, leftTable, rightPropName, null, null, this); leftTable.AddReverseNavigation (Relationship.ManyToMany, rightTable, leftPropName, null, null, this); } private void AddMappingConfiguration(ForeignKey left, ForeignKey right, string leftPropName, string rightPropName, bool includeSchema, string leftNameHumanCase, string rightNameHumanCase) { if (Settings.IsEf6()) { MappingConfiguration.Add(string.Format(@"HasMany(t => t.{0}).WithMany(t => t.{1}).Map(m => {{ m.ToTable(""{2}""{5}); m.MapLeftKey(""{3}""); m.MapRightKey(""{4}""); }});", leftPropName, rightPropName, left.FkTableName, left.FkColumn, right.FkColumn, !includeSchema ? string.Empty : ", \"" + left.FkSchema + "\"")); return; } if(Settings.IsEfCore6Plus()) { UsesDictionary = true; MappingConfiguration.Add(string.Format(@"HasMany<{6}>(t => t.{0}).WithMany(t => t.{1}).UsingEntity>(""{2}"", j => j.HasOne<{6}>().WithMany().HasForeignKey(""{4}""), j => j.HasOne<{5}>().WithMany().HasForeignKey(""{3}""), j => j.ToTable(""{2}""{7}));", leftPropName, rightPropName, right.FkTableName, left.FkColumn, right.FkColumn, leftNameHumanCase, rightNameHumanCase, !includeSchema ? string.Empty : ", \"" + left.FkSchema + "\"")); } } // This method will be called right before we write the POCO class public string WriteClassAttributes() { if (Attributes == null) return string.Empty; var sb = new StringBuilder(); foreach (var attribute in Attributes.Distinct()) sb.AppendLine(attribute); if (Settings.UseDataAnnotations) sb.AppendLine($"[Table(\"{DbName}\", Schema = \"{Schema.DbName}\")]"); return sb.ToString(); } // This method will be called right before we write the POCO class public string WriteComments() { if(Settings.IncludeComments == CommentsStyle.None) return string.Empty; var comment = "// " + DbName + Environment.NewLine; if (string.IsNullOrWhiteSpace(AdditionalComment)) return comment; return comment + "// " + AdditionalComment + Environment.NewLine; } // This method will be called right before we write the POCO class public string WriteExtendedComments() { if (Settings.IncludeExtendedPropertyComments == CommentsStyle.None || !ExtendedProperty.Any()) return string.Empty; var lines = ExtendedProperty .SelectMany(x => x.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)) .ToList(); var sb = new StringBuilder(255); sb.AppendLine("/// "); foreach (var line in lines.Select(x => x.Replace("///", string.Empty).Trim())) { sb.Append("/// "); sb.AppendLine(System.Security.SecurityElement.Escape(line)); } sb.AppendLine("/// "); return sb.ToString(); } } public class Tables : List
{ public Table GetTable(string tableName, string schema) { return this.SingleOrDefault(x => string.Compare(x.DbName, tableName, StringComparison.OrdinalIgnoreCase) == 0 && string.Compare(x.Schema.DbName, schema, StringComparison.OrdinalIgnoreCase) == 0); } public void IdentifyMappingTables(List fkList, bool checkForFkNameClashes, bool includeSchema) { foreach (var tbl in this.Where(x => x.HasForeignKey)) { tbl.IdentifyMappingTable(fkList, this, checkForFkNameClashes, includeSchema); } } public void ResetNavigationProperties() { foreach (var tbl in this) { tbl.ResetNavigationProperties(); } } public void TrimForTrialLicence() { // Mapping tables do not count const int n = 1 + 2 + 3 + 4; TrimForLicence(n); } private void TrimForLicence(int n) { if (this.Count(x => !x.IsMapping) <= n) return; RemoveAll(x => !x.HasPrimaryKey); while (this.Count(x => !x.IsMapping) > n) { try { var index = FindIndex(x => !x.IsMapping); RemoveAt(index); } catch { // Cannot remove anymore return; } } } } public class ContextModel { public string DbContextClassModifiers { get; set; } public string DbContextName { get; set; } public string DbContextBaseClass { get; set; } public bool AddParameterlessConstructorToDbContext { get; set; } public bool HasDefaultConstructorArgument { get; set; } public string DefaultConstructorArgument { get; set; } public string ConfigurationClassName { get; set; } public string contextInterface { get; set; } public string setInitializer { get; set; } public bool DbContextClassIsPartial { get; set; } public bool SqlCe { get; set; } public List tables { get; set; } public bool hasTables { get; set; } public List indexes { get; set; } public bool hasIndexes { get; set; } public List storedProcs { get; set; } public bool hasStoredProcs { get; set; } public List tableValuedFunctionComplexTypes { get; set; } public bool hasTableValuedFunctionComplexTypes { get; set; } public List AdditionalContextInterfaceItems { get; set; } public bool addSaveChanges { get; set; } public List tableValuedFunctions { get; set; } public List scalarValuedFunctions { get; set; } public List Sequences { get; set; } public bool hasSequences { get; set; } public bool hasTableValuedFunctions { get; set; } public bool hasScalarValuedFunctions { get; set; } public string ConnectionString { get; set; } public string ConnectionStringName { get; set; } public string ConnectionStringActions { get; set; } public bool IncludeObjectContextConstructor { get; set; } public string QueryString { get; set; } public string FromSql { get; set; } public string ExecuteSqlCommand { get; set; } public string StoredProcModelBuilderCommand { get; set; } public string StoredProcModelBuilderPostCommand { get; set; } public bool OnConfigurationUsesConfiguration { get; set; } public bool OnConfigurationUsesConnectionString { get; set; } public string DefaultSchema { get; set; } public string UseDatabaseProvider { get; set; } public string SqlParameter { get; set; } public string SqlParameterValue { get; set; } public bool UseLazyLoadingProxies { get; set; } public bool hasTriggers { get; set; } public List Triggers { get; set; } public bool hasMemoryOptimisedTables { get; set; } public List MemoryOptimisedTables { get; set; } } public class FactoryModel { public string classModifier { get; set; } public string contextName { get; set; } } public class FakeContextModel { public string DbContextClassModifiers { get; set; } public string DbContextName { get; set; } public string DbContextBaseClass { get; set; } public string contextInterface { get; set; } public bool DbContextClassIsPartial { get; set; } public List tables { get; set; } public List storedProcs { get; set; } public bool hasStoredProcs { get; set; } public List tableValuedFunctions { get; set; } public List scalarValuedFunctions { get; set; } public bool hasTableValuedFunctions { get; set; } public bool hasScalarValuedFunctions { get; set; } } public class FakeDbSetModel { public string DbContextClassModifiers { get; set; } public bool DbContextClassIsPartial { get; set; } } public class InterfaceModel { public string interfaceModifier { get; set; } public string DbContextInterfaceName { get; set; } public string DbContextInterfaceBaseClasses { get; set; } public string DbContextName { get; set; } public List tables { get; set; } public List AdditionalContextInterfaceItems { get; set; } public bool addSaveChanges { get; set; } public List storedProcs { get; set; } public bool hasStoredProcs { get; set; } public List tableValuedFunctions { get; set; } public List scalarValuedFunctions { get; set; } public bool hasTableValuedFunctions { get; set; } public bool hasScalarValuedFunctions { get; set; } } public class PocoConfigurationModel { public string Name { get; set; } public string ConfigurationClassName { get; set; } public string NameHumanCaseWithSuffix { get; set; } public string Schema { get; set; } public string PrimaryKeyNameHumanCase { get; set; } public bool HasSchema { get; set; } public bool NotUsingDataAnnotations { get; set; } public string ClassModifier { get; set; } public string ClassComment { get; set; } public List Columns { get; set; } public bool HasReverseNavigation { get; set; } public List ReverseNavigationProperty { get; set; } public bool HasForeignKey { get; set; } public List ForeignKeys { get; set; } public List MappingConfiguration { get; set; } public List Indexes { get; set; } public bool HasIndexes { get; set; } public bool ConfigurationClassesArePartial { get; set; } public bool UseHasNoKey { get; set; } public string ToTableOrView { get; set; } public bool UsesDictionary { get; set; } public bool HasSpatial { get; set; } } public class PocoModel { public bool UseHasNoKey { get; set; } public bool HasNoPrimaryKey { get; set; } public string Name { get; set; } public string NameHumanCaseWithSuffix { get; set; } public string ClassModifier { get; set; } public string ClassComment { get; set; } public string ExtendedComments { get; set; } public string ClassAttributes { get; set; } public string BaseClasses { get; set; } public string InsideClassBody { get; set; } public List Columns { get; set; } public bool HasReverseNavigation { get; set; } public List ReverseNavigationProperty { get; set; } public bool HasForeignKey { get; set; } public string ForeignKeyTitleComment { get; set; } public List ForeignKeys { get; set; } public bool CreateConstructor { get; set; } public List ColumnsWithDefaults { get; set; } public List ReverseNavigationCtor { get; set; } public bool EntityClassesArePartial { get; set; } public bool HasHierarchyId { get; set; } public bool HasSpatial { get; set; } } public class PocoColumnModel { public bool AddNewLineBefore { get; set; } public bool HasSummaryComments { get; set; } public string SummaryComments { get; set; } public List Attributes { get; set; } public bool OverrideModifier { get; set; } public bool IncludeFieldNameConstants { get; set; } public string WrapIfNullable { get; set; } public string NameHumanCase { get; set; } public string PrivateSetterForComputedColumns { get; set; } public string PropertyInitialisers { get; set; } public string InlineComments { get; set; } } public class PocoReverseNavigationPropertyModel { public bool ReverseNavHasComment { get; set; } public string ReverseNavComment { get; set; } public string[] AdditionalReverseNavigationsDataAnnotations { get; set; } public string[] AdditionalDataAnnotations { get; set; } public string Definition { get; set; } } public class PocoForeignKeyModel { public bool HasFkComment { get; set; } public string FkComment { get; set; } public string[] AdditionalForeignKeysDataAnnotations { get; set; } public string[] AdditionalDataAnnotations { get; set; } public string Definition { get; set; } } public class PocoColumnsWithDefaultsModel { public string NameHumanCase { get; set; } public string Default { get; set; } } public class StoredProcReturnModel { public string ResultClassModifiers { get; set; } public string WriteStoredProcReturnModelName { get; set; } public string PropertyGetSet { get; set; } public bool SingleModel { get; set; } public List SingleModelReturnColumns { get; set; } public List MultipleModelReturnColumns { get; set; } } public class Trigger { public string TableName { get; set; } public string TriggerName { get; set; } } public enum GeneratorType { Ef6, EfCore, Custom } /// /// {{Mustache}} template documentation available at https://github.com/jehugaleahsa/mustache-sharp /// public abstract class Template { public abstract string Usings(); public abstract List DatabaseContextInterfaceUsings(InterfaceModel data); public abstract string DatabaseContextInterface(); public abstract List DatabaseContextUsings(ContextModel data); public abstract string DatabaseContext(); public abstract List DatabaseContextFactoryUsings(FactoryModel data); public abstract string DatabaseContextFactory(); public abstract List FakeDatabaseContextUsings(FakeContextModel data, IDbContextFilter filter); public abstract string FakeDatabaseContext(); public abstract List FakeDbSetUsings(FakeDbSetModel data); public abstract string FakeDbSet(); public abstract List PocoUsings(PocoModel data); public abstract string Poco(); public abstract List PocoConfigurationUsings(PocoConfigurationModel data); public abstract string PocoConfiguration(); public abstract List StoredProcReturnModelUsings(); public abstract string StoredProcReturnModels(); public abstract List EnumUsings(); public abstract string Enums(); public static string Transform(string template, object data) { if (data == null || template == null) return template; // Thanks to the awesome work by Travis Parks and Keith Williams for the Mustache# for .NET Core library // which is available at https://github.com/SunBrandingSolutions/mustache-sharp var parser = new FormatCompiler(); var mustacheGenerator = parser.Compile(template.Replace("\n", string.Empty).Replace("\r", string.Empty)); return mustacheGenerator.Render(data); } } /// /// {{Mustache}} template documentation available at https://github.com/jehugaleahsa/mustache-sharp /// public class TemplateEf6 : Template { public override string Usings() { return @" {{#each this}} using {{this}};{{#newline}} {{/each}}"; } public override List DatabaseContextInterfaceUsings(InterfaceModel data) { var usings = new List { "System", "System.Data", "System.Threading.Tasks", "System.Threading" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("System.Data.Entity"); usings.Add("System.Data.Entity.Spatial"); usings.Add("System.Linq"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Data.Entity"); usings.Add("System.Data.Entity.Infrastructure"); usings.Add("System.Collections.Generic"); usings.Add("System.Data.Entity.Validation"); } return usings; } public override string DatabaseContextInterface() { return @" {{interfaceModifier}} interface {{DbContextInterfaceName}} : {{DbContextInterfaceBaseClasses}}{{#newline}} {{{#newline}} {{#each tables}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#if AdditionalContextInterfaceItems}} {{#newline}} // Additional interface items{{#newline}} {{/if}} {{#each AdditionalContextInterfaceItems}} {{this}}{{#newline}} {{/each}} {{#if addSaveChanges}} {{#newline}} int SaveChanges();{{#newline}} Task SaveChangesAsync();{{#newline}} Task SaveChangesAsync(CancellationToken cancellationToken);{{#newline}} DbChangeTracker ChangeTracker { get; }{{#newline}} DbContextConfiguration Configuration { get; }{{#newline}} Database Database { get; }{{#newline}} DbEntityEntry Entry(TEntity entity) where TEntity : class;{{#newline}} DbEntityEntry Entry(object entity);{{#newline}} IEnumerable GetValidationErrors();{{#newline}} DbSet Set(Type entityType);{{#newline}} DbSet Set() where TEntity : class;{{#newline}} string ToString();{{#newline}} {{/if}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseTrue}});{{#newline}} {{#if SingleReturnModel}} {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueTrue}});{{#newline}} {{/if}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseTrueToken}});{{#newline}} {{/if}} {{#newline}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#newline}} [DbFunction(""{{DbContextName}}"", ""{{Name}}"")]{{#newline}} [CodeFirstStoreFunctions.DbFunctionDetails(DatabaseSchema = ""{{Schema}}""{{#if SingleReturnModel}}, ResultColumnName = ""{{SingleReturnColumnName}}""{{/if}})]{{#newline}} IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseTrue}});{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseTrue}}); // {{Schema}}.{{Name}}{{#newline}} {{/each}} {{/if}} }"; } public override List DatabaseContextUsings(ContextModel data) { var usings = new List { "System", "System.Data", "System.Data.Common", "System.Data.Entity", "System.Data.Entity.Core.Objects", "System.Data.Entity.Infrastructure", "System.Data.Entity.Infrastructure.Interception", "System.Data.Entity.Infrastructure.Annotations", "System.ComponentModel.DataAnnotations.Schema", "System.Data.SqlClient", "System.Data.SqlTypes", "System.Threading.Tasks", "System.Threading" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("System.Data.Entity"); usings.Add("System.Data.Entity.Spatial"); usings.Add("System.Linq"); } if (data.hasStoredProcs) { usings.Add("System.Collections.Generic"); } if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Data.Entity"); usings.Add("System.Data.Entity.Infrastructure"); usings.Add("System.Collections.Generic"); usings.Add("System.Data.Entity.Validation"); } if (Settings.DatabaseType == DatabaseType.SqlCe) { usings.Add("System.Data.SqlClient"); //usings.Add("System.DBNull"); usings.Add("System.Data.SqlTypes"); } if (Settings.DatabaseType == DatabaseType.PostgreSQL) { usings.Add("Npgsql"); usings.Add("NpgsqlTypes"); } return usings; } public override string DatabaseContext() { return @" {{DbContextClassModifiers}} class {{DbContextName}} : {{DbContextBaseClass}}{{contextInterface}}{{#newline}} {{{#newline}} {{#each tables}} {{DbSetModifier}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#newline}} static {{DbContextName}}(){{#newline}} {{{#newline}} System.Data.Entity.Database.SetInitializer{{setInitializer}}{{#newline}} }{{#newline}}{{#newline}} {{#if AddParameterlessConstructorToDbContext}} /// {{#newline}} public {{DbContextName}}(){{#newline}} {{#if HasDefaultConstructorArgument}} : base({{DefaultConstructorArgument}}){{#newline}} {{/if}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{/if}} /// {{#newline}} public {{DbContextName}}(string connectionString){{#newline}} : base(connectionString){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} /// {{#newline}} public {{DbContextName}}(string connectionString, DbCompiledModel model){{#newline}} : base(connectionString, model){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} /// {{#newline}} public {{DbContextName}}(DbConnection existingConnection, bool contextOwnsConnection){{#newline}} : base(existingConnection, contextOwnsConnection){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} /// {{#newline}} public {{DbContextName}}(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection){{#newline}} : base(existingConnection, model, contextOwnsConnection){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{#if IncludeObjectContextConstructor}} /// {{#newline}} public {{DbContextName}}(ObjectContext objectContext, bool dbContextOwnsObjectContext){{#newline}} : base(objectContext, dbContextOwnsObjectContext){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{/if}} protected override void Dispose(bool disposing){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} DisposePartial(disposing);{{#newline}} {{/if}} base.Dispose(disposing);{{#newline}} }{{#newline}} {{#newline}} public bool IsSqlParameterNull({{SqlParameter}} param){{#newline}} {{{#newline}} var sqlValue = param.{{SqlParameterValue}};{{#newline}} var nullableValue = sqlValue as INullable;{{#newline}} if (nullableValue != null){{#newline}} return nullableValue.IsNull;{{#newline}} return (sqlValue == null || sqlValue == DBNull.Value);{{#newline}} }{{#newline}}{{#newline}} protected override void OnModelCreating(DbModelBuilder modelBuilder){{#newline}} {{{#newline}} base.OnModelCreating(modelBuilder);{{#newline}} {{#if hasTableValuedFunctions}} {{#newline}} modelBuilder.Conventions.Add(new CodeFirstStoreFunctions.FunctionsConvention<{{DbContextName}}>(""{{DefaultSchema}}""));{{#newline}} {{#if hasTableValuedFunctionComplexTypes}} {{#newline}} {{#each tableValuedFunctionComplexTypes}} modelBuilder.ComplexType<{{this}}>();{{#newline}} {{/each}} {{/if}} {{/if}} {{#if hasTables}} {{#newline}} {{#each tables}} modelBuilder.Configurations.Add(new {{DbSetConfigName}}());{{#newline}} {{/each}} {{/if}} {{#if hasIndexes}} {{#newline}} // Indexes{{#newline}} {{#each indexes}} {{this}}{{#newline}} {{/each}} {{/if}} {{#if DbContextClassIsPartial}} {{#newline}} OnModelCreatingPartial(modelBuilder);{{#newline}} {{/if}} }{{#newline}} {{#newline}} public static DbModelBuilder CreateModel(DbModelBuilder modelBuilder, string schema){{#newline}} {{{#newline}} {{#each tables}} modelBuilder.Configurations.Add(new {{DbSetConfigName}}(schema));{{#newline}} {{/each}} {{#newline}} {{#if DbContextClassIsPartial}} OnCreateModelPartial(modelBuilder, schema);{{#newline}} {{#newline}} {{/if}} return modelBuilder;{{#newline}} }{{#newline}} {{#if DbContextClassIsPartial}} {{#newline}} partial void InitializePartial();{{#newline}} partial void DisposePartial(bool disposing);{{#newline}} partial void OnModelCreatingPartial(DbModelBuilder modelBuilder);{{#newline}} static partial void OnCreateModelPartial(DbModelBuilder modelBuilder, string schema);{{#newline}} {{/if}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{#if SingleReturnModel}} {{{#newline}} int procResult;{{#newline}} return {{FunctionName}}({{WriteStoredProcFunctionOverloadCall}});{{#newline}} }{{#newline}} {{#newline}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{/if}} {{{#newline}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}} {{#if SingleReturnModel}} var procResultData = Database.SqlQuery<{{WriteStoredProcReturnModelName}}>(""{{Exec}}""{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrue}}).ToList();{{#newline}} {{WriteStoredProcFunctionSetSqlParametersFalse}} procResult = (int) procResultParam.Value;{{#newline}} {{#else}} var procResultData = new {{WriteStoredProcReturnModelName}}();{{#newline}} var cmd = Database.Connection.CreateCommand();{{#newline}} cmd.CommandTimeout = Database.CommandTimeout ?? cmd.CommandTimeout;{{#newline}} cmd.CommandType = CommandType.StoredProcedure;{{#newline}} cmd.CommandText = ""{{Exec}}"";{{#newline}} {{#each Parameters}} cmd.Parameters.Add({{this}});{{#newline}} {{/each}} {{#newline}} try{{#newline}} {{{#newline}} DbInterception.Dispatch.Connection.Open(Database.Connection, new DbInterceptionContext());{{#newline}} var reader = cmd.ExecuteReader();{{#newline}} var objectContext = ((IObjectContextAdapter) this).ObjectContext;{{#newline}} {{#each ReturnModelResultSetReaderCommand}} {{#newline}} procResultData.ResultSet{{Index}} = objectContext.Translate<{{WriteStoredProcReturnModelName}}.ResultSetModel{{Index}}>(reader).ToList();{{#newline}} reader.{{ReaderCommand}}();{{#newline}} {{/each}} }{{#newline}} finally{{#newline}} {{{#newline}} DbInterception.Dispatch.Connection.Close(Database.Connection, new DbInterceptionContext());{{#newline}} }{{#newline}} {{#newline}} {{WriteStoredProcFunctionSetSqlParametersFalse}} {{/if}} return procResultData;{{#newline}} }{{#newline}} {{#else}}{{#! if HasReturnModels }} public int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}}{{#newline}} Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction, ""{{ExecWithNoReturnModel}}""{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrue}});{{#newline}} {{#newline}} {{WriteStoredProcFunctionSetSqlParametersFalse}} return (int)procResultParam.Value;{{#newline}} }{{#newline}} {{/if}}{{#! if HasReturnModels }} {{#newline}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}}{{#! if AsyncFunctionCannotBeCreated }} public async Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} {{#if HasNoReturnModels}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}} {{#newline}} await Database.ExecuteSqlCommandAsync(TransactionalBehavior.DoNotEnsureTransaction, ""{{AsyncExec}}""{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrueToken}});{{#newline}} {{#newline}} return (int)procResultParam.Value;{{#newline}} {{#else}} {{WriteStoredProcFunctionDeclareSqlParameterFalse}} {{#if SingleReturnModel}} var procResultData = await Database.SqlQuery<{{WriteStoredProcReturnModelName}}>(""{{AsyncExec}}""{{WriteStoredProcFunctionSqlParameterAnonymousArrayFalse}}).ToListAsync(cancellationToken);{{#newline}} {{#else}} var procResultData = new {{WriteStoredProcReturnModelName}}();{{#newline}} var cmd = Database.Connection.CreateCommand();{{#newline}} cmd.CommandTimeout = Database.CommandTimeout ?? cmd.CommandTimeout;{{#newline}} cmd.CommandType = CommandType.StoredProcedure;{{#newline}} cmd.CommandText = ""{{Exec}}"";{{#newline}} {{#each Parameters}} cmd.Parameters.Add({{this}});{{#newline}} {{/each}} {{#newline}} try{{#newline}} {{{#newline}} await DbInterception.Dispatch.Connection.OpenAsync(Database.Connection, new DbInterceptionContext(), new CancellationToken()).ConfigureAwait(false);{{#newline}} var reader = await cmd.ExecuteReaderAsync().ConfigureAwait(false);{{#newline}} var objectContext = ((IObjectContextAdapter) this).ObjectContext;{{#newline}} {{#each ReturnModelResultSetReaderCommand}} {{#newline}} procResultData.ResultSet{{Index}} = objectContext.Translate<{{WriteStoredProcReturnModelName}}.ResultSetModel{{Index}}>(reader).ToList();{{#newline}} {{#if NotLastRecord}} await reader.NextResultAsync().ConfigureAwait(false);{{#newline}} {{/if}} {{/each}} }{{#newline}} finally{{#newline}} {{{#newline}} DbInterception.Dispatch.Connection.Close(Database.Connection, new DbInterceptionContext());{{#newline}} }{{#newline}} {{#newline}} {{WriteStoredProcFunctionSetSqlParametersFalse}} {{/if}}{{#! if AsyncFunctionCannotBeCreated }} return procResultData;{{#newline}} {{/if}} }{{#newline}} {{#newline}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#newline}} [DbFunction(""{{DbContextName}}"", ""{{Name}}"")]{{#newline}} [CodeFirstStoreFunctions.DbFunctionDetails(DatabaseSchema = ""{{Schema}}""{{#if SingleReturnModel}}, ResultColumnName = ""{{SingleReturnColumnName}}""{{/if}})]{{#newline}} public IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} {{WriteTableValuedFunctionDeclareSqlParameter}} {{#newline}} return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<{{ReturnClassName}}>(""[{{DbContextName}}].[{{Name}}]({{WriteStoredProcFunctionSqlAtParams}})"", {{WriteTableValuedFunctionSqlParameterAnonymousArray}});{{#newline}} }{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{#newline}} [DbFunction(""CodeFirstDatabaseSchema"", ""{{Name}}"")]{{#newline}} public {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} throw new Exception(""Don't call this directly. Use LINQ to call the scalar valued function as part of your query"");{{#newline}} }{{#newline}} {{/each}} {{/if}} }"; } public override List DatabaseContextFactoryUsings(FactoryModel data) { var usings = new List { "System", "System.Data.Entity.Infrastructure" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string DatabaseContextFactory() { return @" {{classModifier}} class {{contextName}}Factory : IDbContextFactory<{{contextName}}>{{#newline}} {{{#newline}} public {{contextName}} Create(){{#newline}} {{{#newline}} return new {{contextName}}();{{#newline}} }{{#newline}} }"; } public override List FakeDatabaseContextUsings(FakeContextModel data, IDbContextFilter filter) { var usings = new List { "System", "System.Data", "System.Data.Common", "System.Data.Entity", "System.Data.Entity.Core.Objects", "System.Data.Entity.Infrastructure", "System.Data.Entity.Infrastructure.Interception", "System.Data.Entity.Infrastructure.Annotations", "System.Data.SqlClient", "System.Data.Entity.Spatial", "System.Data.SqlTypes", "System.Threading.Tasks", "System.Threading" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("System.Data.Entity"); usings.Add("System.Linq"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Data.Entity"); usings.Add("System.Data.Entity.Infrastructure"); usings.Add("System.Collections.Generic"); usings.Add("System.Data.Entity.Validation"); } if (Settings.DatabaseType == DatabaseType.SqlCe) { usings.Add("System.Data.SqlClient"); //usings.Add("System.DBNull"); usings.Add("System.Data.SqlTypes"); } if (Settings.DatabaseType == DatabaseType.PostgreSQL) { usings.Add("Npgsql"); usings.Add("NpgsqlTypes"); } return usings; } public override string FakeDatabaseContext() { return @" {{DbContextClassModifiers}} class Fake{{DbContextName}}{{contextInterface}}{{#newline}} {{{#newline}} {{#each tables}} {{DbSetModifier}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#newline}} public Fake{{DbContextName}}(){{#newline}} {{{#newline}} _changeTracker = null;{{#newline}} _configuration = null;{{#newline}} _database = null;{{#newline}} {{#newline}} {{#each tables}} {{PluralTableName}} = new FakeDbSet<{{DbSetName}}>({{DbSetPrimaryKeys}});{{#newline}} {{/each}} {{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}} {{#newline}} public int SaveChangesCount { get; private set; }{{#newline}} public int SaveChanges(){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return 1;{{#newline}} }{{#newline}} {{#newline}} public Task SaveChangesAsync(){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return Task.Factory.StartNew(() => 1);{{#newline}} }{{#newline}}{{#newline}} public Task SaveChangesAsync(CancellationToken cancellationToken){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return Task.Factory.StartNew(() => 1, cancellationToken);{{#newline}} }{{#newline}} {{#newline}} {{#if DbContextClassIsPartial}} partial void InitializePartial();{{#newline}} {{#newline}} {{/if}} protected virtual void Dispose(bool disposing){{#newline}} {{{#newline}} }{{#newline}} {{#newline}} public void Dispose(){{#newline}} {{{#newline}} Dispose(true);{{#newline}} }{{#newline}} {{#newline}} private DbChangeTracker _changeTracker;{{#newline}} {{#newline}} public DbChangeTracker ChangeTracker { get { return _changeTracker; } }{{#newline}} {{#newline}} private DbContextConfiguration _configuration;{{#newline}} {{#newline}} public DbContextConfiguration Configuration { get { return _configuration; } }{{#newline}} {{#newline}} private Database _database;{{#newline}} {{#newline}} public Database Database { get { return _database; } }{{#newline}} {{#newline}} public DbEntityEntry Entry(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}} {{#newline}} public DbEntityEntry Entry(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}} {{#newline}} public IEnumerable GetValidationErrors(){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}} {{#newline}} public DbSet Set(Type entityType){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}} {{#newline}} public DbSet Set() where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}} {{#newline}} public override string ToString(){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#newline}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return {{FunctionName}}({{WriteStoredProcFunctionOverloadCall}});{{#newline}} }{{#newline}}{{#newline}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} procResult = 0;{{#newline}} return new {{ReturnType}}();{{#newline}} }{{#newline}} {{#newline}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return Task.FromResult({{FunctionName}}({{WriteStoredProcFunctionOverloadCall}}));{{#newline}} }{{#newline}} {{/if}} {{#else}} {{#newline}} public int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} return 0;{{#newline}} }{{#newline}} {{#newline}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public Task {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} return Task.FromResult(0);{{#newline}} }{{#newline}} {{/if}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#newline}} [DbFunction(""{{DbContextName}}"", ""{{Name}}"")]{{#newline}} public IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return new List<{{ReturnClassName}}>().AsQueryable();{{#newline}} }{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return default({{ReturnType}});{{#newline}} }{{#newline}} {{/each}} {{/if}} }"; } public override List FakeDbSetUsings(FakeDbSetModel data) { var usings = new List { "System", "System.Collections", "System.Linq", "System.Linq.Expressions", "System.Reflection", "System.Data.Entity", "System.Collections.ObjectModel", "System.Collections.Generic", "System.Data.Entity.Infrastructure", "System.Threading", "System.Threading.Tasks" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string FakeDbSet() { return @" // ************************************************************************{{#newline}} // Fake DbSet{{#newline}} // Implementing Find:{{#newline}} // The Find method is difficult to implement in a generic fashion. If{{#newline}} // you need to test code that makes use of the Find method it is{{#newline}} // easiest to create a test DbSet for each of the entity types that{{#newline}} // need to support find. You can then write logic to find that{{#newline}} // particular type of entity, as shown below:{{#newline}} // public class FakeBlogDbSet : FakeDbSet{{#newline}} // {{{#newline}} // public override Blog Find(params object[] keyValues){{#newline}} // {{{#newline}} // var id = (int) keyValues.Single();{{#newline}} // return this.SingleOrDefault(b => b.BlogId == id);{{#newline}} // }{{#newline}} // }{{#newline}} // Read more about it here: https://msdn.microsoft.com/en-us/data/dn314431.aspx{{#newline}} {{DbContextClassModifiers}} class FakeDbSet : DbSet, IQueryable, IEnumerable, IDbAsyncEnumerable where TEntity : class {{#newline}} {{{#newline}} private readonly PropertyInfo[] _primaryKeys;{{#newline}} private readonly ObservableCollection _data;{{#newline}} private readonly IQueryable _query;{{#newline}}{{#newline}} public FakeDbSet(){{#newline}} {{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} public FakeDbSet(params string[] primaryKeys){{#newline}} {{{#newline}} _primaryKeys = typeof(TEntity).GetProperties().Where(x => primaryKeys.Contains(x.Name)).ToArray();{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} public override TEntity Find(params object[] keyValues){{#newline}} {{{#newline}} if (_primaryKeys == null){{#newline}} throw new ArgumentException(""No primary keys defined"");{{#newline}} if (keyValues.Length != _primaryKeys.Length){{#newline}} throw new ArgumentException(""Incorrect number of keys passed to Find method"");{{#newline}}{{#newline}} var keyQuery = this.AsQueryable();{{#newline}} keyQuery = keyValues{{#newline}} .Select((t, i) => i){{#newline}} .Aggregate(keyQuery,{{#newline}} (current, x) =>{{#newline}} current.Where(entity => _primaryKeys[x].GetValue(entity, null).Equals(keyValues[x])));{{#newline}}{{#newline}} return keyQuery.SingleOrDefault();{{#newline}} }{{#newline}}{{#newline}} public override Task FindAsync(CancellationToken cancellationToken, params object[] keyValues){{#newline}} {{{#newline}} return Task.Factory.StartNew(() => Find(keyValues), cancellationToken);{{#newline}} }{{#newline}}{{#newline}} public override Task FindAsync(params object[] keyValues){{#newline}} {{{#newline}} return Task.Factory.StartNew(() => Find(keyValues));{{#newline}} }{{#newline}}{{#newline}} public override IEnumerable AddRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} var items = entities.ToList();{{#newline}} foreach (var entity in items){{#newline}} {{{#newline}} _data.Add(entity);{{#newline}} }{{#newline}} return items;{{#newline}} }{{#newline}}{{#newline}} public override TEntity Add(TEntity item){{#newline}} {{{#newline}} if (item == null) throw new ArgumentNullException(""item"");{{#newline}} _data.Add(item);{{#newline}} return item;{{#newline}} }{{#newline}}{{#newline}} public override IEnumerable RemoveRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} var items = entities.ToList();{{#newline}} foreach (var entity in items){{#newline}} {{{#newline}} _data.Remove(entity);{{#newline}} }{{#newline}} return items;{{#newline}} }{{#newline}}{{#newline}} public override TEntity Remove(TEntity item){{#newline}} {{{#newline}} if (item == null) throw new ArgumentNullException(""item"");{{#newline}} _data.Remove(item);{{#newline}} return item;{{#newline}} }{{#newline}}{{#newline}} public override TEntity Attach(TEntity item){{#newline}} {{{#newline}} if (item == null) throw new ArgumentNullException(""item"");{{#newline}} _data.Add(item);{{#newline}} return item;{{#newline}} }{{#newline}}{{#newline}} public override TEntity Create(){{#newline}} {{{#newline}} return Activator.CreateInstance();{{#newline}} }{{#newline}}{{#newline}} public override TDerivedEntity Create(){{#newline}} {{{#newline}} return Activator.CreateInstance();{{#newline}} }{{#newline}}{{#newline}} public override ObservableCollection Local{{#newline}} {{{#newline}} get { return _data; }{{#newline}} }{{#newline}}{{#newline}} Type IQueryable.ElementType{{#newline}} {{{#newline}} get { return _query.ElementType; }{{#newline}} }{{#newline}}{{#newline}} Expression IQueryable.Expression{{#newline}} {{{#newline}} get { return _query.Expression; }{{#newline}} }{{#newline}}{{#newline}} IQueryProvider IQueryable.Provider{{#newline}} {{{#newline}} get { return new FakeDbAsyncQueryProvider(_query.Provider); }{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return _data.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return _data.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator(){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(_data.GetEnumerator());{{#newline}} }{{#newline}} {{#if DbContextClassIsPartial}} {{#newline}} partial void InitializePartial();{{#newline}} {{/if}} } {{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncQueryProvider : IDbAsyncQueryProvider{{#newline}} {{{#newline}} private readonly IQueryProvider _inner;{{#newline}}{{#newline}} public FakeDbAsyncQueryProvider(IQueryProvider inner){{#newline}} {{{#newline}} _inner = inner;{{#newline}} }{{#newline}}{{#newline}} public IQueryable CreateQuery(Expression expression){{#newline}} {{{#newline}} var m = expression as MethodCallExpression;{{#newline}} if (m != null){{#newline}} {{{#newline}} var resultType = m.Method.ReturnType; // it should be IQueryable{{#newline}} var tElement = resultType.GetGenericArguments()[0];{{#newline}} var queryType = typeof(FakeDbAsyncEnumerable<>).MakeGenericType(tElement);{{#newline}} return (IQueryable) Activator.CreateInstance(queryType, expression);{{#newline}} }{{#newline}} return new FakeDbAsyncEnumerable(expression);{{#newline}} }{{#newline}}{{#newline}} public IQueryable CreateQuery(Expression expression){{#newline}} {{{#newline}} var queryType = typeof(FakeDbAsyncEnumerable<>).MakeGenericType(typeof(TElement));{{#newline}} return (IQueryable) Activator.CreateInstance(queryType, expression);{{#newline}} }{{#newline}}{{#newline}} public object Execute(Expression expression){{#newline}} {{{#newline}} return _inner.Execute(expression);{{#newline}} }{{#newline}}{{#newline}} public TResult Execute(Expression expression){{#newline}} {{{#newline}} return _inner.Execute(expression);{{#newline}} }{{#newline}}{{#newline}} public Task ExecuteAsync(Expression expression, CancellationToken cancellationToken){{#newline}} {{{#newline}} return Task.FromResult(Execute(expression));{{#newline}} }{{#newline}}{{#newline}} public Task ExecuteAsync(Expression expression, CancellationToken cancellationToken){{#newline}} {{{#newline}} return Task.FromResult(Execute(expression));{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncEnumerable : EnumerableQuery, IDbAsyncEnumerable, IQueryable{{#newline}} {{{#newline}} public FakeDbAsyncEnumerable(IEnumerable enumerable){{#newline}} : base(enumerable){{#newline}} { }{{#newline}}{{#newline}} public FakeDbAsyncEnumerable(Expression expression){{#newline}} : base(expression){{#newline}} { }{{#newline}}{{#newline}} public IDbAsyncEnumerator GetAsyncEnumerator(){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}}{{#newline}} IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator(){{#newline}} {{{#newline}} return GetAsyncEnumerator();{{#newline}} }{{#newline}}{{#newline}} IQueryProvider IQueryable.Provider{{#newline}} {{{#newline}} get { return new FakeDbAsyncQueryProvider(this); }{{#newline}} }{{#newline}}{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncEnumerator : IDbAsyncEnumerator{{#newline}} {{{#newline}} private readonly IEnumerator _inner;{{#newline}}{{#newline}} public FakeDbAsyncEnumerator(IEnumerator inner){{#newline}} {{{#newline}} _inner = inner;{{#newline}} }{{#newline}}{{#newline}} public void Dispose(){{#newline}} {{{#newline}} _inner.Dispose();{{#newline}} }{{#newline}}{{#newline}} public Task MoveNextAsync(CancellationToken cancellationToken){{#newline}} {{{#newline}} return Task.FromResult(_inner.MoveNext());{{#newline}} }{{#newline}}{{#newline}} public T Current{{#newline}} {{{#newline}} get { return _inner.Current; }{{#newline}} }{{#newline}}{{#newline}} object IDbAsyncEnumerator.Current{{#newline}} {{{#newline}} get { return Current; }{{#newline}} }{{#newline}} }"; } public override List PocoUsings(PocoModel data) { var usings = new List { "System", "System.Data.Entity.Infrastructure", "System.Collections.Generic", "System.Threading", "System.Threading.Tasks", }; if (data.HasSpatial) usings.Add("System.Data.Entity.Spatial"); if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string Poco() { return @" {{#if HasNoPrimaryKey}} // The table '{{Name}}' is not usable by entity framework because it{{#newline}} // does not have a primary key. It is listed here for completeness.{{#newline}} {{/if}} {{ClassComment}} {{ExtendedComments}} {{ClassAttributes}} {{ClassModifier}} class {{NameHumanCaseWithSuffix}}{{BaseClasses}}{{#newline}} {{{#newline}} {{InsideClassBody}} {{#each Columns}} {{#if AddNewLineBefore}}{{#newline}}{{/if}} {{#if HasSummaryComments}} /// {{#newline}} /// {{SummaryComments}}{{#newline}} /// {{#newline}} {{/if}} {{#each Attributes}} {{this}}{{#newline}} {{/each}} public {{#if OverrideModifier}}override {{/if}}{{WrapIfNullable}} {{NameHumanCase}} { get; {{PrivateSetterForComputedColumns}}set; }{{PropertyInitialisers}}{{InlineComments}}{{#newline}} {{#if IncludeFieldNameConstants}} public const string {{NameHumanCase}}Field = ""{{NameHumanCase}}"";{{#newline}}{{/if}} {{/each}} {{#if HasReverseNavigation}} {{#newline}} // Reverse navigation{{#newline}} {{#each ReverseNavigationProperty}} {{#if ReverseNavHasComment}} {{#newline}} /// {{#newline}} /// {{ReverseNavComment}}{{#newline}} /// {{#newline}} {{/if}} {{#each AdditionalReverseNavigationsDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{#each AdditionalDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{Definition}}{{#newline}} {{/each}} {{/if}} {{#if HasForeignKey}} {{#newline}} {{ForeignKeyTitleComment}} {{#each ForeignKeys}} {{#if HasFkComment}} {{#newline}} /// {{#newline}} /// {{FkComment}}{{#newline}} /// {{#newline}} {{/if}} {{#each AdditionalForeignKeysDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{#each AdditionalDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{Definition}}{{#newline}} {{/each}} {{/if}} {{#if CreateConstructor}} {{#newline}} public {{NameHumanCaseWithSuffix}}(){{#newline}} {{{#newline}} {{#each ColumnsWithDefaults}} {{NameHumanCase}} = {{Default}};{{#newline}} {{/each}} {{#each ReverseNavigationCtor}} {{this}}{{#newline}} {{/each}} {{#if EntityClassesArePartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}} {{#if EntityClassesArePartial}} {{#newline}} partial void InitializePartial();{{#newline}} {{/if}} {{/if}} }{{#newline}} "; } public override List PocoConfigurationUsings(PocoConfigurationModel data) { var usings = new List { "System", "System.Data.Entity.Spatial", "System.Data.Entity.ModelConfiguration", "System.ComponentModel.DataAnnotations.Schema" }; if (data.HasSpatial) usings.Add("System.Data.Entity.Spatial"); if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string PocoConfiguration() { return @" {{ClassComment}} {{ClassModifier}} class {{ConfigurationClassName}} : EntityTypeConfiguration<{{NameHumanCaseWithSuffix}}>{{#newline}} {{{#newline}} public {{ConfigurationClassName}}(){{#newline}} : this(""{{Schema}}""){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public {{ConfigurationClassName}}(string schema){{#newline}} {{{#newline}} {{#if NotUsingDataAnnotations}} {{#if HasSchema}} ToTable(""{{Name}}"", schema);{{#newline}} {{#else}} ToTable(""{{Name}}"");{{#newline}} {{/if}} {{/if}} HasKey({{PrimaryKeyNameHumanCase}});{{#newline}}{{#newline}} {{#each Columns}} {{this}}{{#newline}} {{/each}} {{#if HasForeignKey}} {{#newline}} // Foreign keys{{#newline}} {{#each ForeignKeys}} {{this}}{{#newline}} {{/each}} {{/if}} {{#each MappingConfiguration}} {{this}}{{#newline}} {{/each}} {{#if ConfigurationClassesArePartial}} {{#newline}} InitializePartial();{{#newline}} {{/if}} }{{#newline}} {{#if ConfigurationClassesArePartial}} {{#newline}} partial void InitializePartial();{{#newline}} {{/if}} }{{#newline}}"; } public override List StoredProcReturnModelUsings() { var usings = new List { "System", "System.Collections.Generic" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string StoredProcReturnModels() { return @" {{ResultClassModifiers}} class {{WriteStoredProcReturnModelName}}{{#newline}} {{{#newline}} {{#if SingleModel}} {{#each SingleModelReturnColumns}} {{this}}{{#newline}} {{/each}} {{#else}} {{#each MultipleModelReturnColumns}} public class ResultSetModel{{Model}}{{#newline}} {{{#newline}} {{#each ReturnColumns}} {{this}}{{#newline}} {{/each}} }{{#newline}} public List ResultSet{{Model}}{{PropertyGetSet}}{{#newline}} {{/each}} {{/if}} }{{#newline}} "; } public override List EnumUsings() { var usings = new List(); if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string Enums() { return @" {{#each EnumAttributes}} {{this}}{{#newline}} {{/each}} public enum {{EnumName}}{{#newline}} {{{#newline}} {{#each Items}} {{#each Attributes}} {{this}}{{#newline}} {{/each}} {{Key}} = {{Value}},{{#newline}} {{/each}} }{{#newline}} "; } } /// /// {{Mustache}} template documentation available at https://github.com/jehugaleahsa/mustache-sharp /// public class TemplateEfCore3 : Template { public override string Usings() { return @" {{#each this}} using {{this}};{{#newline}} {{/each}}"; } public override List DatabaseContextInterfaceUsings(InterfaceModel data) { var usings = new List { "System", "System.Data", "System.Threading.Tasks", "System.Threading" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("Microsoft.EntityFrameworkCore"); usings.Add("Microsoft.EntityFrameworkCore.Infrastructure"); usings.Add("System.Linq"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Collections.Generic"); usings.Add("Microsoft.EntityFrameworkCore.ChangeTracking"); } return usings; } public override string DatabaseContextInterface() { return @" {{interfaceModifier}} interface {{DbContextInterfaceName}} : {{DbContextInterfaceBaseClasses}}{{#newline}} {{{#newline}} {{#each tables}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#if AdditionalContextInterfaceItems}} {{#newline}} // Additional interface items{{#newline}} {{/if}} {{#each AdditionalContextInterfaceItems}} {{this}}{{#newline}} {{/each}} {{#if addSaveChanges}} {{#newline}} int SaveChanges();{{#newline}} int SaveChanges(bool acceptAllChangesOnSuccess);{{#newline}} Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));{{#newline}} Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken));{{#newline}} DatabaseFacade Database { get; }{{#newline}} DbSet Set() where TEntity : class;{{#newline}} string ToString();{{#newline}}{{#newline}} EntityEntry Add(object entity);{{#newline}} EntityEntry Add(TEntity entity) where TEntity : class;{{#newline}} Task AddRangeAsync(params object[] entities);{{#newline}} Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default);{{#newline}} ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class;{{#newline}} ValueTask AddAsync(object entity, CancellationToken cancellationToken = default);{{#newline}} void AddRange(IEnumerable entities);{{#newline}} void AddRange(params object[] entities);{{#newline}}{{#newline}} EntityEntry Attach(object entity);{{#newline}} EntityEntry Attach(TEntity entity) where TEntity : class;{{#newline}} void AttachRange(IEnumerable entities);{{#newline}} void AttachRange(params object[] entities);{{#newline}}{{#newline}} EntityEntry Entry(object entity);{{#newline}} EntityEntry Entry(TEntity entity) where TEntity : class;{{#newline}}{{#newline}} TEntity Find(params object[] keyValues) where TEntity : class;{{#newline}} ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) where TEntity : class;{{#newline}} ValueTask FindAsync(params object[] keyValues) where TEntity : class;{{#newline}} ValueTask FindAsync(Type entityType, object[] keyValues, CancellationToken cancellationToken);{{#newline}} ValueTask FindAsync(Type entityType, params object[] keyValues);{{#newline}} object Find(Type entityType, params object[] keyValues);{{#newline}}{{#newline}} EntityEntry Remove(object entity);{{#newline}} EntityEntry Remove(TEntity entity) where TEntity : class;{{#newline}} void RemoveRange(IEnumerable entities);{{#newline}} void RemoveRange(params object[] entities);{{#newline}}{{#newline}} EntityEntry Update(object entity);{{#newline}} EntityEntry Update(TEntity entity) where TEntity : class;{{#newline}} void UpdateRange(IEnumerable entities);{{#newline}} void UpdateRange(params object[] entities);{{#newline}}{{#newline}} {{/if}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#if MultipleReturnModels}} // {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseTrue}}); Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#else}} {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseTrue}});{{#newline}} {{/if}} {{#if SingleReturnModel}} {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueTrue}});{{#newline}} {{/if}} {{#else}} int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueTrue}});{{#newline}} {{/if}} {{#if MultipleReturnModels}} // Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseTrue}}); Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#else}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseTrueToken}});{{#newline}} {{/if}} {{/if}} {{#newline}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseTrue}}); // {{Schema}}.{{Name}}{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseTrue}}); // {{Schema}}.{{Name}}{{#newline}} {{/each}} {{/if}} }"; } public override List DatabaseContextUsings(ContextModel data) { var usings = new List { "System", "System.Data", "System.Data.SqlTypes", "Microsoft.EntityFrameworkCore", "System.Threading.Tasks", "System.Threading" }; switch(Settings.DatabaseType) { case DatabaseType.SqlServer: case DatabaseType.SqlCe: case DatabaseType.Plugin: usings.Add("Microsoft.Data.SqlClient"); break; case DatabaseType.SQLite: usings.Add("Microsoft.Data.Sqlite"); break; case DatabaseType.PostgreSQL: break; case DatabaseType.MySql: break; case DatabaseType.Oracle: break; } if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("System.Linq"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if(Settings.OnConfiguration == OnConfiguration.Configuration) usings.Add("Microsoft.Extensions.Configuration"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Collections.Generic"); usings.Add("Microsoft.EntityFrameworkCore.ChangeTracking"); } return usings; } public override string DatabaseContext() { return @" {{DbContextClassModifiers}} class {{DbContextName}} : {{DbContextBaseClass}}{{contextInterface}}{{#newline}} {{{#newline}} {{#if OnConfigurationUsesConfiguration}} private readonly IConfiguration _configuration;{{#newline}}{{#newline}} {{/if}} {{#if AddParameterlessConstructorToDbContext}} public {{DbContextName}}(){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{/if}} public {{DbContextName}}(DbContextOptions<{{DbContextName}}> options){{#newline}} : base(options){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{#if OnConfigurationUsesConfiguration}} public {{DbContextName}}(IConfiguration configuration){{#newline}} {{{#newline}} _configuration = configuration;{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{/if}} {{#each tables}} {{DbSetModifier}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#newline}} {{#if OnConfigurationUsesConfiguration}} protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){{#newline}} {{{#newline}} if (!optionsBuilder.IsConfigured && _configuration != null){{#newline}} {{{#newline}} optionsBuilder.{{UseDatabaseProvider}}(_configuration.GetConnectionString(@""{{ConnectionStringName}}""){{ConnectionStringActions}});{{#newline}} {{#if UseLazyLoadingProxies}} optionsBuilder.UseLazyLoadingProxies();{{#newline}} {{/if}} }{{#newline}} }{{#newline}}{{#newline}} {{/if}} {{#if OnConfigurationUsesConnectionString}} protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){{#newline}} {{{#newline}} if (!optionsBuilder.IsConfigured){{#newline}} {{{#newline}} optionsBuilder.{{UseDatabaseProvider}}(@""{{ConnectionString}}""{{ConnectionStringActions}});{{#newline}} {{#if UseLazyLoadingProxies}} optionsBuilder.UseLazyLoadingProxies();{{#newline}} {{/if}} }{{#newline}} }{{#newline}}{{#newline}} {{/if}} public bool IsSqlParameterNull({{SqlParameter}} param){{#newline}} {{{#newline}} var sqlValue = param.{{SqlParameterValue}};{{#newline}} var nullableValue = sqlValue as INullable;{{#newline}} if (nullableValue != null){{#newline}} return nullableValue.IsNull;{{#newline}} return (sqlValue == null || sqlValue == DBNull.Value);{{#newline}} }{{#newline}}{{#newline}} protected override void OnModelCreating(ModelBuilder modelBuilder){{#newline}} {{{#newline}} base.OnModelCreating(modelBuilder);{{#newline}} {{#if hasSequences}} {{#newline}} {{#each Sequences}} modelBuilder.HasSequence<{{DataType}}>(""{{Name}}"", ""{{Schema}}"").StartsAt({{StartValue}}).IncrementsBy({{IncrementValue}}).IsCyclic({{IsCycleEnabled}}) {{#if hasMinValue}} .HasMin({{MinValue}}) {{/if}} {{#if hasMaxValue}} .HasMax({{MaxValue}}) {{/if}} ;{{#newline}} {{/each}} {{/if}} {{#if hasTables}} {{#newline}} {{#each tables}} modelBuilder.ApplyConfiguration(new {{DbSetConfigName}}());{{#newline}} {{/each}} {{/if}} {{#if hasStoredProcs}} {{#newline}} {{#each storedProcs}} {{#if SingleReturnModel}} modelBuilder.{{StoredProcModelBuilderCommand}}<{{ReturnModelName}}>(){{StoredProcModelBuilderPostCommand}};{{#newline}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#if IncludeModelBuilder}} modelBuilder.{{ModelBuilderCommand}}<{{ReturnClassName}}>(){{ModelBuilderPostCommand}};{{#newline}} {{/if}} {{/each}} {{/if}} {{#if DbContextClassIsPartial}} {{#newline}} OnModelCreatingPartial(modelBuilder);{{#newline}} {{/if}} }{{#newline}} {{#newline}} {{#if DbContextClassIsPartial}} {{#newline}} partial void InitializePartial();{{#newline}} partial void DisposePartial(bool disposing);{{#newline}} partial void OnModelCreatingPartial(ModelBuilder modelBuilder);{{#newline}} static partial void OnCreateModelPartial(ModelBuilder modelBuilder, string schema);{{#newline}} {{/if}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#if MultipleReturnModels}} // public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}) Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#else}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return {{FunctionName}}({{WriteStoredProcFunctionOverloadCall}});{{#newline}} }{{#newline}}{{#newline}} {{/if}} {{#if SingleReturnModel}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}} const string sqlCommand = ""{{Exec}}"";{{#newline}} var procResultData = {{QueryString}}<{{ReturnModelName}}>(){{#newline}} .{{FromSql}}(sqlCommand{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrue}}){{#newline}} .ToList();{{#newline}}{{#newline}} {{WriteStoredProcFunctionSetSqlParametersFalse}} procResult = (int) procResultParam.Value;{{#newline}} return procResultData;{{#newline}} }{{#newline}} {{/if}} {{#else}} public int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}}{{#newline}} Database.{{ExecuteSqlCommand}}(""{{ExecWithNoReturnModel}}""{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrue}});{{#newline}} {{#newline}} {{WriteStoredProcFunctionSetSqlParametersFalse}} return (int)procResultParam.Value;{{#newline}} }{{#newline}} {{/if}} {{#newline}} {{#if MultipleReturnModels}} // public async Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}) Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#newline}} {{#else}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public async Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} {{#if HasNoReturnModels}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}} {{#newline}} await Database.ExecuteSqlRawAsync(""{{AsyncExec}}""{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrueToken}});{{#newline}} {{#newline}} return (int)procResultParam.Value;{{#newline}} {{#else}} {{WriteStoredProcFunctionDeclareSqlParameterFalse}} {{WriteStoredProcFunctionSetSqlParametersFalse}} const string sqlCommand = ""{{AsyncExec}}"";{{#newline}} var procResultData = await {{QueryString}}<{{ReturnModelName}}>(){{#newline}} .{{FromSql}}(sqlCommand{{WriteStoredProcFunctionSqlParameterAnonymousArrayFalse}}){{#newline}} .ToListAsync();{{#newline}}{{#newline}} return procResultData;{{#newline}} {{/if}} }{{#newline}} {{/if}} {{#newline}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return {{QueryString}}<{{ReturnClassName}}>(){{#newline}} .{{FromSql}}(""SELECT * FROM [{{Schema}}].[{{Name}}]({{WriteStoredProcFunctionSqlAtParams}})""{{WriteTableValuedFunctionSqlParameterAnonymousArray}}){{#newline}} .AsNoTracking();{{#newline}} }{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{#newline}} [DbFunction(""{{Name}}"", ""{{Schema}}"")]{{#newline}} public {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} throw new Exception(""Don't call this directly. Use LINQ to call the scalar valued function as part of your query"");{{#newline}} }{{#newline}} {{/each}} {{/if}} }"; } public override List DatabaseContextFactoryUsings(FactoryModel data) { var usings = new List { "Microsoft.EntityFrameworkCore.Design" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string DatabaseContextFactory() { return @" {{classModifier}} class {{contextName}}Factory : IDesignTimeDbContextFactory<{{contextName}}>{{#newline}} {{{#newline}} public {{contextName}} CreateDbContext(string[] args){{#newline}} {{{#newline}} return new {{contextName}}();{{#newline}} }{{#newline}} }"; } public override List FakeDatabaseContextUsings(FakeContextModel data, IDbContextFilter filter) { var usings = new List { "System", "System.Data", "System.Threading.Tasks", "System.Threading", "Microsoft.EntityFrameworkCore.Infrastructure" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("System.Linq"); usings.Add("Microsoft.EntityFrameworkCore"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Collections.Generic"); usings.Add("Microsoft.EntityFrameworkCore.ChangeTracking"); } if (Settings.DatabaseType == DatabaseType.SqlCe) { usings.Add("Microsoft.Data.SqlClient"); //usings.Add("System.DBNull"); usings.Add("System.Data.SqlTypes"); } if (Settings.DatabaseType == DatabaseType.PostgreSQL) { usings.Add("Npgsql"); usings.Add("NpgsqlTypes"); } return usings; } public override string FakeDatabaseContext() { return @" {{DbContextClassModifiers}} class Fake{{DbContextName}}{{contextInterface}}{{#newline}} {{{#newline}} {{#each tables}} {{DbSetModifier}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#newline}} public Fake{{DbContextName}}(){{#newline}} {{{#newline}} _database = new FakeDatabaseFacade(new {{DbContextName}}());{{#newline}} {{#newline}} {{#each tables}} {{PluralTableName}} = new FakeDbSet<{{DbSetName}}>({{DbSetPrimaryKeys}});{{#newline}} {{/each}} {{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}} {{#newline}} public int SaveChangesCount { get; private set; }{{#newline}} public virtual int SaveChanges(){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return 1;{{#newline}} }{{#newline}}{{#newline}} public virtual int SaveChanges(bool acceptAllChangesOnSuccess){{#newline}} {{{#newline}} return SaveChanges();{{#newline}} }{{#newline}}{{#newline}} public virtual Task SaveChangesAsync(CancellationToken cancellationToken){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return Task.Factory.StartNew(() => 1, cancellationToken);{{#newline}} }{{#newline}} public virtual Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return Task.Factory.StartNew(x => 1, acceptAllChangesOnSuccess, cancellationToken);{{#newline}} }{{#newline}}{{#newline}} {{#if DbContextClassIsPartial}} partial void InitializePartial();{{#newline}} {{#newline}} {{/if}} protected virtual void Dispose(bool disposing){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public void Dispose(){{#newline}} {{{#newline}} Dispose(true);{{#newline}} }{{#newline}}{{#newline}} private DatabaseFacade _database;{{#newline}} public DatabaseFacade Database { get { return _database; } }{{#newline}}{{#newline}} public DbSet Set() where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public override string ToString(){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Add(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Add(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual Task AddRangeAsync(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual async Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} await Task.CompletedTask;{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual async ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class{{#newline}} {{{#newline}} await Task.CompletedTask;{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual async ValueTask AddAsync(object entity, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} await Task.CompletedTask;{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AddRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AddRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Attach(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Attach(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AttachRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AttachRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Entry(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Entry(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual TEntity Find(params object[] keyValues) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(params object[] keyValues) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(Type entityType, object[] keyValues, CancellationToken cancellationToken){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(Type entityType, params object[] keyValues){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual object Find(Type entityType, params object[] keyValues){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Remove(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Remove(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void RemoveRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void RemoveRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Update(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Update(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void UpdateRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void UpdateRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#newline}} {{#if CreateDbSetForReturnModel}} public DbSet<{{ReturnModelName}}> {{ReturnModelName}} { get; set; }{{#newline}} {{/if}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return {{FunctionName}}({{WriteStoredProcFunctionOverloadCall}});{{#newline}} }{{#newline}}{{#newline}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} procResult = 0;{{#newline}} return new {{ReturnType}}();{{#newline}} }{{#newline}} {{#newline}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return Task.FromResult({{FunctionName}}({{WriteStoredProcFunctionOverloadCall}}));{{#newline}} }{{#newline}} {{/if}} {{#else}} {{#newline}} public int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} return 0;{{#newline}} }{{#newline}} {{#newline}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public Task {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} return Task.FromResult(0);{{#newline}} }{{#newline}} {{/if}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return new List<{{ReturnClassName}}>().AsQueryable();{{#newline}} }{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return default({{ReturnType}});{{#newline}} }{{#newline}} {{/each}} {{/if}} }"; } public override List FakeDbSetUsings(FakeDbSetModel data) { var usings = new List { "System", "System.Collections", "System.ComponentModel", "System.Linq", "System.Linq.Expressions", "System.Reflection", "System.Collections.ObjectModel", "System.Collections.Generic", "System.Threading", "System.Threading.Tasks", "Microsoft.EntityFrameworkCore", "Microsoft.EntityFrameworkCore.Query", "Microsoft.EntityFrameworkCore.Query.Internal", "Microsoft.EntityFrameworkCore.Infrastructure", "Microsoft.EntityFrameworkCore.ChangeTracking", "Microsoft.EntityFrameworkCore.Storage" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string FakeDbSet() { return @" // ************************************************************************{{#newline}} // Fake DbSet{{#newline}} // Implementing Find:{{#newline}} // The Find method is difficult to implement in a generic fashion. If{{#newline}} // you need to test code that makes use of the Find method it is{{#newline}} // easiest to create a test DbSet for each of the entity types that{{#newline}} // need to support find. You can then write logic to find that{{#newline}} // particular type of entity, as shown below:{{#newline}} // public class FakeBlogDbSet : FakeDbSet{{#newline}} // {{{#newline}} // public override Blog Find(params object[] keyValues){{#newline}} // {{{#newline}} // var id = (int) keyValues.Single();{{#newline}} // return this.SingleOrDefault(b => b.BlogId == id);{{#newline}} // }{{#newline}} // }{{#newline}} // Read more about it here: https://msdn.microsoft.com/en-us/data/dn314431.aspx{{#newline}} {{DbContextClassModifiers}} class FakeDbSet : DbSet, IQueryable, IAsyncEnumerable, IListSource where TEntity : class{{#newline}} {{{#newline}} private readonly PropertyInfo[] _primaryKeys;{{#newline}} private readonly ObservableCollection _data;{{#newline}} private readonly IQueryable _query;{{#newline}}{{#newline}} public FakeDbSet(){{#newline}} {{{#newline}} _primaryKeys = null;{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} public FakeDbSet(params string[] primaryKeys){{#newline}} {{{#newline}} _primaryKeys = typeof(TEntity).GetProperties().Where(x => primaryKeys.Contains(x.Name)).ToArray();{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} public override TEntity Find(params object[] keyValues){{#newline}} {{{#newline}} if (_primaryKeys == null){{#newline}} throw new ArgumentException(""No primary keys defined"");{{#newline}} if (keyValues.Length != _primaryKeys.Length){{#newline}} throw new ArgumentException(""Incorrect number of keys passed to Find method"");{{#newline}}{{#newline}} var keyQuery = this.AsQueryable();{{#newline}} keyQuery = keyValues{{#newline}} .Select((t, i) => i){{#newline}} .Aggregate(keyQuery,{{#newline}} (current, x) =>{{#newline}} current.Where(entity => _primaryKeys[x].GetValue(entity, null).Equals(keyValues[x])));{{#newline}}{{#newline}} return keyQuery.SingleOrDefault();{{#newline}} }{{#newline}}{{#newline}} public override ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken){{#newline}} {{{#newline}} return new ValueTask(Task.Factory.StartNew(() => Find(keyValues), cancellationToken));{{#newline}} }{{#newline}}{{#newline}} public override ValueTask FindAsync(params object[] keyValues){{#newline}} {{{#newline}} return new ValueTask(Task.Factory.StartNew(() => Find(keyValues)));{{#newline}} }{{#newline}}{{#newline}} IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken){{#newline}} {{{#newline}} return GetAsyncEnumerator(cancellationToken);{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Add(TEntity entity){{#newline}} {{{#newline}} _data.Add(entity);{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} return new ValueTask>(Task>.Factory.StartNew(() => Add(entity), cancellationToken));{{#newline}} }{{#newline}}{{#newline}} public override void AddRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} foreach (var entity in entities){{#newline}} _data.Add(entity);{{#newline}} }{{#newline}}{{#newline}} public override void AddRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} foreach (var entity in entities){{#newline}} _data.Add(entity);{{#newline}} }{{#newline}}{{#newline}} public override Task AddRangeAsync(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} return Task.Factory.StartNew(() => AddRange(entities));{{#newline}} }{{#newline}}{{#newline}} public override Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} return Task.Factory.StartNew(() => AddRange(entities), cancellationToken);{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Attach(TEntity entity){{#newline}} {{{#newline}} if (entity == null) throw new ArgumentNullException(""entity"");{{#newline}} return Add(entity);{{#newline}} }{{#newline}}{{#newline}} public override void AttachRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} AddRange(entities);{{#newline}} }{{#newline}}{{#newline}} public override void AttachRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} AddRange(entities);{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Remove(TEntity entity){{#newline}} {{{#newline}} _data.Remove(entity);{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override void RemoveRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} foreach (var entity in entities.ToList()){{#newline}} _data.Remove(entity);{{#newline}} }{{#newline}}{{#newline}} public override void RemoveRange(IEnumerable entities){{#newline}} {{{#newline}} RemoveRange(entities.ToArray());{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Update(TEntity entity){{#newline}} {{{#newline}} _data.Remove(entity);{{#newline}} _data.Add(entity);{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override void UpdateRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} RemoveRange(entities);{{#newline}} AddRange(entities);{{#newline}} }{{#newline}}{{#newline}} public override void UpdateRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} var array = entities.ToArray(); RemoveRange(array);{{#newline}} AddRange(array);{{#newline}} }{{#newline}}{{#newline}} public IList GetList(){{#newline}} {{{#newline}} return _data;{{#newline}} }{{#newline}}{{#newline}} IList IListSource.GetList(){{#newline}} {{{#newline}} return _data;{{#newline}} }{{#newline}}{{#newline}} Type IQueryable.ElementType{{#newline}} {{{#newline}} get { return _query.ElementType; }{{#newline}} }{{#newline}}{{#newline}} Expression IQueryable.Expression{{#newline}} {{{#newline}} get { return _query.Expression; }{{#newline}} }{{#newline}}{{#newline}} IQueryProvider IQueryable.Provider{{#newline}} {{{#newline}} get { return new FakeDbAsyncQueryProvider(_data); }{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return _data.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return _data.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default(CancellationToken)){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}} {{#if DbContextClassIsPartial}} {{#newline}} partial void InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncQueryProvider : FakeQueryProvider, IAsyncEnumerable, IAsyncQueryProvider{{#newline}} {{{#newline}} public FakeDbAsyncQueryProvider(Expression expression) : base(expression){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public FakeDbAsyncQueryProvider(IEnumerable enumerable) : base(enumerable){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public TResult ExecuteAsync(Expression expression, CancellationToken cancellationToken){{#newline}} {{{#newline}} var expectedResultType = typeof(TResult).GetGenericArguments()[0];{{#newline}} var executionResult = typeof(IQueryProvider){{#newline}} .GetMethods(){{#newline}} .First(method => method.Name == nameof(IQueryProvider.Execute) && method.IsGenericMethod){{#newline}} .MakeGenericMethod(expectedResultType){{#newline}} .Invoke(this, new object[] { expression });{{#newline}}{{#newline}} return (TResult) typeof(Task).GetMethod(nameof(Task.FromResult)){{#newline}} ?.MakeGenericMethod(expectedResultType){{#newline}} .Invoke(null, new[] { executionResult });{{#newline}} }{{#newline}}{{#newline}} public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncEnumerable : EnumerableQuery, IAsyncEnumerable, IQueryable{{#newline}} {{{#newline}} public FakeDbAsyncEnumerable(IEnumerable enumerable){{#newline}} : base(enumerable){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public FakeDbAsyncEnumerable(Expression expression){{#newline}} : base(expression){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}}{{#newline}} IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken){{#newline}} {{{#newline}} return GetAsyncEnumerator(cancellationToken);{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return this.AsEnumerable().GetEnumerator();{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncEnumerator : IAsyncEnumerator{{#newline}} {{{#newline}} private readonly IEnumerator _inner;{{#newline}}{{#newline}} public FakeDbAsyncEnumerator(IEnumerator inner){{#newline}} {{{#newline}} _inner = inner;{{#newline}} }{{#newline}}{{#newline}} public T Current{{#newline}} {{{#newline}} get { return _inner.Current; }{{#newline}} }{{#newline}} public ValueTask MoveNextAsync(){{#newline}} {{{#newline}} return new ValueTask(_inner.MoveNext());{{#newline}} }{{#newline}}{{#newline}} public ValueTask DisposeAsync(){{#newline}} {{{#newline}} _inner.Dispose();{{#newline}} return new ValueTask(Task.CompletedTask);{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} public abstract class FakeQueryProvider : IOrderedQueryable, IQueryProvider{{#newline}} {{{#newline}} private IEnumerable _enumerable;{{#newline}}{{#newline}} protected FakeQueryProvider(Expression expression){{#newline}} {{{#newline}} Expression = expression;{{#newline}} }{{#newline}}{{#newline}} protected FakeQueryProvider(IEnumerable enumerable){{#newline}} {{{#newline}} _enumerable = enumerable;{{#newline}} Expression = enumerable.AsQueryable().Expression;{{#newline}} }{{#newline}}{{#newline}} public IQueryable CreateQuery(Expression expression){{#newline}} {{{#newline}} if (expression is MethodCallExpression m){{#newline}} {{{#newline}} var resultType = m.Method.ReturnType; // it should be IQueryable{{#newline}} var tElement = resultType.GetGenericArguments().First();{{#newline}} return (IQueryable) CreateInstance(tElement, expression);{{#newline}} }{{#newline}}{{#newline}} return CreateQuery(expression);{{#newline}} }{{#newline}}{{#newline}} public IQueryable CreateQuery(Expression expression){{#newline}} {{{#newline}} return (IQueryable) CreateInstance(typeof(TEntity), expression);{{#newline}} }{{#newline}}{{#newline}} private object CreateInstance(Type tElement, Expression expression){{#newline}} {{{#newline}} var queryType = GetType().GetGenericTypeDefinition().MakeGenericType(tElement);{{#newline}} return Activator.CreateInstance(queryType, expression);{{#newline}} }{{#newline}}{{#newline}} public object Execute(Expression expression){{#newline}} {{{#newline}} return CompileExpressionItem(expression);{{#newline}} }{{#newline}}{{#newline}} public TResult Execute(Expression expression){{#newline}} {{{#newline}} return CompileExpressionItem(expression);{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} if (_enumerable == null) _enumerable = CompileExpressionItem>(Expression);{{#newline}} return _enumerable.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} if (_enumerable == null) _enumerable = CompileExpressionItem>(Expression);{{#newline}} return _enumerable.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} public Type ElementType => typeof(T);{{#newline}}{{#newline}} public Expression Expression { get; }{{#newline}}{{#newline}} public IQueryProvider Provider => this;{{#newline}}{{#newline}} private static TResult CompileExpressionItem(Expression expression){{#newline}} {{{#newline}} var visitor = new FakeExpressionVisitor();{{#newline}} var body = visitor.Visit(expression);{{#newline}} var f = Expression.Lambda>(body ?? throw new InvalidOperationException(string.Format(""{0} is null"", nameof(body))), (IEnumerable) null);{{#newline}} return f.Compile()();{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeExpressionVisitor : ExpressionVisitor{{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public class FakeDatabaseFacade : DatabaseFacade{{#newline}} {{{#newline}} public FakeDatabaseFacade(DbContext context) : base(context){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public override bool EnsureCreated(){{#newline}} {{{#newline}} return true;{{#newline}} }{{#newline}}{{#newline}} public override Task EnsureCreatedAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(EnsureCreated());{{#newline}} }{{#newline}}{{#newline}} public override bool EnsureDeleted(){{#newline}} {{{#newline}} return true;{{#newline}} }{{#newline}}{{#newline}} public override Task EnsureDeletedAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(EnsureDeleted());{{#newline}} }{{#newline}}{{#newline}} public override bool CanConnect(){{#newline}} {{{#newline}} return true;{{#newline}} }{{#newline}}{{#newline}} public override Task CanConnectAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(CanConnect());{{#newline}} }{{#newline}}{{#newline}} public override IDbContextTransaction BeginTransaction(){{#newline}} {{{#newline}} return new FakeDbContextTransaction();{{#newline}} }{{#newline}}{{#newline}} public override Task BeginTransactionAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(BeginTransaction());{{#newline}} }{{#newline}}{{#newline}} public override void CommitTransaction(){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public override void RollbackTransaction(){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public override IExecutionStrategy CreateExecutionStrategy(){{#newline}} {{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override string ToString(){{#newline}} {{{#newline}} return string.Empty;{{#newline}} }{{#newline}}{{#newline}} }{{#newline}}{{#newline}} public class FakeDbContextTransaction : IDbContextTransaction{{#newline}} {{{#newline}} public virtual Guid TransactionId => Guid.NewGuid();{{#newline}} public virtual void Commit() { }{{#newline}} public virtual void Rollback() { }{{#newline}} public virtual Task CommitAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;{{#newline}} public virtual Task RollbackAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;{{#newline}} public virtual void Dispose() { }{{#newline}} public virtual ValueTask DisposeAsync() => default;{{#newline}} }"; } public override List PocoUsings(PocoModel data) { var usings = new List { "System", "System.Collections.Generic", "System.Threading", "System.Threading.Tasks", }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string Poco() { return @" {{#if UseHasNoKey}} {{#else}} {{#if HasNoPrimaryKey}} // The table '{{Name}}' is not usable by entity framework because it{{#newline}} // does not have a primary key. It is listed here for completeness.{{#newline}} {{/if}} {{/if}} {{ClassComment}} {{ExtendedComments}} {{ClassAttributes}} {{ClassModifier}} class {{NameHumanCaseWithSuffix}}{{BaseClasses}}{{#newline}} {{{#newline}} {{InsideClassBody}} {{#each Columns}} {{#if AddNewLineBefore}}{{#newline}}{{/if}} {{#if HasSummaryComments}} /// {{#newline}} /// {{SummaryComments}}{{#newline}} /// {{#newline}} {{/if}} {{#each Attributes}} {{this}}{{#newline}} {{/each}} public {{#if OverrideModifier}}override {{/if}}{{WrapIfNullable}} {{NameHumanCase}} { get; {{PrivateSetterForComputedColumns}}set; }{{PropertyInitialisers}}{{InlineComments}}{{#newline}} {{#if IncludeFieldNameConstants}} public const string {{NameHumanCase}}Field = ""{{NameHumanCase}}"";{{#newline}}{{/if}} {{/each}} {{#if HasReverseNavigation}} {{#newline}} // Reverse navigation{{#newline}} {{#each ReverseNavigationProperty}} {{#if ReverseNavHasComment}} {{#newline}} /// {{#newline}} /// {{ReverseNavComment}}{{#newline}} /// {{#newline}} {{/if}} {{#each AdditionalReverseNavigationsDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{#each AdditionalDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{Definition}}{{#newline}} {{/each}} {{/if}} {{#if HasForeignKey}} {{#newline}} {{ForeignKeyTitleComment}} {{#each ForeignKeys}} {{#if HasFkComment}} {{#newline}} /// {{#newline}} /// {{FkComment}}{{#newline}} /// {{#newline}} {{/if}} {{#each AdditionalForeignKeysDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{#each AdditionalDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{Definition}}{{#newline}} {{/each}} {{/if}} {{#if CreateConstructor}} {{#newline}} public {{NameHumanCaseWithSuffix}}(){{#newline}} {{{#newline}} {{#each ColumnsWithDefaults}} {{NameHumanCase}} = {{Default}};{{#newline}} {{/each}} {{#each ReverseNavigationCtor}} {{this}}{{#newline}} {{/each}} {{#if EntityClassesArePartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}} {{#if EntityClassesArePartial}} {{#newline}} partial void InitializePartial();{{#newline}} {{/if}} {{/if}} }{{#newline}} "; } public override List PocoConfigurationUsings(PocoConfigurationModel data) { var usings = new List { "Microsoft.EntityFrameworkCore", "Microsoft.EntityFrameworkCore.Metadata.Builders" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (Settings.TrimCharFields) usings.Add("Microsoft.EntityFrameworkCore.Storage.ValueConversion"); return usings; } public override string PocoConfiguration() { return @" {{ClassComment}} {{ClassModifier}} class {{ConfigurationClassName}} : IEntityTypeConfiguration<{{NameHumanCaseWithSuffix}}>{{#newline}} {{{#newline}} public void Configure(EntityTypeBuilder<{{NameHumanCaseWithSuffix}}> builder){{#newline}} {{{#newline}} {{#if NotUsingDataAnnotations}} {{#if HasSchema}} builder.{{ToTableOrView}}(""{{Name}}"", ""{{Schema}}"");{{#newline}} {{#else}} builder.{{ToTableOrView}}(""{{Name}}"");{{#newline}} {{/if}} {{/if}} {{PrimaryKeyNameHumanCase}}{{#newline}}{{#newline}} {{#each Columns}} {{this}}{{#newline}} {{/each}} {{#if HasForeignKey}} {{#newline}} // Foreign keys{{#newline}} {{#each ForeignKeys}} {{this}}{{#newline}} {{/each}} {{/if}} {{#each MappingConfiguration}} builder.{{this}}{{#newline}} {{/each}} {{#if HasIndexes}} {{#newline}} {{#each Indexes}} {{this}}{{#newline}} {{/each}} {{/if}} {{#if ConfigurationClassesArePartial}} {{#newline}} InitializePartial(builder);{{#newline}} {{/if}} }{{#newline}} {{#if ConfigurationClassesArePartial}} {{#newline}} partial void InitializePartial(EntityTypeBuilder<{{NameHumanCaseWithSuffix}}> builder);{{#newline}} {{/if}} }{{#newline}}"; } public override List StoredProcReturnModelUsings() { var usings = new List { "System", "System.Collections.Generic" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string StoredProcReturnModels() { return @" {{ResultClassModifiers}} class {{WriteStoredProcReturnModelName}}{{#newline}} {{{#newline}} {{#if SingleModel}} {{#each SingleModelReturnColumns}} {{this}}{{#newline}} {{/each}} {{#else}} {{#each MultipleModelReturnColumns}} public class ResultSetModel{{Model}}{{#newline}} {{{#newline}} {{#each ReturnColumns}} {{this}}{{#newline}} {{/each}} }{{#newline}} public List ResultSet{{Model}}{{PropertyGetSet}}{{#newline}} {{/each}} {{/if}} }{{#newline}} "; } public override List EnumUsings() { var usings = new List(); if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string Enums() { return @" {{#each EnumAttributes}} {{this}}{{#newline}} {{/each}} public enum {{EnumName}}{{#newline}} {{{#newline}} {{#each Items}} {{#each Attributes}} {{this}}{{#newline}} {{/each}} {{Key}} = {{Value}},{{#newline}} {{/each}} }{{#newline}} "; } } /// /// {{Mustache}} template documentation available at https://github.com/jehugaleahsa/mustache-sharp /// public class TemplateEfCore6 : Template { public override string Usings() { return @" {{#each this}} using {{this}};{{#newline}} {{/each}}"; } public override List DatabaseContextInterfaceUsings(InterfaceModel data) { var usings = new List { "System", "System.Data", "System.Threading.Tasks", "System.Threading" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("Microsoft.EntityFrameworkCore"); usings.Add("Microsoft.EntityFrameworkCore.Infrastructure"); usings.Add("System.Linq"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Collections.Generic"); usings.Add("Microsoft.EntityFrameworkCore.ChangeTracking"); usings.Add("System.Linq"); usings.Add("System.Linq.Expressions"); } return usings; } public override string DatabaseContextInterface() { return @" {{interfaceModifier}} interface {{DbContextInterfaceName}} : {{DbContextInterfaceBaseClasses}}{{#newline}} {{{#newline}} {{#each tables}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#if AdditionalContextInterfaceItems}} {{#newline}} // Additional interface items{{#newline}} {{/if}} {{#each AdditionalContextInterfaceItems}} {{this}}{{#newline}} {{/each}} {{#if addSaveChanges}} {{#newline}} int SaveChanges();{{#newline}} int SaveChanges(bool acceptAllChangesOnSuccess);{{#newline}} Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));{{#newline}} Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken));{{#newline}} DatabaseFacade Database { get; }{{#newline}} DbSet Set() where TEntity : class;{{#newline}} string ToString();{{#newline}}{{#newline}} EntityEntry Add(object entity);{{#newline}} EntityEntry Add(TEntity entity) where TEntity : class;{{#newline}} Task AddRangeAsync(params object[] entities);{{#newline}} Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default);{{#newline}} ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class;{{#newline}} ValueTask AddAsync(object entity, CancellationToken cancellationToken = default);{{#newline}} void AddRange(IEnumerable entities);{{#newline}} void AddRange(params object[] entities);{{#newline}}{{#newline}} EntityEntry Attach(object entity);{{#newline}} EntityEntry Attach(TEntity entity) where TEntity : class;{{#newline}} void AttachRange(IEnumerable entities);{{#newline}} void AttachRange(params object[] entities);{{#newline}}{{#newline}} EntityEntry Entry(object entity);{{#newline}} EntityEntry Entry(TEntity entity) where TEntity : class;{{#newline}}{{#newline}} TEntity Find(params object[] keyValues) where TEntity : class;{{#newline}} ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) where TEntity : class;{{#newline}} ValueTask FindAsync(params object[] keyValues) where TEntity : class;{{#newline}} ValueTask FindAsync(Type entityType, object[] keyValues, CancellationToken cancellationToken);{{#newline}} ValueTask FindAsync(Type entityType, params object[] keyValues);{{#newline}} object Find(Type entityType, params object[] keyValues);{{#newline}}{{#newline}} EntityEntry Remove(object entity);{{#newline}} EntityEntry Remove(TEntity entity) where TEntity : class;{{#newline}} void RemoveRange(IEnumerable entities);{{#newline}} void RemoveRange(params object[] entities);{{#newline}}{{#newline}} EntityEntry Update(object entity);{{#newline}} EntityEntry Update(TEntity entity) where TEntity : class;{{#newline}} void UpdateRange(IEnumerable entities);{{#newline}} void UpdateRange(params object[] entities);{{#newline}}{{#newline}} IQueryable FromExpression (Expression>> expression);{{#newline}} {{/if}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#if MultipleReturnModels}} // {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseTrue}}); Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#else}} {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseTrue}});{{#newline}} {{/if}} {{#if SingleReturnModel}} {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueTrue}});{{#newline}} {{/if}} {{#else}} int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueTrue}});{{#newline}} {{/if}} {{#if MultipleReturnModels}} // Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseTrue}}); Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#else}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseTrueToken}});{{#newline}} {{/if}} {{/if}} {{#newline}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseTrue}}); // {{Schema}}.{{Name}}{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseTrue}}); // {{Schema}}.{{Name}}{{#newline}} {{/each}} {{/if}} }"; } public override List DatabaseContextUsings(ContextModel data) { var usings = new List { "System", "System.Data", "System.Data.SqlTypes", "Microsoft.EntityFrameworkCore", "System.Threading.Tasks", "System.Threading" }; switch (Settings.DatabaseType) { case DatabaseType.SqlServer: case DatabaseType.SqlCe: case DatabaseType.Plugin: usings.Add("Microsoft.Data.SqlClient"); break; case DatabaseType.SQLite: usings.Add("Microsoft.Data.Sqlite"); break; case DatabaseType.PostgreSQL: usings.Add("Npgsql"); usings.Add("NpgsqlTypes"); break; case DatabaseType.MySql: break; case DatabaseType.Oracle: break; } if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("System.Linq"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if(Settings.OnConfiguration == OnConfiguration.Configuration) usings.Add("Microsoft.Extensions.Configuration"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Collections.Generic"); usings.Add("Microsoft.EntityFrameworkCore.ChangeTracking"); } return usings; } public override string DatabaseContext() { return @" {{DbContextClassModifiers}} class {{DbContextName}} : {{DbContextBaseClass}}{{contextInterface}}{{#newline}} {{{#newline}} {{#if OnConfigurationUsesConfiguration}} private readonly IConfiguration _configuration;{{#newline}}{{#newline}} {{/if}} {{#if AddParameterlessConstructorToDbContext}} public {{DbContextName}}(){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{/if}} public {{DbContextName}}(DbContextOptions<{{DbContextName}}> options){{#newline}} : base(options){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{#if OnConfigurationUsesConfiguration}} public {{DbContextName}}(IConfiguration configuration){{#newline}} {{{#newline}} _configuration = configuration;{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{/if}} {{#each tables}} {{DbSetModifier}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#newline}} {{#if OnConfigurationUsesConfiguration}} protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){{#newline}} {{{#newline}} if (!optionsBuilder.IsConfigured && _configuration != null){{#newline}} {{{#newline}} optionsBuilder.{{UseDatabaseProvider}}(_configuration.GetConnectionString(@""{{ConnectionStringName}}""){{ConnectionStringActions}});{{#newline}} {{#if UseLazyLoadingProxies}} optionsBuilder.UseLazyLoadingProxies();{{#newline}} {{/if}} }{{#newline}} }{{#newline}}{{#newline}} {{/if}} {{#if OnConfigurationUsesConnectionString}} protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){{#newline}} {{{#newline}} if (!optionsBuilder.IsConfigured){{#newline}} {{{#newline}} optionsBuilder.{{UseDatabaseProvider}}(@""{{ConnectionString}}""{{ConnectionStringActions}});{{#newline}} {{#if UseLazyLoadingProxies}} optionsBuilder.UseLazyLoadingProxies();{{#newline}} {{/if}} }{{#newline}} }{{#newline}}{{#newline}} {{/if}} public bool IsSqlParameterNull({{SqlParameter}} param){{#newline}} {{{#newline}} var sqlValue = param.{{SqlParameterValue}};{{#newline}} var nullableValue = sqlValue as INullable;{{#newline}} if (nullableValue != null){{#newline}} return nullableValue.IsNull;{{#newline}} return (sqlValue == null || sqlValue == DBNull.Value);{{#newline}} }{{#newline}}{{#newline}} protected override void OnModelCreating(ModelBuilder modelBuilder){{#newline}} {{{#newline}} base.OnModelCreating(modelBuilder);{{#newline}} {{#if hasSequences}} {{#newline}} {{#each Sequences}} modelBuilder.HasSequence<{{DataType}}>(""{{Name}}"", ""{{Schema}}"").StartsAt({{StartValue}}).IncrementsBy({{IncrementValue}}).IsCyclic({{IsCycleEnabled}}) {{#if hasMinValue}} .HasMin({{MinValue}}) {{/if}} {{#if hasMaxValue}} .HasMax({{MaxValue}}) {{/if}} ;{{#newline}} {{/each}} {{/if}} {{#if hasTables}} {{#newline}} {{#each tables}} modelBuilder.ApplyConfiguration(new {{DbSetConfigName}}());{{#newline}} {{/each}} {{/if}} {{#if hasMemoryOptimisedTables}} {{#newline}} {{#each MemoryOptimisedTables}} modelBuilder.Entity<{{this}}>().IsMemoryOptimized();{{#newline}} {{/each}} {{/if}} {{#if hasStoredProcs}} {{#newline}} {{#each storedProcs}} {{#if SingleReturnModel}} modelBuilder.{{StoredProcModelBuilderCommand}}<{{ReturnModelName}}>(){{StoredProcModelBuilderPostCommand}};{{#newline}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#if IncludeModelBuilder}} modelBuilder.{{ModelBuilderCommand}}<{{ReturnClassName}}>(){{ModelBuilderPostCommand}};{{#newline}} {{/if}} {{/each}} {{/if}} {{#if DbContextClassIsPartial}} {{#newline}} OnModelCreatingPartial(modelBuilder);{{#newline}} {{/if}} }{{#newline}} {{#newline}} {{#if DbContextClassIsPartial}} {{#newline}} partial void InitializePartial();{{#newline}} partial void DisposePartial(bool disposing);{{#newline}} partial void OnModelCreatingPartial(ModelBuilder modelBuilder);{{#newline}} static partial void OnCreateModelPartial(ModelBuilder modelBuilder, string schema);{{#newline}} {{/if}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#if MultipleReturnModels}} // public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}) Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#else}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return {{FunctionName}}({{WriteStoredProcFunctionOverloadCall}});{{#newline}} }{{#newline}}{{#newline}} {{/if}} {{#if SingleReturnModel}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}} const string sqlCommand = ""{{Exec}}"";{{#newline}} var procResultData = {{QueryString}}<{{ReturnModelName}}>(){{#newline}} .{{FromSql}}(sqlCommand{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrue}}){{#newline}} .ToList();{{#newline}}{{#newline}} {{WriteStoredProcFunctionSetSqlParametersFalse}} procResult = (int) procResultParam.Value;{{#newline}} return procResultData;{{#newline}} }{{#newline}} {{/if}} {{#else}} public int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}}{{#newline}} Database.{{ExecuteSqlCommand}}(""{{ExecWithNoReturnModel}}""{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrue}});{{#newline}} {{#newline}} {{WriteStoredProcFunctionSetSqlParametersFalse}} return (int)procResultParam.Value;{{#newline}} }{{#newline}} {{/if}} {{#newline}} {{#if MultipleReturnModels}} // public async Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalse}}) Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#newline}} {{#else}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public async Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} {{#if HasNoReturnModels}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}} {{#newline}} await Database.ExecuteSqlRawAsync(""{{AsyncExec}}""{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrueToken}});{{#newline}} {{#newline}} return (int)procResultParam.Value;{{#newline}} {{#else}} {{WriteStoredProcFunctionDeclareSqlParameterFalse}} {{WriteStoredProcFunctionSetSqlParametersFalse}} const string sqlCommand = ""{{AsyncExec}}"";{{#newline}} var procResultData = await {{QueryString}}<{{ReturnModelName}}>(){{#newline}} .{{FromSql}}(sqlCommand{{WriteStoredProcFunctionSqlParameterAnonymousArrayFalse}}){{#newline}} .ToListAsync();{{#newline}}{{#newline}} return procResultData;{{#newline}} {{/if}} }{{#newline}} {{/if}} {{#newline}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return {{QueryString}}<{{ReturnClassName}}>(){{#newline}} .{{FromSql}}(""SELECT * FROM [{{Schema}}].[{{Name}}]({{WriteStoredProcFunctionSqlAtParams}})""{{WriteTableValuedFunctionSqlParameterAnonymousArray}}){{#newline}} .AsNoTracking();{{#newline}} }{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{#newline}} [DbFunction(""{{Name}}"", ""{{Schema}}"")]{{#newline}} public {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} throw new Exception(""Don't call this directly. Use LINQ to call the scalar valued function as part of your query"");{{#newline}} }{{#newline}} {{/each}} {{/if}} }"; } public override List DatabaseContextFactoryUsings(FactoryModel data) { var usings = new List { "Microsoft.EntityFrameworkCore.Design" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string DatabaseContextFactory() { return @" {{classModifier}} class {{contextName}}Factory : IDesignTimeDbContextFactory<{{contextName}}>{{#newline}} {{{#newline}} public {{contextName}} CreateDbContext(string[] args){{#newline}} {{{#newline}} return new {{contextName}}();{{#newline}} }{{#newline}} }"; } public override List FakeDatabaseContextUsings(FakeContextModel data, IDbContextFilter filter) { var usings = new List { "System", "System.Data", "System.Threading.Tasks", "System.Threading", "Microsoft.EntityFrameworkCore.Infrastructure" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("System.Linq"); usings.Add("Microsoft.EntityFrameworkCore"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Collections.Generic"); usings.Add("Microsoft.EntityFrameworkCore.ChangeTracking"); usings.Add("System.Linq"); usings.Add("System.Linq.Expressions"); } if (Settings.DatabaseType == DatabaseType.SqlCe) { usings.Add("Microsoft.Data.SqlClient"); //usings.Add("System.DBNull"); usings.Add("System.Data.SqlTypes"); } if (Settings.DatabaseType == DatabaseType.PostgreSQL) { usings.Add("Npgsql"); usings.Add("NpgsqlTypes"); } return usings; } public override string FakeDatabaseContext() { return @" {{DbContextClassModifiers}} class Fake{{DbContextName}}{{contextInterface}}{{#newline}} {{{#newline}} {{#each tables}} {{DbSetModifier}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#newline}} public Fake{{DbContextName}}(){{#newline}} {{{#newline}} _database = new FakeDatabaseFacade(new {{DbContextName}}());{{#newline}} {{#newline}} {{#each tables}} {{PluralTableName}} = new FakeDbSet<{{DbSetName}}>({{DbSetPrimaryKeys}});{{#newline}} {{/each}} {{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}} {{#newline}} public int SaveChangesCount { get; private set; }{{#newline}} public virtual int SaveChanges(){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return 1;{{#newline}} }{{#newline}}{{#newline}} public virtual int SaveChanges(bool acceptAllChangesOnSuccess){{#newline}} {{{#newline}} return SaveChanges();{{#newline}} }{{#newline}}{{#newline}} public virtual Task SaveChangesAsync(CancellationToken cancellationToken){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return Task.Factory.StartNew(() => 1, cancellationToken);{{#newline}} }{{#newline}} public virtual Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return Task.Factory.StartNew(x => 1, acceptAllChangesOnSuccess, cancellationToken);{{#newline}} }{{#newline}}{{#newline}} {{#if DbContextClassIsPartial}} partial void InitializePartial();{{#newline}} {{#newline}} {{/if}} protected virtual void Dispose(bool disposing){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public void Dispose(){{#newline}} {{{#newline}} Dispose(true);{{#newline}} }{{#newline}}{{#newline}} private DatabaseFacade _database;{{#newline}} public DatabaseFacade Database { get { return _database; } }{{#newline}}{{#newline}} public DbSet Set() where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public override string ToString(){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Add(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Add(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual Task AddRangeAsync(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual async Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} await Task.CompletedTask;{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual async ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class{{#newline}} {{{#newline}} await Task.CompletedTask;{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual async ValueTask AddAsync(object entity, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} await Task.CompletedTask;{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AddRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AddRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Attach(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Attach(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AttachRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AttachRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Entry(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Entry(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual TEntity Find(params object[] keyValues) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(params object[] keyValues) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(Type entityType, object[] keyValues, CancellationToken cancellationToken){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(Type entityType, params object[] keyValues){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual object Find(Type entityType, params object[] keyValues){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Remove(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Remove(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void RemoveRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void RemoveRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Update(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Update(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void UpdateRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void UpdateRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual IQueryable FromExpression (Expression>> expression){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#newline}} {{#if CreateDbSetForReturnModel}} public DbSet<{{ReturnModelName}}> {{ReturnModelName}} { get; set; }{{#newline}} {{/if}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return {{FunctionName}}({{WriteStoredProcFunctionOverloadCall}});{{#newline}} }{{#newline}}{{#newline}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} procResult = 0;{{#newline}} return new {{ReturnType}}();{{#newline}} }{{#newline}} {{#newline}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return Task.FromResult({{FunctionName}}({{WriteStoredProcFunctionOverloadCall}}));{{#newline}} }{{#newline}} {{/if}} {{#else}} {{#newline}} public int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} return 0;{{#newline}} }{{#newline}} {{#newline}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public Task {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} return Task.FromResult(0);{{#newline}} }{{#newline}} {{/if}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return new List<{{ReturnClassName}}>().AsQueryable();{{#newline}} }{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return default({{ReturnType}});{{#newline}} }{{#newline}} {{/each}} {{/if}} }"; } public override List FakeDbSetUsings(FakeDbSetModel data) { var usings = new List { "System", "System.Collections", "System.ComponentModel", "System.Linq", "System.Linq.Expressions", "System.Reflection", "System.Collections.ObjectModel", "System.Collections.Generic", "System.Threading", "System.Threading.Tasks", "Microsoft.EntityFrameworkCore", "Microsoft.EntityFrameworkCore.Query", "Microsoft.EntityFrameworkCore.Query.Internal", "Microsoft.EntityFrameworkCore.Infrastructure", "Microsoft.EntityFrameworkCore.ChangeTracking", "Microsoft.EntityFrameworkCore.Storage", "Microsoft.EntityFrameworkCore.Metadata" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string FakeDbSet() { return @" // ************************************************************************{{#newline}} // Fake DbSet{{#newline}} // Implementing Find:{{#newline}} // The Find method is difficult to implement in a generic fashion. If{{#newline}} // you need to test code that makes use of the Find method it is{{#newline}} // easiest to create a test DbSet for each of the entity types that{{#newline}} // need to support find. You can then write logic to find that{{#newline}} // particular type of entity, as shown below:{{#newline}} // public class FakeBlogDbSet : FakeDbSet{{#newline}} // {{{#newline}} // public override Blog Find(params object[] keyValues){{#newline}} // {{{#newline}} // var id = (int) keyValues.Single();{{#newline}} // return this.SingleOrDefault(b => b.BlogId == id);{{#newline}} // }{{#newline}} // }{{#newline}} // Read more about it here: https://msdn.microsoft.com/en-us/data/dn314431.aspx{{#newline}} {{DbContextClassModifiers}} class FakeDbSet :{{#newline}} DbSet,{{#newline}} IQueryable,{{#newline}} IAsyncEnumerable,{{#newline}} IListSource,{{#newline}} IResettableService{{#newline}} where TEntity : class{{#newline}} {{{#newline}} private readonly PropertyInfo[] _primaryKeys;{{#newline}} private ObservableCollection _data;{{#newline}} private IQueryable _query;{{#newline}} public override IEntityType EntityType { get; }{{#newline}}{{#newline}} public FakeDbSet(){{#newline}} {{{#newline}} _primaryKeys = null;{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} public FakeDbSet(params string[] primaryKeys){{#newline}} {{{#newline}} _primaryKeys = typeof(TEntity).GetProperties().Where(x => primaryKeys.Contains(x.Name)).ToArray();{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} public override TEntity Find(params object[] keyValues){{#newline}} {{{#newline}} if (_primaryKeys == null){{#newline}} throw new ArgumentException(""No primary keys defined"");{{#newline}} if (keyValues.Length != _primaryKeys.Length){{#newline}} throw new ArgumentException(""Incorrect number of keys passed to Find method"");{{#newline}}{{#newline}} var keyQuery = this.AsQueryable();{{#newline}} keyQuery = keyValues{{#newline}} .Select((t, i) => i){{#newline}} .Aggregate(keyQuery,{{#newline}} (current, x) =>{{#newline}} current.Where(entity => _primaryKeys[x].GetValue(entity, null).Equals(keyValues[x])));{{#newline}}{{#newline}} return keyQuery.SingleOrDefault();{{#newline}} }{{#newline}}{{#newline}} public override ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken){{#newline}} {{{#newline}} return new ValueTask(Task.Factory.StartNew(() => Find(keyValues), cancellationToken));{{#newline}} }{{#newline}}{{#newline}} public override ValueTask FindAsync(params object[] keyValues){{#newline}} {{{#newline}} return new ValueTask(Task.Factory.StartNew(() => Find(keyValues)));{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Add(TEntity entity){{#newline}} {{{#newline}} _data.Add(entity);{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} return new ValueTask>(Task>.Factory.StartNew(() => Add(entity), cancellationToken));{{#newline}} }{{#newline}}{{#newline}} public override void AddRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} foreach (var entity in entities){{#newline}} _data.Add(entity);{{#newline}} }{{#newline}}{{#newline}} public override void AddRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} foreach (var entity in entities){{#newline}} _data.Add(entity);{{#newline}} }{{#newline}}{{#newline}} public override Task AddRangeAsync(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} return Task.Factory.StartNew(() => AddRange(entities));{{#newline}} }{{#newline}}{{#newline}} public override Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} return Task.Factory.StartNew(() => AddRange(entities), cancellationToken);{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Attach(TEntity entity){{#newline}} {{{#newline}} if (entity == null) throw new ArgumentNullException(""entity"");{{#newline}} return Add(entity);{{#newline}} }{{#newline}}{{#newline}} public override void AttachRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} AddRange(entities);{{#newline}} }{{#newline}}{{#newline}} public override void AttachRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} AddRange(entities);{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Remove(TEntity entity){{#newline}} {{{#newline}} _data.Remove(entity);{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override void RemoveRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} foreach (var entity in entities.ToList()){{#newline}} _data.Remove(entity);{{#newline}} }{{#newline}}{{#newline}} public override void RemoveRange(IEnumerable entities){{#newline}} {{{#newline}} RemoveRange(entities.ToArray());{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Update(TEntity entity){{#newline}} {{{#newline}} _data.Remove(entity);{{#newline}} _data.Add(entity);{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override void UpdateRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} RemoveRange(entities);{{#newline}} AddRange(entities);{{#newline}} }{{#newline}}{{#newline}} public override void UpdateRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} var array = entities.ToArray(); RemoveRange(array);{{#newline}} AddRange(array);{{#newline}} }{{#newline}}{{#newline}} bool IListSource.ContainsListCollection => true;{{#newline}}{{#newline}} public IList GetList(){{#newline}} {{{#newline}} return _data;{{#newline}} }{{#newline}}{{#newline}} IList IListSource.GetList(){{#newline}} {{{#newline}} return _data;{{#newline}} }{{#newline}}{{#newline}} Type IQueryable.ElementType{{#newline}} {{{#newline}} get { return _query.ElementType; }{{#newline}} }{{#newline}}{{#newline}} Expression IQueryable.Expression{{#newline}} {{{#newline}} get { return _query.Expression; }{{#newline}} }{{#newline}}{{#newline}} IQueryProvider IQueryable.Provider{{#newline}} {{{#newline}} get { return new FakeDbAsyncQueryProvider(_data); }{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return _data.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return _data.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}}{{#newline}} public void ResetState(){{#newline}} {{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} }{{#newline}}{{#newline}} public Task ResetStateAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.Factory.StartNew(() => ResetState());{{#newline}} }{{#newline}} {{#if DbContextClassIsPartial}} {{#newline}} partial void InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncQueryProvider : FakeQueryProvider, IAsyncEnumerable, IAsyncQueryProvider{{#newline}} {{{#newline}} public FakeDbAsyncQueryProvider(Expression expression) : base(expression){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public FakeDbAsyncQueryProvider(IEnumerable enumerable) : base(enumerable){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public TResult ExecuteAsync(Expression expression, CancellationToken cancellationToken){{#newline}} {{{#newline}} var expectedResultType = typeof(TResult).GetGenericArguments()[0];{{#newline}} var executionResult = typeof(IQueryProvider){{#newline}} .GetMethods(){{#newline}} .First(method => method.Name == nameof(IQueryProvider.Execute) && method.IsGenericMethod){{#newline}} .MakeGenericMethod(expectedResultType){{#newline}} .Invoke(this, new object[] { expression });{{#newline}}{{#newline}} return (TResult) typeof(Task).GetMethod(nameof(Task.FromResult)){{#newline}} ?.MakeGenericMethod(expectedResultType){{#newline}} .Invoke(null, new[] { executionResult });{{#newline}} }{{#newline}}{{#newline}} public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncEnumerable : EnumerableQuery, IAsyncEnumerable, IQueryable{{#newline}} {{{#newline}} public FakeDbAsyncEnumerable(IEnumerable enumerable){{#newline}} : base(enumerable){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public FakeDbAsyncEnumerable(Expression expression){{#newline}} : base(expression){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}}{{#newline}} IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken){{#newline}} {{{#newline}} return GetAsyncEnumerator(cancellationToken);{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return this.AsEnumerable().GetEnumerator();{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncEnumerator : IAsyncEnumerator{{#newline}} {{{#newline}} private readonly IEnumerator _inner;{{#newline}}{{#newline}} public FakeDbAsyncEnumerator(IEnumerator inner){{#newline}} {{{#newline}} _inner = inner;{{#newline}} }{{#newline}}{{#newline}} public T Current{{#newline}} {{{#newline}} get { return _inner.Current; }{{#newline}} }{{#newline}}{{#newline}} public ValueTask MoveNextAsync(){{#newline}} {{{#newline}} return new ValueTask(_inner.MoveNext());{{#newline}} }{{#newline}}{{#newline}} public ValueTask DisposeAsync(){{#newline}} {{{#newline}} _inner.Dispose();{{#newline}} return new ValueTask(Task.CompletedTask);{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} public abstract class FakeQueryProvider : IOrderedQueryable, IQueryProvider{{#newline}} {{{#newline}} private IEnumerable _enumerable;{{#newline}}{{#newline}} protected FakeQueryProvider(Expression expression){{#newline}} {{{#newline}} Expression = expression;{{#newline}} }{{#newline}}{{#newline}} protected FakeQueryProvider(IEnumerable enumerable){{#newline}} {{{#newline}} _enumerable = enumerable;{{#newline}} Expression = enumerable.AsQueryable().Expression;{{#newline}} }{{#newline}}{{#newline}} public IQueryable CreateQuery(Expression expression){{#newline}} {{{#newline}} if (expression is MethodCallExpression m){{#newline}} {{{#newline}} var resultType = m.Method.ReturnType; // it should be IQueryable{{#newline}} var tElement = resultType.GetGenericArguments().First();{{#newline}} return (IQueryable) CreateInstance(tElement, expression);{{#newline}} }{{#newline}}{{#newline}} return CreateQuery(expression);{{#newline}} }{{#newline}}{{#newline}} public IQueryable CreateQuery(Expression expression){{#newline}} {{{#newline}} return (IQueryable) CreateInstance(typeof(TEntity), expression);{{#newline}} }{{#newline}}{{#newline}} private object CreateInstance(Type tElement, Expression expression){{#newline}} {{{#newline}} var queryType = GetType().GetGenericTypeDefinition().MakeGenericType(tElement);{{#newline}} return Activator.CreateInstance(queryType, expression);{{#newline}} }{{#newline}}{{#newline}} public object Execute(Expression expression){{#newline}} {{{#newline}} return CompileExpressionItem(expression);{{#newline}} }{{#newline}}{{#newline}} public TResult Execute(Expression expression){{#newline}} {{{#newline}} return CompileExpressionItem(expression);{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} if (_enumerable == null) _enumerable = CompileExpressionItem>(Expression);{{#newline}} return _enumerable.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} if (_enumerable == null) _enumerable = CompileExpressionItem>(Expression);{{#newline}} return _enumerable.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} public Type ElementType => typeof(T);{{#newline}}{{#newline}} public Expression Expression { get; }{{#newline}}{{#newline}} public IQueryProvider Provider => this;{{#newline}}{{#newline}} private static TResult CompileExpressionItem(Expression expression){{#newline}} {{{#newline}} var visitor = new FakeExpressionVisitor();{{#newline}} var body = visitor.Visit(expression);{{#newline}} var f = Expression.Lambda>(body ?? throw new InvalidOperationException(string.Format(""{0} is null"", nameof(body))), (IEnumerable) null);{{#newline}} return f.Compile()();{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeExpressionVisitor : ExpressionVisitor{{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public class FakeDatabaseFacade : DatabaseFacade{{#newline}} {{{#newline}} public FakeDatabaseFacade(DbContext context) : base(context){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public override bool EnsureCreated(){{#newline}} {{{#newline}} return true;{{#newline}} }{{#newline}}{{#newline}} public override Task EnsureCreatedAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(EnsureCreated());{{#newline}} }{{#newline}}{{#newline}} public override bool EnsureDeleted(){{#newline}} {{{#newline}} return true;{{#newline}} }{{#newline}}{{#newline}} public override Task EnsureDeletedAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(EnsureDeleted());{{#newline}} }{{#newline}}{{#newline}} public override bool CanConnect(){{#newline}} {{{#newline}} return true;{{#newline}} }{{#newline}}{{#newline}} public override Task CanConnectAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(CanConnect());{{#newline}} }{{#newline}}{{#newline}} public override IDbContextTransaction BeginTransaction(){{#newline}} {{{#newline}} return new FakeDbContextTransaction();{{#newline}} }{{#newline}}{{#newline}} public override Task BeginTransactionAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(BeginTransaction());{{#newline}} }{{#newline}}{{#newline}} public override void CommitTransaction(){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public override Task CommitTransactionAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.CompletedTask;{{#newline}} }{{#newline}}{{#newline}} public override void RollbackTransaction(){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public override Task RollbackTransactionAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.CompletedTask;{{#newline}} }{{#newline}}{{#newline}} public override IExecutionStrategy CreateExecutionStrategy(){{#newline}} {{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override string ToString(){{#newline}} {{{#newline}} return string.Empty;{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} public class FakeDbContextTransaction : IDbContextTransaction{{#newline}} {{{#newline}} public Guid TransactionId => Guid.NewGuid();{{#newline}} public void Commit() { }{{#newline}} public void Rollback() { }{{#newline}} public Task CommitAsync(CancellationToken cancellationToken = new CancellationToken()) => Task.CompletedTask;{{#newline}} public Task RollbackAsync(CancellationToken cancellationToken = new CancellationToken()) => Task.CompletedTask;{{#newline}} public void Dispose() { }{{#newline}} public ValueTask DisposeAsync() => default;{{#newline}} }"; } public override List PocoUsings(PocoModel data) { var usings = new List { "System", "System.Collections.Generic", "System.Threading", "System.Threading.Tasks", }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if(data.HasHierarchyId) usings.Add("Microsoft.EntityFrameworkCore"); return usings; } public override string Poco() { return @" {{#if UseHasNoKey}} {{#else}} {{#if HasNoPrimaryKey}} // The table '{{Name}}' is not usable by entity framework because it{{#newline}} // does not have a primary key. It is listed here for completeness.{{#newline}} {{/if}} {{/if}} {{ClassComment}} {{ExtendedComments}} {{ClassAttributes}} {{ClassModifier}} class {{NameHumanCaseWithSuffix}}{{BaseClasses}}{{#newline}} {{{#newline}} {{InsideClassBody}} {{#each Columns}} {{#if AddNewLineBefore}}{{#newline}}{{/if}} {{#if HasSummaryComments}} /// {{#newline}} /// {{SummaryComments}}{{#newline}} /// {{#newline}} {{/if}} {{#each Attributes}} {{this}}{{#newline}} {{/each}} public {{#if OverrideModifier}}override {{/if}}{{WrapIfNullable}} {{NameHumanCase}} { get; {{PrivateSetterForComputedColumns}}set; }{{PropertyInitialisers}}{{InlineComments}}{{#newline}} {{#if IncludeFieldNameConstants}} public const string {{NameHumanCase}}Field = ""{{NameHumanCase}}"";{{#newline}}{{/if}} {{/each}} {{#if HasReverseNavigation}} {{#newline}} // Reverse navigation{{#newline}} {{#each ReverseNavigationProperty}} {{#if ReverseNavHasComment}} {{#newline}} /// {{#newline}} /// {{ReverseNavComment}}{{#newline}} /// {{#newline}} {{/if}} {{#each AdditionalReverseNavigationsDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{#each AdditionalDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{Definition}}{{#newline}} {{/each}} {{/if}} {{#if HasForeignKey}} {{#newline}} {{ForeignKeyTitleComment}} {{#each ForeignKeys}} {{#if HasFkComment}} {{#newline}} /// {{#newline}} /// {{FkComment}}{{#newline}} /// {{#newline}} {{/if}} {{#each AdditionalForeignKeysDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{#each AdditionalDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{Definition}}{{#newline}} {{/each}} {{/if}} {{#if CreateConstructor}} {{#newline}} public {{NameHumanCaseWithSuffix}}(){{#newline}} {{{#newline}} {{#each ColumnsWithDefaults}} {{NameHumanCase}} = {{Default}};{{#newline}} {{/each}} {{#each ReverseNavigationCtor}} {{this}}{{#newline}} {{/each}} {{#if EntityClassesArePartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}} {{#if EntityClassesArePartial}} {{#newline}} partial void InitializePartial();{{#newline}} {{/if}} {{/if}} }{{#newline}} "; } public override List PocoConfigurationUsings(PocoConfigurationModel data) { var usings = new List { "Microsoft.EntityFrameworkCore", "Microsoft.EntityFrameworkCore.Metadata.Builders" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (Settings.TrimCharFields) usings.Add("Microsoft.EntityFrameworkCore.Storage.ValueConversion"); if(data.UsesDictionary) usings.Add("System.Collections.Generic"); return usings; } public override string PocoConfiguration() { return @" {{ClassComment}} {{ClassModifier}} class {{ConfigurationClassName}} : IEntityTypeConfiguration<{{NameHumanCaseWithSuffix}}>{{#newline}} {{{#newline}} public void Configure(EntityTypeBuilder<{{NameHumanCaseWithSuffix}}> builder){{#newline}} {{{#newline}} {{#if NotUsingDataAnnotations}} {{#if HasSchema}} builder.{{ToTableOrView}}(""{{Name}}"", ""{{Schema}}"");{{#newline}} {{#else}} builder.{{ToTableOrView}}(""{{Name}}"");{{#newline}} {{/if}} {{/if}} {{PrimaryKeyNameHumanCase}}{{#newline}}{{#newline}} {{#each Columns}} {{this}}{{#newline}} {{/each}} {{#if HasForeignKey}} {{#newline}} // Foreign keys{{#newline}} {{#each ForeignKeys}} {{this}}{{#newline}} {{/each}} {{/if}} {{#each MappingConfiguration}} builder.{{this}}{{#newline}} {{/each}} {{#if HasIndexes}} {{#newline}} {{#each Indexes}} {{this}}{{#newline}} {{/each}} {{/if}} {{#if ConfigurationClassesArePartial}} {{#newline}} InitializePartial(builder);{{#newline}} {{/if}} }{{#newline}} {{#if ConfigurationClassesArePartial}} {{#newline}} partial void InitializePartial(EntityTypeBuilder<{{NameHumanCaseWithSuffix}}> builder);{{#newline}} {{/if}} }{{#newline}}"; } public override List StoredProcReturnModelUsings() { var usings = new List { "System", "System.Collections.Generic" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string StoredProcReturnModels() { return @" {{ResultClassModifiers}} class {{WriteStoredProcReturnModelName}}{{#newline}} {{{#newline}} {{#if SingleModel}} {{#each SingleModelReturnColumns}} {{this}}{{#newline}} {{/each}} {{#else}} {{#each MultipleModelReturnColumns}} public class ResultSetModel{{Model}}{{#newline}} {{{#newline}} {{#each ReturnColumns}} {{this}}{{#newline}} {{/each}} }{{#newline}} public List ResultSet{{Model}}{{PropertyGetSet}}{{#newline}} {{/each}} {{/if}} }{{#newline}} "; } public override List EnumUsings() { var usings = new List(); if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string Enums() { return @" {{#each EnumAttributes}} {{this}}{{#newline}} {{/each}} public enum {{EnumName}}{{#newline}} {{{#newline}} {{#each Items}} {{#each Attributes}} {{this}}{{#newline}} {{/each}} {{Key}} = {{Value}},{{#newline}} {{/each}} }{{#newline}} "; } } /// /// {{Mustache}} template documentation available at https://github.com/jehugaleahsa/mustache-sharp /// public class TemplateEfCore7 : Template { public override string Usings() { return @" {{#each this}} using {{this}};{{#newline}} {{/each}}"; } public override List DatabaseContextInterfaceUsings(InterfaceModel data) { var usings = new List { "System", "System.Data", "System.Threading.Tasks", "System.Threading" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("Microsoft.EntityFrameworkCore"); usings.Add("Microsoft.EntityFrameworkCore.Infrastructure"); usings.Add("System.Linq"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Collections.Generic"); usings.Add("Microsoft.EntityFrameworkCore.ChangeTracking"); usings.Add("System.Linq"); usings.Add("System.Linq.Expressions"); } return usings; } public override string DatabaseContextInterface() { return @" {{interfaceModifier}} interface {{DbContextInterfaceName}} : {{DbContextInterfaceBaseClasses}}{{#newline}} {{{#newline}} {{#each tables}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#if AdditionalContextInterfaceItems}} {{#newline}} // Additional interface items{{#newline}} {{/if}} {{#each AdditionalContextInterfaceItems}} {{this}}{{#newline}} {{/each}} {{#if addSaveChanges}} {{#newline}} int SaveChanges();{{#newline}} int SaveChanges(bool acceptAllChangesOnSuccess);{{#newline}} Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));{{#newline}} Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken));{{#newline}} DatabaseFacade Database { get; }{{#newline}} DbSet Set() where TEntity : class;{{#newline}} string ToString();{{#newline}}{{#newline}} EntityEntry Add(object entity);{{#newline}} EntityEntry Add(TEntity entity) where TEntity : class;{{#newline}} Task AddRangeAsync(params object[] entities);{{#newline}} Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default);{{#newline}} ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class;{{#newline}} ValueTask AddAsync(object entity, CancellationToken cancellationToken = default);{{#newline}} void AddRange(IEnumerable entities);{{#newline}} void AddRange(params object[] entities);{{#newline}}{{#newline}} EntityEntry Attach(object entity);{{#newline}} EntityEntry Attach(TEntity entity) where TEntity : class;{{#newline}} void AttachRange(IEnumerable entities);{{#newline}} void AttachRange(params object[] entities);{{#newline}}{{#newline}} EntityEntry Entry(object entity);{{#newline}} EntityEntry Entry(TEntity entity) where TEntity : class;{{#newline}}{{#newline}} TEntity Find(params object[] keyValues) where TEntity : class;{{#newline}} ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) where TEntity : class;{{#newline}} ValueTask FindAsync(params object[] keyValues) where TEntity : class;{{#newline}} ValueTask FindAsync(Type entityType, object[] keyValues, CancellationToken cancellationToken);{{#newline}} ValueTask FindAsync(Type entityType, params object[] keyValues);{{#newline}} object Find(Type entityType, params object[] keyValues);{{#newline}}{{#newline}} EntityEntry Remove(object entity);{{#newline}} EntityEntry Remove(TEntity entity) where TEntity : class;{{#newline}} void RemoveRange(IEnumerable entities);{{#newline}} void RemoveRange(params object[] entities);{{#newline}}{{#newline}} EntityEntry Update(object entity);{{#newline}} EntityEntry Update(TEntity entity) where TEntity : class;{{#newline}} void UpdateRange(IEnumerable entities);{{#newline}} void UpdateRange(params object[] entities);{{#newline}}{{#newline}} IQueryable FromExpression (Expression>> expression);{{#newline}} {{/if}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#if MultipleReturnModels}} // {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseTrue}}); Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#else}} {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseTrue}});{{#newline}} {{/if}} {{#if SingleReturnModel}} {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueTrue}});{{#newline}} {{/if}} {{#else}} int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueTrue}});{{#newline}} {{/if}} {{#if MultipleReturnModels}} // Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseTrue}}); Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#else}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseTrueToken}});{{#newline}} {{/if}} {{/if}} {{#newline}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseTrue}}); // {{Schema}}.{{Name}}{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseTrue}}); // {{Schema}}.{{Name}}{{#newline}} {{/each}} {{/if}} }"; } public override List DatabaseContextUsings(ContextModel data) { var usings = new List { "System", "System.Data", "System.Data.SqlTypes", "Microsoft.EntityFrameworkCore", "System.Threading.Tasks", "System.Threading" }; switch (Settings.DatabaseType) { case DatabaseType.SqlServer: case DatabaseType.SqlCe: case DatabaseType.Plugin: usings.Add("Microsoft.Data.SqlClient"); break; case DatabaseType.SQLite: usings.Add("Microsoft.Data.Sqlite"); break; case DatabaseType.PostgreSQL: usings.Add("Npgsql"); usings.Add("NpgsqlTypes"); break; case DatabaseType.MySql: break; case DatabaseType.Oracle: break; } if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("System.Linq"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if(Settings.OnConfiguration == OnConfiguration.Configuration) usings.Add("Microsoft.Extensions.Configuration"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Collections.Generic"); usings.Add("Microsoft.EntityFrameworkCore.ChangeTracking"); } return usings; } public override string DatabaseContext() { return @" {{DbContextClassModifiers}} class {{DbContextName}} : {{DbContextBaseClass}}{{contextInterface}}{{#newline}} {{{#newline}} {{#if OnConfigurationUsesConfiguration}} private readonly IConfiguration _configuration;{{#newline}}{{#newline}} {{/if}} {{#if AddParameterlessConstructorToDbContext}} public {{DbContextName}}(){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{/if}} public {{DbContextName}}(DbContextOptions<{{DbContextName}}> options){{#newline}} : base(options){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{#if OnConfigurationUsesConfiguration}} public {{DbContextName}}(IConfiguration configuration){{#newline}} {{{#newline}} _configuration = configuration;{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{/if}} {{#each tables}} {{DbSetModifier}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#newline}} {{#if OnConfigurationUsesConfiguration}} protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){{#newline}} {{{#newline}} if (!optionsBuilder.IsConfigured && _configuration != null){{#newline}} {{{#newline}} optionsBuilder.{{UseDatabaseProvider}}(_configuration.GetConnectionString(@""{{ConnectionStringName}}""){{ConnectionStringActions}});{{#newline}} {{#if UseLazyLoadingProxies}} optionsBuilder.UseLazyLoadingProxies();{{#newline}} {{/if}} }{{#newline}} }{{#newline}}{{#newline}} {{/if}} {{#if OnConfigurationUsesConnectionString}} protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){{#newline}} {{{#newline}} if (!optionsBuilder.IsConfigured){{#newline}} {{{#newline}} optionsBuilder.{{UseDatabaseProvider}}(@""{{ConnectionString}}""{{ConnectionStringActions}});{{#newline}} {{#if UseLazyLoadingProxies}} optionsBuilder.UseLazyLoadingProxies();{{#newline}} {{/if}} }{{#newline}} }{{#newline}}{{#newline}} {{/if}} public bool IsSqlParameterNull({{SqlParameter}} param){{#newline}} {{{#newline}} var sqlValue = param.{{SqlParameterValue}};{{#newline}} var nullableValue = sqlValue as INullable;{{#newline}} if (nullableValue != null){{#newline}} return nullableValue.IsNull;{{#newline}} return (sqlValue == null || sqlValue == DBNull.Value);{{#newline}} }{{#newline}}{{#newline}} protected override void OnModelCreating(ModelBuilder modelBuilder){{#newline}} {{{#newline}} base.OnModelCreating(modelBuilder);{{#newline}} {{#if hasSequences}} {{#newline}} {{#each Sequences}} modelBuilder.HasSequence<{{DataType}}>(""{{Name}}"", ""{{Schema}}"").StartsAt({{StartValue}}).IncrementsBy({{IncrementValue}}).IsCyclic({{IsCycleEnabled}}) {{#if hasMinValue}} .HasMin({{MinValue}}) {{/if}} {{#if hasMaxValue}} .HasMax({{MaxValue}}) {{/if}} ;{{#newline}} {{/each}} {{/if}} {{#if hasTables}} {{#newline}} {{#each tables}} modelBuilder.ApplyConfiguration(new {{DbSetConfigName}}());{{#newline}} {{/each}} {{/if}} {{#if hasMemoryOptimisedTables}} {{#newline}} {{#each MemoryOptimisedTables}} modelBuilder.Entity<{{this}}>().ToTable(t => t.IsMemoryOptimized());{{#newline}} {{/each}} {{/if}} {{#if hasTriggers}} {{#newline}} {{#each Triggers}} modelBuilder.Entity<{{TableName}}>().ToTable(tb => tb.HasTrigger(""{{TriggerName}}""));{{#newline}} {{/each}} {{/if}} {{#if hasStoredProcs}} {{#newline}} {{#each storedProcs}} {{#if SingleReturnModel}} modelBuilder.{{StoredProcModelBuilderCommand}}<{{ReturnModelName}}>(){{StoredProcModelBuilderPostCommand}};{{#newline}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#if IncludeModelBuilder}} modelBuilder.{{ModelBuilderCommand}}<{{ReturnClassName}}>(){{ModelBuilderPostCommand}};{{#newline}} {{/if}} {{/each}} {{/if}} {{#if DbContextClassIsPartial}} {{#newline}} OnModelCreatingPartial(modelBuilder);{{#newline}} {{/if}} }{{#newline}} {{#newline}} {{#if DbContextClassIsPartial}} {{#newline}} partial void InitializePartial();{{#newline}} partial void DisposePartial(bool disposing);{{#newline}} partial void OnModelCreatingPartial(ModelBuilder modelBuilder);{{#newline}} static partial void OnCreateModelPartial(ModelBuilder modelBuilder, string schema);{{#newline}} {{/if}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#if MultipleReturnModels}} // public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}) Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#else}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return {{FunctionName}}({{WriteStoredProcFunctionOverloadCall}});{{#newline}} }{{#newline}}{{#newline}} {{/if}} {{#if SingleReturnModel}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}} const string sqlCommand = ""{{Exec}}"";{{#newline}} var procResultData = {{QueryString}}<{{ReturnModelName}}>(){{#newline}} .{{FromSql}}(sqlCommand{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrue}}){{#newline}} .ToList();{{#newline}}{{#newline}} {{WriteStoredProcFunctionSetSqlParametersFalse}} procResult = (int) procResultParam.Value;{{#newline}} return procResultData;{{#newline}} }{{#newline}} {{/if}} {{#else}} public int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}}{{#newline}} Database.{{ExecuteSqlCommand}}(""{{ExecWithNoReturnModel}}""{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrue}});{{#newline}} {{#newline}} {{WriteStoredProcFunctionSetSqlParametersFalse}} return (int)procResultParam.Value;{{#newline}} }{{#newline}} {{/if}} {{#newline}} {{#if MultipleReturnModels}} // public async Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalse}}) Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#newline}} {{#else}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public async Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} {{#if HasNoReturnModels}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}} {{#newline}} await Database.ExecuteSqlRawAsync(""{{AsyncExec}}""{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrueToken}});{{#newline}} {{#newline}} return (int)procResultParam.Value;{{#newline}} {{#else}} {{WriteStoredProcFunctionDeclareSqlParameterFalse}} {{WriteStoredProcFunctionSetSqlParametersFalse}} const string sqlCommand = ""{{AsyncExec}}"";{{#newline}} var procResultData = await {{QueryString}}<{{ReturnModelName}}>(){{#newline}} .{{FromSql}}(sqlCommand{{WriteStoredProcFunctionSqlParameterAnonymousArrayFalse}}){{#newline}} .ToListAsync();{{#newline}}{{#newline}} return procResultData;{{#newline}} {{/if}} }{{#newline}} {{/if}} {{#newline}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return {{QueryString}}<{{ReturnClassName}}>(){{#newline}} .{{FromSql}}(""SELECT * FROM [{{Schema}}].[{{Name}}]({{WriteStoredProcFunctionSqlAtParams}})""{{WriteTableValuedFunctionSqlParameterAnonymousArray}}){{#newline}} .AsNoTracking();{{#newline}} }{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{#newline}} [DbFunction(""{{Name}}"", ""{{Schema}}"")]{{#newline}} public {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} throw new Exception(""Don't call this directly. Use LINQ to call the scalar valued function as part of your query"");{{#newline}} }{{#newline}} {{/each}} {{/if}} }"; } public override List DatabaseContextFactoryUsings(FactoryModel data) { var usings = new List { "Microsoft.EntityFrameworkCore.Design" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string DatabaseContextFactory() { return @" {{classModifier}} class {{contextName}}Factory : IDesignTimeDbContextFactory<{{contextName}}>{{#newline}} {{{#newline}} public {{contextName}} CreateDbContext(string[] args){{#newline}} {{{#newline}} return new {{contextName}}();{{#newline}} }{{#newline}} }"; } public override List FakeDatabaseContextUsings(FakeContextModel data, IDbContextFilter filter) { var usings = new List { "System", "System.Data", "System.Threading.Tasks", "System.Threading", "Microsoft.EntityFrameworkCore.Infrastructure" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("System.Linq"); usings.Add("Microsoft.EntityFrameworkCore"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Collections.Generic"); usings.Add("Microsoft.EntityFrameworkCore.ChangeTracking"); usings.Add("System.Linq"); usings.Add("System.Linq.Expressions"); } if (Settings.DatabaseType == DatabaseType.SqlCe) { usings.Add("Microsoft.Data.SqlClient"); //usings.Add("System.DBNull"); usings.Add("System.Data.SqlTypes"); } if (Settings.DatabaseType == DatabaseType.PostgreSQL) { usings.Add("Npgsql"); usings.Add("NpgsqlTypes"); } return usings; } public override string FakeDatabaseContext() { return @" {{DbContextClassModifiers}} class Fake{{DbContextName}}{{contextInterface}}{{#newline}} {{{#newline}} {{#each tables}} {{DbSetModifier}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#newline}} public Fake{{DbContextName}}(){{#newline}} {{{#newline}} _database = new FakeDatabaseFacade(new {{DbContextName}}());{{#newline}} {{#newline}} {{#each tables}} {{PluralTableName}} = new FakeDbSet<{{DbSetName}}>({{DbSetPrimaryKeys}});{{#newline}} {{/each}} {{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}} {{#newline}} public int SaveChangesCount { get; private set; }{{#newline}} public virtual int SaveChanges(){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return 1;{{#newline}} }{{#newline}}{{#newline}} public virtual int SaveChanges(bool acceptAllChangesOnSuccess){{#newline}} {{{#newline}} return SaveChanges();{{#newline}} }{{#newline}}{{#newline}} public virtual Task SaveChangesAsync(CancellationToken cancellationToken){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return Task.Factory.StartNew(() => 1, cancellationToken);{{#newline}} }{{#newline}} public virtual Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return Task.Factory.StartNew(x => 1, acceptAllChangesOnSuccess, cancellationToken);{{#newline}} }{{#newline}}{{#newline}} {{#if DbContextClassIsPartial}} partial void InitializePartial();{{#newline}} {{#newline}} {{/if}} protected virtual void Dispose(bool disposing){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public void Dispose(){{#newline}} {{{#newline}} Dispose(true);{{#newline}} }{{#newline}}{{#newline}} private DatabaseFacade _database;{{#newline}} public DatabaseFacade Database { get { return _database; } }{{#newline}}{{#newline}} public DbSet Set() where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public override string ToString(){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Add(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Add(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual Task AddRangeAsync(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual async Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} await Task.CompletedTask;{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual async ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class{{#newline}} {{{#newline}} await Task.CompletedTask;{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual async ValueTask AddAsync(object entity, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} await Task.CompletedTask;{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AddRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AddRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Attach(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Attach(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AttachRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AttachRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Entry(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Entry(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual TEntity Find(params object[] keyValues) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(params object[] keyValues) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(Type entityType, object[] keyValues, CancellationToken cancellationToken){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(Type entityType, params object[] keyValues){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual object Find(Type entityType, params object[] keyValues){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Remove(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Remove(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void RemoveRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void RemoveRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Update(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Update(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void UpdateRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void UpdateRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual IQueryable FromExpression (Expression>> expression){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#newline}} {{#if CreateDbSetForReturnModel}} public DbSet<{{ReturnModelName}}> {{ReturnModelName}} { get; set; }{{#newline}} {{/if}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return {{FunctionName}}({{WriteStoredProcFunctionOverloadCall}});{{#newline}} }{{#newline}}{{#newline}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} procResult = 0;{{#newline}} return new {{ReturnType}}();{{#newline}} }{{#newline}} {{#newline}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return Task.FromResult({{FunctionName}}({{WriteStoredProcFunctionOverloadCall}}));{{#newline}} }{{#newline}} {{/if}} {{#else}} {{#newline}} public int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} return 0;{{#newline}} }{{#newline}} {{#newline}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public Task {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} return Task.FromResult(0);{{#newline}} }{{#newline}} {{/if}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return new List<{{ReturnClassName}}>().AsQueryable();{{#newline}} }{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return default({{ReturnType}});{{#newline}} }{{#newline}} {{/each}} {{/if}} }"; } public override List FakeDbSetUsings(FakeDbSetModel data) { var usings = new List { "System", "System.Collections", "System.ComponentModel", "System.Linq", "System.Linq.Expressions", "System.Reflection", "System.Collections.ObjectModel", "System.Collections.Generic", "System.Threading", "System.Threading.Tasks", "Microsoft.EntityFrameworkCore", "Microsoft.EntityFrameworkCore.Query", "Microsoft.EntityFrameworkCore.Query.Internal", "Microsoft.EntityFrameworkCore.Infrastructure", "Microsoft.EntityFrameworkCore.ChangeTracking", "Microsoft.EntityFrameworkCore.Storage", "Microsoft.EntityFrameworkCore.Metadata" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string FakeDbSet() { return @" // ************************************************************************{{#newline}} // Fake DbSet{{#newline}} // Implementing Find:{{#newline}} // The Find method is difficult to implement in a generic fashion. If{{#newline}} // you need to test code that makes use of the Find method it is{{#newline}} // easiest to create a test DbSet for each of the entity types that{{#newline}} // need to support find. You can then write logic to find that{{#newline}} // particular type of entity, as shown below:{{#newline}} // public class FakeBlogDbSet : FakeDbSet{{#newline}} // {{{#newline}} // public override Blog Find(params object[] keyValues){{#newline}} // {{{#newline}} // var id = (int) keyValues.Single();{{#newline}} // return this.SingleOrDefault(b => b.BlogId == id);{{#newline}} // }{{#newline}} // }{{#newline}} // Read more about it here: https://msdn.microsoft.com/en-us/data/dn314431.aspx{{#newline}} {{DbContextClassModifiers}} class FakeDbSet :{{#newline}} DbSet,{{#newline}} IQueryable,{{#newline}} IAsyncEnumerable,{{#newline}} IListSource,{{#newline}} IResettableService{{#newline}} where TEntity : class{{#newline}} {{{#newline}} private readonly PropertyInfo[] _primaryKeys;{{#newline}} private ObservableCollection _data;{{#newline}} private IQueryable _query;{{#newline}} public override IEntityType EntityType { get; }{{#newline}}{{#newline}} public FakeDbSet(){{#newline}} {{{#newline}} _primaryKeys = null;{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} public FakeDbSet(params string[] primaryKeys){{#newline}} {{{#newline}} _primaryKeys = typeof(TEntity).GetProperties().Where(x => primaryKeys.Contains(x.Name)).ToArray();{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} public override TEntity Find(params object[] keyValues){{#newline}} {{{#newline}} if (_primaryKeys == null){{#newline}} throw new ArgumentException(""No primary keys defined"");{{#newline}} if (keyValues.Length != _primaryKeys.Length){{#newline}} throw new ArgumentException(""Incorrect number of keys passed to Find method"");{{#newline}}{{#newline}} var keyQuery = this.AsQueryable();{{#newline}} keyQuery = keyValues{{#newline}} .Select((t, i) => i){{#newline}} .Aggregate(keyQuery,{{#newline}} (current, x) =>{{#newline}} current.Where(entity => _primaryKeys[x].GetValue(entity, null).Equals(keyValues[x])));{{#newline}}{{#newline}} return keyQuery.SingleOrDefault();{{#newline}} }{{#newline}}{{#newline}} public override ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken){{#newline}} {{{#newline}} return new ValueTask(Task.Factory.StartNew(() => Find(keyValues), cancellationToken));{{#newline}} }{{#newline}}{{#newline}} public override ValueTask FindAsync(params object[] keyValues){{#newline}} {{{#newline}} return new ValueTask(Task.Factory.StartNew(() => Find(keyValues)));{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Add(TEntity entity){{#newline}} {{{#newline}} _data.Add(entity);{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} return new ValueTask>(Task>.Factory.StartNew(() => Add(entity), cancellationToken));{{#newline}} }{{#newline}}{{#newline}} public override void AddRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} foreach (var entity in entities){{#newline}} _data.Add(entity);{{#newline}} }{{#newline}}{{#newline}} public override void AddRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} foreach (var entity in entities){{#newline}} _data.Add(entity);{{#newline}} }{{#newline}}{{#newline}} public override Task AddRangeAsync(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} return Task.Factory.StartNew(() => AddRange(entities));{{#newline}} }{{#newline}}{{#newline}} public override Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} return Task.Factory.StartNew(() => AddRange(entities), cancellationToken);{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Attach(TEntity entity){{#newline}} {{{#newline}} if (entity == null) throw new ArgumentNullException(""entity"");{{#newline}} return Add(entity);{{#newline}} }{{#newline}}{{#newline}} public override void AttachRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} AddRange(entities);{{#newline}} }{{#newline}}{{#newline}} public override void AttachRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} AddRange(entities);{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Remove(TEntity entity){{#newline}} {{{#newline}} _data.Remove(entity);{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override void RemoveRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} foreach (var entity in entities.ToList()){{#newline}} _data.Remove(entity);{{#newline}} }{{#newline}}{{#newline}} public override void RemoveRange(IEnumerable entities){{#newline}} {{{#newline}} RemoveRange(entities.ToArray());{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Update(TEntity entity){{#newline}} {{{#newline}} _data.Remove(entity);{{#newline}} _data.Add(entity);{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override void UpdateRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} RemoveRange(entities);{{#newline}} AddRange(entities);{{#newline}} }{{#newline}}{{#newline}} public override void UpdateRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} var array = entities.ToArray(); RemoveRange(array);{{#newline}} AddRange(array);{{#newline}} }{{#newline}}{{#newline}} bool IListSource.ContainsListCollection => true;{{#newline}}{{#newline}} public IList GetList(){{#newline}} {{{#newline}} return _data;{{#newline}} }{{#newline}}{{#newline}} IList IListSource.GetList(){{#newline}} {{{#newline}} return _data;{{#newline}} }{{#newline}}{{#newline}} Type IQueryable.ElementType{{#newline}} {{{#newline}} get { return _query.ElementType; }{{#newline}} }{{#newline}}{{#newline}} Expression IQueryable.Expression{{#newline}} {{{#newline}} get { return _query.Expression; }{{#newline}} }{{#newline}}{{#newline}} IQueryProvider IQueryable.Provider{{#newline}} {{{#newline}} get { return new FakeDbAsyncQueryProvider(_data); }{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return _data.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return _data.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}}{{#newline}} public void ResetState(){{#newline}} {{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} }{{#newline}}{{#newline}} public Task ResetStateAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.Factory.StartNew(() => ResetState());{{#newline}} }{{#newline}} {{#if DbContextClassIsPartial}} {{#newline}} partial void InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncQueryProvider : FakeQueryProvider, IAsyncEnumerable, IAsyncQueryProvider{{#newline}} {{{#newline}} public FakeDbAsyncQueryProvider(Expression expression) : base(expression){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public FakeDbAsyncQueryProvider(IEnumerable enumerable) : base(enumerable){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public TResult ExecuteAsync(Expression expression, CancellationToken cancellationToken){{#newline}} {{{#newline}} var expectedResultType = typeof(TResult).GetGenericArguments()[0];{{#newline}} var executionResult = typeof(IQueryProvider){{#newline}} .GetMethods(){{#newline}} .First(method => method.Name == nameof(IQueryProvider.Execute) && method.IsGenericMethod){{#newline}} .MakeGenericMethod(expectedResultType){{#newline}} .Invoke(this, new object[] { expression });{{#newline}}{{#newline}} return (TResult) typeof(Task).GetMethod(nameof(Task.FromResult)){{#newline}} ?.MakeGenericMethod(expectedResultType){{#newline}} .Invoke(null, new[] { executionResult });{{#newline}} }{{#newline}}{{#newline}} public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncEnumerable : EnumerableQuery, IAsyncEnumerable, IQueryable{{#newline}} {{{#newline}} public FakeDbAsyncEnumerable(IEnumerable enumerable){{#newline}} : base(enumerable){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public FakeDbAsyncEnumerable(Expression expression){{#newline}} : base(expression){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}}{{#newline}} IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken){{#newline}} {{{#newline}} return GetAsyncEnumerator(cancellationToken);{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return this.AsEnumerable().GetEnumerator();{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncEnumerator : IAsyncEnumerator{{#newline}} {{{#newline}} private readonly IEnumerator _inner;{{#newline}}{{#newline}} public FakeDbAsyncEnumerator(IEnumerator inner){{#newline}} {{{#newline}} _inner = inner;{{#newline}} }{{#newline}}{{#newline}} public T Current{{#newline}} {{{#newline}} get { return _inner.Current; }{{#newline}} }{{#newline}}{{#newline}} public ValueTask MoveNextAsync(){{#newline}} {{{#newline}} return new ValueTask(_inner.MoveNext());{{#newline}} }{{#newline}}{{#newline}} public ValueTask DisposeAsync(){{#newline}} {{{#newline}} _inner.Dispose();{{#newline}} return new ValueTask(Task.CompletedTask);{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} public abstract class FakeQueryProvider : IOrderedQueryable, IQueryProvider{{#newline}} {{{#newline}} private IEnumerable _enumerable;{{#newline}}{{#newline}} protected FakeQueryProvider(Expression expression){{#newline}} {{{#newline}} Expression = expression;{{#newline}} }{{#newline}}{{#newline}} protected FakeQueryProvider(IEnumerable enumerable){{#newline}} {{{#newline}} _enumerable = enumerable;{{#newline}} Expression = enumerable.AsQueryable().Expression;{{#newline}} }{{#newline}}{{#newline}} public IQueryable CreateQuery(Expression expression){{#newline}} {{{#newline}} if (expression is MethodCallExpression m){{#newline}} {{{#newline}} var resultType = m.Method.ReturnType; // it should be IQueryable{{#newline}} var tElement = resultType.GetGenericArguments().First();{{#newline}} return (IQueryable) CreateInstance(tElement, expression);{{#newline}} }{{#newline}}{{#newline}} return CreateQuery(expression);{{#newline}} }{{#newline}}{{#newline}} public IQueryable CreateQuery(Expression expression){{#newline}} {{{#newline}} return (IQueryable) CreateInstance(typeof(TEntity), expression);{{#newline}} }{{#newline}}{{#newline}} private object CreateInstance(Type tElement, Expression expression){{#newline}} {{{#newline}} var queryType = GetType().GetGenericTypeDefinition().MakeGenericType(tElement);{{#newline}} return Activator.CreateInstance(queryType, expression);{{#newline}} }{{#newline}}{{#newline}} public object Execute(Expression expression){{#newline}} {{{#newline}} return CompileExpressionItem(expression);{{#newline}} }{{#newline}}{{#newline}} public TResult Execute(Expression expression){{#newline}} {{{#newline}} return CompileExpressionItem(expression);{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} if (_enumerable == null) _enumerable = CompileExpressionItem>(Expression);{{#newline}} return _enumerable.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} if (_enumerable == null) _enumerable = CompileExpressionItem>(Expression);{{#newline}} return _enumerable.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} public Type ElementType => typeof(T);{{#newline}}{{#newline}} public Expression Expression { get; }{{#newline}}{{#newline}} public IQueryProvider Provider => this;{{#newline}}{{#newline}} private static TResult CompileExpressionItem(Expression expression){{#newline}} {{{#newline}} var visitor = new FakeExpressionVisitor();{{#newline}} var body = visitor.Visit(expression);{{#newline}} var f = Expression.Lambda>(body ?? throw new InvalidOperationException(string.Format(""{0} is null"", nameof(body))), (IEnumerable) null);{{#newline}} return f.Compile()();{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeExpressionVisitor : ExpressionVisitor{{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public class FakeDatabaseFacade : DatabaseFacade{{#newline}} {{{#newline}} public FakeDatabaseFacade(DbContext context) : base(context){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public override bool EnsureCreated(){{#newline}} {{{#newline}} return true;{{#newline}} }{{#newline}}{{#newline}} public override Task EnsureCreatedAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(EnsureCreated());{{#newline}} }{{#newline}}{{#newline}} public override bool EnsureDeleted(){{#newline}} {{{#newline}} return true;{{#newline}} }{{#newline}}{{#newline}} public override Task EnsureDeletedAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(EnsureDeleted());{{#newline}} }{{#newline}}{{#newline}} public override bool CanConnect(){{#newline}} {{{#newline}} return true;{{#newline}} }{{#newline}}{{#newline}} public override Task CanConnectAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(CanConnect());{{#newline}} }{{#newline}}{{#newline}} public override IDbContextTransaction BeginTransaction(){{#newline}} {{{#newline}} return new FakeDbContextTransaction();{{#newline}} }{{#newline}}{{#newline}} public override Task BeginTransactionAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(BeginTransaction());{{#newline}} }{{#newline}}{{#newline}} public override void CommitTransaction(){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public override Task CommitTransactionAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.CompletedTask;{{#newline}} }{{#newline}}{{#newline}} public override void RollbackTransaction(){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public override Task RollbackTransactionAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.CompletedTask;{{#newline}} }{{#newline}}{{#newline}} public override IExecutionStrategy CreateExecutionStrategy(){{#newline}} {{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override string ToString(){{#newline}} {{{#newline}} return string.Empty;{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} public class FakeDbContextTransaction : IDbContextTransaction{{#newline}} {{{#newline}} public Guid TransactionId => Guid.NewGuid();{{#newline}} public void Commit() { }{{#newline}} public void Rollback() { }{{#newline}} public Task CommitAsync(CancellationToken cancellationToken = new CancellationToken()) => Task.CompletedTask;{{#newline}} public Task RollbackAsync(CancellationToken cancellationToken = new CancellationToken()) => Task.CompletedTask;{{#newline}} public void Dispose() { }{{#newline}} public ValueTask DisposeAsync() => default;{{#newline}} }"; } public override List PocoUsings(PocoModel data) { var usings = new List { "System", "System.Collections.Generic", "System.Threading", "System.Threading.Tasks", }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if(data.HasHierarchyId) usings.Add("Microsoft.EntityFrameworkCore"); return usings; } public override string Poco() { return @" {{#if UseHasNoKey}} {{#else}} {{#if HasNoPrimaryKey}} // The table '{{Name}}' is not usable by entity framework because it{{#newline}} // does not have a primary key. It is listed here for completeness.{{#newline}} {{/if}} {{/if}} {{ClassComment}} {{ExtendedComments}} {{ClassAttributes}} {{ClassModifier}} class {{NameHumanCaseWithSuffix}}{{BaseClasses}}{{#newline}} {{{#newline}} {{InsideClassBody}} {{#each Columns}} {{#if AddNewLineBefore}}{{#newline}}{{/if}} {{#if HasSummaryComments}} /// {{#newline}} /// {{SummaryComments}}{{#newline}} /// {{#newline}} {{/if}} {{#each Attributes}} {{this}}{{#newline}} {{/each}} public {{#if OverrideModifier}}override {{/if}}{{WrapIfNullable}} {{NameHumanCase}} { get; {{PrivateSetterForComputedColumns}}set; }{{PropertyInitialisers}}{{InlineComments}}{{#newline}} {{#if IncludeFieldNameConstants}} public const string {{NameHumanCase}}Field = ""{{NameHumanCase}}"";{{#newline}}{{/if}} {{/each}} {{#if HasReverseNavigation}} {{#newline}} // Reverse navigation{{#newline}} {{#each ReverseNavigationProperty}} {{#if ReverseNavHasComment}} {{#newline}} /// {{#newline}} /// {{ReverseNavComment}}{{#newline}} /// {{#newline}} {{/if}} {{#each AdditionalReverseNavigationsDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{#each AdditionalDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{Definition}}{{#newline}} {{/each}} {{/if}} {{#if HasForeignKey}} {{#newline}} {{ForeignKeyTitleComment}} {{#each ForeignKeys}} {{#if HasFkComment}} {{#newline}} /// {{#newline}} /// {{FkComment}}{{#newline}} /// {{#newline}} {{/if}} {{#each AdditionalForeignKeysDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{#each AdditionalDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{Definition}}{{#newline}} {{/each}} {{/if}} {{#if CreateConstructor}} {{#newline}} public {{NameHumanCaseWithSuffix}}(){{#newline}} {{{#newline}} {{#each ColumnsWithDefaults}} {{NameHumanCase}} = {{Default}};{{#newline}} {{/each}} {{#each ReverseNavigationCtor}} {{this}}{{#newline}} {{/each}} {{#if EntityClassesArePartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}} {{#if EntityClassesArePartial}} {{#newline}} partial void InitializePartial();{{#newline}} {{/if}} {{/if}} }{{#newline}} "; } public override List PocoConfigurationUsings(PocoConfigurationModel data) { var usings = new List { "Microsoft.EntityFrameworkCore", "Microsoft.EntityFrameworkCore.Metadata.Builders" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (Settings.TrimCharFields) usings.Add("Microsoft.EntityFrameworkCore.Storage.ValueConversion"); if(data.UsesDictionary) usings.Add("System.Collections.Generic"); return usings; } public override string PocoConfiguration() { return @" {{ClassComment}} {{ClassModifier}} class {{ConfigurationClassName}} : IEntityTypeConfiguration<{{NameHumanCaseWithSuffix}}>{{#newline}} {{{#newline}} public void Configure(EntityTypeBuilder<{{NameHumanCaseWithSuffix}}> builder){{#newline}} {{{#newline}} {{#if NotUsingDataAnnotations}} {{#if HasSchema}} builder.{{ToTableOrView}}(""{{Name}}"", ""{{Schema}}"");{{#newline}} {{#else}} builder.{{ToTableOrView}}(""{{Name}}"");{{#newline}} {{/if}} {{/if}} {{PrimaryKeyNameHumanCase}}{{#newline}}{{#newline}} {{#each Columns}} {{this}}{{#newline}} {{/each}} {{#if HasForeignKey}} {{#newline}} // Foreign keys{{#newline}} {{#each ForeignKeys}} {{this}}{{#newline}} {{/each}} {{/if}} {{#each MappingConfiguration}} builder.{{this}}{{#newline}} {{/each}} {{#if HasIndexes}} {{#newline}} {{#each Indexes}} {{this}}{{#newline}} {{/each}} {{/if}} {{#if ConfigurationClassesArePartial}} {{#newline}} InitializePartial(builder);{{#newline}} {{/if}} }{{#newline}} {{#if ConfigurationClassesArePartial}} {{#newline}} partial void InitializePartial(EntityTypeBuilder<{{NameHumanCaseWithSuffix}}> builder);{{#newline}} {{/if}} }{{#newline}}"; } public override List StoredProcReturnModelUsings() { var usings = new List { "System", "System.Collections.Generic" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string StoredProcReturnModels() { return @" {{ResultClassModifiers}} class {{WriteStoredProcReturnModelName}}{{#newline}} {{{#newline}} {{#if SingleModel}} {{#each SingleModelReturnColumns}} {{this}}{{#newline}} {{/each}} {{#else}} {{#each MultipleModelReturnColumns}} public class ResultSetModel{{Model}}{{#newline}} {{{#newline}} {{#each ReturnColumns}} {{this}}{{#newline}} {{/each}} }{{#newline}} public List ResultSet{{Model}}{{PropertyGetSet}}{{#newline}} {{/each}} {{/if}} }{{#newline}} "; } public override List EnumUsings() { var usings = new List(); if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string Enums() { return @" {{#each EnumAttributes}} {{this}}{{#newline}} {{/each}} public enum {{EnumName}}{{#newline}} {{{#newline}} {{#each Items}} {{#each Attributes}} {{this}}{{#newline}} {{/each}} {{Key}} = {{Value}},{{#newline}} {{/each}} }{{#newline}} "; } } /// /// {{Mustache}} template documentation available at https://github.com/jehugaleahsa/mustache-sharp /// public class TemplateEfCore8 : Template { public override string Usings() { return @" {{#each this}} using {{this}};{{#newline}} {{/each}}"; } public override List DatabaseContextInterfaceUsings(InterfaceModel data) { var usings = new List { "System", "System.Data", "System.Threading.Tasks", "System.Threading" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("Microsoft.EntityFrameworkCore"); usings.Add("Microsoft.EntityFrameworkCore.Infrastructure"); usings.Add("System.Linq"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Collections.Generic"); usings.Add("Microsoft.EntityFrameworkCore.ChangeTracking"); usings.Add("System.Linq"); usings.Add("System.Linq.Expressions"); } return usings; } public override string DatabaseContextInterface() { return @" {{interfaceModifier}} interface {{DbContextInterfaceName}} : {{DbContextInterfaceBaseClasses}}{{#newline}} {{{#newline}} {{#each tables}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#if AdditionalContextInterfaceItems}} {{#newline}} // Additional interface items{{#newline}} {{/if}} {{#each AdditionalContextInterfaceItems}} {{this}}{{#newline}} {{/each}} {{#if addSaveChanges}} {{#newline}} int SaveChanges();{{#newline}} int SaveChanges(bool acceptAllChangesOnSuccess);{{#newline}} Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));{{#newline}} Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken));{{#newline}} DatabaseFacade Database { get; }{{#newline}} DbSet Set() where TEntity : class;{{#newline}} string ToString();{{#newline}}{{#newline}} EntityEntry Add(object entity);{{#newline}} EntityEntry Add(TEntity entity) where TEntity : class;{{#newline}} Task AddRangeAsync(params object[] entities);{{#newline}} Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default);{{#newline}} ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class;{{#newline}} ValueTask AddAsync(object entity, CancellationToken cancellationToken = default);{{#newline}} void AddRange(IEnumerable entities);{{#newline}} void AddRange(params object[] entities);{{#newline}}{{#newline}} EntityEntry Attach(object entity);{{#newline}} EntityEntry Attach(TEntity entity) where TEntity : class;{{#newline}} void AttachRange(IEnumerable entities);{{#newline}} void AttachRange(params object[] entities);{{#newline}}{{#newline}} EntityEntry Entry(object entity);{{#newline}} EntityEntry Entry(TEntity entity) where TEntity : class;{{#newline}}{{#newline}} TEntity Find(params object[] keyValues) where TEntity : class;{{#newline}} ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) where TEntity : class;{{#newline}} ValueTask FindAsync(params object[] keyValues) where TEntity : class;{{#newline}} ValueTask FindAsync(Type entityType, object[] keyValues, CancellationToken cancellationToken);{{#newline}} ValueTask FindAsync(Type entityType, params object[] keyValues);{{#newline}} object Find(Type entityType, params object[] keyValues);{{#newline}}{{#newline}} EntityEntry Remove(object entity);{{#newline}} EntityEntry Remove(TEntity entity) where TEntity : class;{{#newline}} void RemoveRange(IEnumerable entities);{{#newline}} void RemoveRange(params object[] entities);{{#newline}}{{#newline}} EntityEntry Update(object entity);{{#newline}} EntityEntry Update(TEntity entity) where TEntity : class;{{#newline}} void UpdateRange(IEnumerable entities);{{#newline}} void UpdateRange(params object[] entities);{{#newline}}{{#newline}} IQueryable FromExpression (Expression>> expression);{{#newline}} {{/if}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#if MultipleReturnModels}} // {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseTrue}}); Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#else}} {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseTrue}});{{#newline}} {{/if}} {{#if SingleReturnModel}} {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueTrue}});{{#newline}} {{/if}} {{#else}} int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueTrue}});{{#newline}} {{/if}} {{#if MultipleReturnModels}} // Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseTrue}}); Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#else}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseTrueToken}});{{#newline}} {{/if}} {{/if}} {{#newline}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseTrue}}); // {{Schema}}.{{Name}}{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseTrue}}); // {{Schema}}.{{Name}}{{#newline}} {{/each}} {{/if}} }"; } public override List DatabaseContextUsings(ContextModel data) { var usings = new List { "System", "System.Data", "System.Data.SqlTypes", "Microsoft.EntityFrameworkCore", "System.Threading.Tasks", "System.Threading" }; switch (Settings.DatabaseType) { case DatabaseType.SqlServer: case DatabaseType.SqlCe: case DatabaseType.Plugin: usings.Add("Microsoft.Data.SqlClient"); break; case DatabaseType.SQLite: usings.Add("Microsoft.Data.Sqlite"); break; case DatabaseType.PostgreSQL: usings.Add("Npgsql"); usings.Add("NpgsqlTypes"); break; case DatabaseType.MySql: break; case DatabaseType.Oracle: break; } if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("System.Linq"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if(Settings.OnConfiguration == OnConfiguration.Configuration) usings.Add("Microsoft.Extensions.Configuration"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Collections.Generic"); usings.Add("Microsoft.EntityFrameworkCore.ChangeTracking"); } return usings; } public override string DatabaseContext() { return @" {{DbContextClassModifiers}} class {{DbContextName}} : {{DbContextBaseClass}}{{contextInterface}}{{#newline}} {{{#newline}} {{#if OnConfigurationUsesConfiguration}} private readonly IConfiguration _configuration;{{#newline}}{{#newline}} {{/if}} {{#if AddParameterlessConstructorToDbContext}} public {{DbContextName}}(){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{/if}} public {{DbContextName}}(DbContextOptions<{{DbContextName}}> options){{#newline}} : base(options){{#newline}} {{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{#if OnConfigurationUsesConfiguration}} public {{DbContextName}}(IConfiguration configuration){{#newline}} {{{#newline}} _configuration = configuration;{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{/if}} {{#each tables}} {{DbSetModifier}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#newline}} {{#if OnConfigurationUsesConfiguration}} protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){{#newline}} {{{#newline}} if (!optionsBuilder.IsConfigured && _configuration != null){{#newline}} {{{#newline}} optionsBuilder.{{UseDatabaseProvider}}(_configuration.GetConnectionString(@""{{ConnectionStringName}}""){{ConnectionStringActions}});{{#newline}} {{#if UseLazyLoadingProxies}} optionsBuilder.UseLazyLoadingProxies();{{#newline}} {{/if}} }{{#newline}} }{{#newline}}{{#newline}} {{/if}} {{#if OnConfigurationUsesConnectionString}} protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){{#newline}} {{{#newline}} if (!optionsBuilder.IsConfigured){{#newline}} {{{#newline}} optionsBuilder.{{UseDatabaseProvider}}(@""{{ConnectionString}}""{{ConnectionStringActions}});{{#newline}} {{#if UseLazyLoadingProxies}} optionsBuilder.UseLazyLoadingProxies();{{#newline}} {{/if}} }{{#newline}} }{{#newline}}{{#newline}} {{/if}} public bool IsSqlParameterNull({{SqlParameter}} param){{#newline}} {{{#newline}} var sqlValue = param.{{SqlParameterValue}};{{#newline}} var nullableValue = sqlValue as INullable;{{#newline}} if (nullableValue != null){{#newline}} return nullableValue.IsNull;{{#newline}} return (sqlValue == null || sqlValue == DBNull.Value);{{#newline}} }{{#newline}}{{#newline}} protected override void OnModelCreating(ModelBuilder modelBuilder){{#newline}} {{{#newline}} base.OnModelCreating(modelBuilder);{{#newline}} {{#if hasSequences}} {{#newline}} {{#each Sequences}} modelBuilder.HasSequence<{{DataType}}>(""{{Name}}"", ""{{Schema}}"").StartsAt({{StartValue}}).IncrementsBy({{IncrementValue}}).IsCyclic({{IsCycleEnabled}}) {{#if hasMinValue}} .HasMin({{MinValue}}) {{/if}} {{#if hasMaxValue}} .HasMax({{MaxValue}}) {{/if}} ;{{#newline}} {{/each}} {{/if}} {{#if hasTables}} {{#newline}} {{#each tables}} modelBuilder.ApplyConfiguration(new {{DbSetConfigName}}());{{#newline}} {{/each}} {{/if}} {{#if hasMemoryOptimisedTables}} {{#newline}} {{#each MemoryOptimisedTables}} modelBuilder.Entity<{{this}}>().ToTable(t => t.IsMemoryOptimized());{{#newline}} {{/each}} {{/if}} {{#if hasTriggers}} {{#newline}} {{#each Triggers}} modelBuilder.Entity<{{TableName}}>().ToTable(tb => tb.HasTrigger(""{{TriggerName}}""));{{#newline}} {{/each}} {{/if}} {{#if hasStoredProcs}} {{#newline}} {{#each storedProcs}} {{#if SingleReturnModel}} modelBuilder.{{StoredProcModelBuilderCommand}}<{{ReturnModelName}}>(){{StoredProcModelBuilderPostCommand}};{{#newline}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#if IncludeModelBuilder}} modelBuilder.{{ModelBuilderCommand}}<{{ReturnClassName}}>(){{ModelBuilderPostCommand}};{{#newline}} {{/if}} {{/each}} {{/if}} {{#if DbContextClassIsPartial}} {{#newline}} OnModelCreatingPartial(modelBuilder);{{#newline}} {{/if}} }{{#newline}} {{#newline}} {{#if DbContextClassIsPartial}} {{#newline}} partial void InitializePartial();{{#newline}} partial void DisposePartial(bool disposing);{{#newline}} partial void OnModelCreatingPartial(ModelBuilder modelBuilder);{{#newline}} static partial void OnCreateModelPartial(ModelBuilder modelBuilder, string schema);{{#newline}} {{/if}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#if MultipleReturnModels}} // public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}) Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#else}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return {{FunctionName}}({{WriteStoredProcFunctionOverloadCall}});{{#newline}} }{{#newline}}{{#newline}} {{/if}} {{#if SingleReturnModel}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}} const string sqlCommand = ""{{Exec}}"";{{#newline}} var procResultData = {{QueryString}}<{{ReturnModelName}}>(){{#newline}} .{{FromSql}}(sqlCommand{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrue}}){{#newline}} .ToList();{{#newline}}{{#newline}} {{WriteStoredProcFunctionSetSqlParametersFalse}} procResult = (int) procResultParam.Value;{{#newline}} return procResultData;{{#newline}} }{{#newline}} {{/if}} {{#else}} public int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}}{{#newline}} Database.{{ExecuteSqlCommand}}(""{{ExecWithNoReturnModel}}""{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrue}});{{#newline}} {{#newline}} {{WriteStoredProcFunctionSetSqlParametersFalse}} return (int)procResultParam.Value;{{#newline}} }{{#newline}} {{/if}} {{#newline}} {{#if MultipleReturnModels}} // public async Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalse}}) Cannot be created as EF Core does not yet support stored procedures with multiple result sets.{{#newline}} {{#newline}} {{#else}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public async Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} {{#if HasNoReturnModels}} {{WriteStoredProcFunctionDeclareSqlParameterTrue}} {{#newline}} await Database.ExecuteSqlRawAsync(""{{AsyncExec}}""{{WriteStoredProcFunctionSqlParameterAnonymousArrayTrueToken}});{{#newline}} {{#newline}} return (int)procResultParam.Value;{{#newline}} {{#else}} {{WriteStoredProcFunctionDeclareSqlParameterFalse}} {{WriteStoredProcFunctionSetSqlParametersFalse}} const string sqlCommand = ""{{AsyncExec}}"";{{#newline}} var procResultData = await {{QueryString}}<{{ReturnModelName}}>(){{#newline}} .{{FromSql}}(sqlCommand{{WriteStoredProcFunctionSqlParameterAnonymousArrayFalse}}){{#newline}} .ToListAsync();{{#newline}}{{#newline}} return procResultData;{{#newline}} {{/if}} }{{#newline}} {{/if}} {{#newline}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return {{QueryString}}<{{ReturnClassName}}>(){{#newline}} .{{FromSql}}(""SELECT * FROM [{{Schema}}].[{{Name}}]({{WriteStoredProcFunctionSqlAtParams}})""{{WriteTableValuedFunctionSqlParameterAnonymousArray}}){{#newline}} .AsNoTracking();{{#newline}} }{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{#newline}} [DbFunction(""{{Name}}"", ""{{Schema}}"")]{{#newline}} public {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} throw new Exception(""Don't call this directly. Use LINQ to call the scalar valued function as part of your query"");{{#newline}} }{{#newline}} {{/each}} {{/if}} }"; } public override List DatabaseContextFactoryUsings(FactoryModel data) { var usings = new List { "Microsoft.EntityFrameworkCore.Design" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string DatabaseContextFactory() { return @" {{classModifier}} class {{contextName}}Factory : IDesignTimeDbContextFactory<{{contextName}}>{{#newline}} {{{#newline}} public {{contextName}} CreateDbContext(string[] args){{#newline}} {{{#newline}} return new {{contextName}}();{{#newline}} }{{#newline}} }"; } public override List FakeDatabaseContextUsings(FakeContextModel data, IDbContextFilter filter) { var usings = new List { "System", "System.Data", "System.Threading.Tasks", "System.Threading", "Microsoft.EntityFrameworkCore.Infrastructure" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (data.tables.Any() || data.hasStoredProcs) { usings.Add("System.Linq"); usings.Add("Microsoft.EntityFrameworkCore"); } if (data.hasStoredProcs) usings.Add("System.Collections.Generic"); if (!Settings.UseInheritedBaseInterfaceFunctions) { usings.Add("System.Collections.Generic"); usings.Add("Microsoft.EntityFrameworkCore.ChangeTracking"); usings.Add("System.Linq"); usings.Add("System.Linq.Expressions"); } if (Settings.DatabaseType == DatabaseType.SqlCe) { usings.Add("Microsoft.Data.SqlClient"); //usings.Add("System.DBNull"); usings.Add("System.Data.SqlTypes"); } if (Settings.DatabaseType == DatabaseType.PostgreSQL) { usings.Add("Npgsql"); usings.Add("NpgsqlTypes"); } return usings; } public override string FakeDatabaseContext() { return @" {{DbContextClassModifiers}} class Fake{{DbContextName}}{{contextInterface}}{{#newline}} {{{#newline}} {{#each tables}} {{DbSetModifier}} DbSet<{{DbSetName}}> {{PluralTableName}} { get; set; }{{Comment}}{{#newline}} {{/each}} {{#newline}} public Fake{{DbContextName}}(){{#newline}} {{{#newline}} _database = new FakeDatabaseFacade(new {{DbContextName}}());{{#newline}} {{#newline}} {{#each tables}} {{PluralTableName}} = new FakeDbSet<{{DbSetName}}>({{DbSetPrimaryKeys}});{{#newline}} {{/each}} {{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}} {{#newline}} public int SaveChangesCount { get; private set; }{{#newline}} public virtual int SaveChanges(){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return 1;{{#newline}} }{{#newline}}{{#newline}} public virtual int SaveChanges(bool acceptAllChangesOnSuccess){{#newline}} {{{#newline}} return SaveChanges();{{#newline}} }{{#newline}}{{#newline}} public virtual Task SaveChangesAsync(CancellationToken cancellationToken){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return Task.Factory.StartNew(() => 1, cancellationToken);{{#newline}} }{{#newline}} public virtual Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken){{#newline}} {{{#newline}} ++SaveChangesCount;{{#newline}} return Task.Factory.StartNew(x => 1, acceptAllChangesOnSuccess, cancellationToken);{{#newline}} }{{#newline}}{{#newline}} {{#if DbContextClassIsPartial}} partial void InitializePartial();{{#newline}} {{#newline}} {{/if}} protected virtual void Dispose(bool disposing){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public void Dispose(){{#newline}} {{{#newline}} Dispose(true);{{#newline}} }{{#newline}}{{#newline}} private DatabaseFacade _database;{{#newline}} public DatabaseFacade Database { get { return _database; } }{{#newline}}{{#newline}} public DbSet Set() where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public override string ToString(){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Add(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Add(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual Task AddRangeAsync(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual async Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} await Task.CompletedTask;{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual async ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class{{#newline}} {{{#newline}} await Task.CompletedTask;{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual async ValueTask AddAsync(object entity, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} await Task.CompletedTask;{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AddRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AddRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Attach(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Attach(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AttachRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void AttachRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Entry(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Entry(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual TEntity Find(params object[] keyValues) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(params object[] keyValues) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(Type entityType, object[] keyValues, CancellationToken cancellationToken){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual ValueTask FindAsync(Type entityType, params object[] keyValues){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual object Find(Type entityType, params object[] keyValues){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Remove(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Remove(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void RemoveRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void RemoveRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Update(object entity){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual EntityEntry Update(TEntity entity) where TEntity : class{{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void UpdateRange(IEnumerable entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual void UpdateRange(params object[] entities){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} public virtual IQueryable FromExpression (Expression>> expression){{#newline}} {{{#newline}} throw new NotImplementedException();{{#newline}} }{{#newline}}{{#newline}} {{#if hasStoredProcs}} {{#newline}} // Stored Procedures{{#newline}} {{#each storedProcs}} {{#if HasReturnModels}} {{#newline}} {{#if CreateDbSetForReturnModel}} public DbSet<{{ReturnModelName}}> {{ReturnModelName}} { get; set; }{{#newline}} {{/if}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return {{FunctionName}}({{WriteStoredProcFunctionOverloadCall}});{{#newline}} }{{#newline}}{{#newline}} public {{ReturnType}} {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} procResult = 0;{{#newline}} return new {{ReturnType}}();{{#newline}} }{{#newline}} {{#newline}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public Task<{{ReturnType}}> {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} int procResult;{{#newline}} return Task.FromResult({{FunctionName}}({{WriteStoredProcFunctionOverloadCall}}));{{#newline}} }{{#newline}} {{/if}} {{#else}} {{#newline}} public int {{FunctionName}}({{WriteStoredProcFunctionParamsTrueFalse}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} return 0;{{#newline}} }{{#newline}} {{#newline}} {{#if AsyncFunctionCannotBeCreated}} // {{FunctionName}}Async() cannot be created due to having out parameters, or is relying on the procedure result ({{ReturnType}}){{#newline}} {{#else}} public Task {{FunctionName}}Async({{WriteStoredProcFunctionParamsFalseFalseToken}}){{#newline}} {{{#newline}} {{WriteStoredProcFunctionSetSqlParametersTrue}} return Task.FromResult(0);{{#newline}} }{{#newline}} {{/if}} {{/if}} {{/each}} {{/if}} {{#if hasTableValuedFunctions}} {{#newline}} // Table Valued Functions{{#newline}} {{#each tableValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public IQueryable<{{ReturnClassName}}> {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return new List<{{ReturnClassName}}>().AsQueryable();{{#newline}} }{{#newline}} {{/each}} {{/if}} {{#if hasScalarValuedFunctions}} {{#newline}} // Scalar Valued Functions{{#newline}} {{#each scalarValuedFunctions}} {{#newline}} // {{Schema}}.{{Name}}{{#newline}} public {{ReturnType}} {{ExecName}}({{WriteStoredProcFunctionParamsFalseFalse}}){{#newline}} {{{#newline}} return default({{ReturnType}});{{#newline}} }{{#newline}} {{/each}} {{/if}} }"; } public override List FakeDbSetUsings(FakeDbSetModel data) { var usings = new List { "System", "System.Collections", "System.ComponentModel", "System.Linq", "System.Linq.Expressions", "System.Reflection", "System.Collections.ObjectModel", "System.Collections.Generic", "System.Threading", "System.Threading.Tasks", "Microsoft.EntityFrameworkCore", "Microsoft.EntityFrameworkCore.Query", "Microsoft.EntityFrameworkCore.Query.Internal", "Microsoft.EntityFrameworkCore.Infrastructure", "Microsoft.EntityFrameworkCore.ChangeTracking", "Microsoft.EntityFrameworkCore.Storage", "Microsoft.EntityFrameworkCore.Metadata" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string FakeDbSet() { return @" // ************************************************************************{{#newline}} // Fake DbSet{{#newline}} // Implementing Find:{{#newline}} // The Find method is difficult to implement in a generic fashion. If{{#newline}} // you need to test code that makes use of the Find method it is{{#newline}} // easiest to create a test DbSet for each of the entity types that{{#newline}} // need to support find. You can then write logic to find that{{#newline}} // particular type of entity, as shown below:{{#newline}} // public class FakeBlogDbSet : FakeDbSet{{#newline}} // {{{#newline}} // public override Blog Find(params object[] keyValues){{#newline}} // {{{#newline}} // var id = (int) keyValues.Single();{{#newline}} // return this.SingleOrDefault(b => b.BlogId == id);{{#newline}} // }{{#newline}} // }{{#newline}} // Read more about it here: https://msdn.microsoft.com/en-us/data/dn314431.aspx{{#newline}} {{DbContextClassModifiers}} class FakeDbSet :{{#newline}} DbSet,{{#newline}} IQueryable,{{#newline}} IAsyncEnumerable,{{#newline}} IListSource,{{#newline}} IResettableService{{#newline}} where TEntity : class{{#newline}} {{{#newline}} private readonly PropertyInfo[] _primaryKeys;{{#newline}} private ObservableCollection _data;{{#newline}} private IQueryable _query;{{#newline}} public override IEntityType EntityType { get; }{{#newline}}{{#newline}} public FakeDbSet(){{#newline}} {{{#newline}} _primaryKeys = null;{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} public FakeDbSet(params string[] primaryKeys){{#newline}} {{{#newline}} _primaryKeys = typeof(TEntity).GetProperties().Where(x => primaryKeys.Contains(x.Name)).ToArray();{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} {{#if DbContextClassIsPartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} public override TEntity Find(params object[] keyValues){{#newline}} {{{#newline}} if (_primaryKeys == null){{#newline}} throw new ArgumentException(""No primary keys defined"");{{#newline}} if (keyValues.Length != _primaryKeys.Length){{#newline}} throw new ArgumentException(""Incorrect number of keys passed to Find method"");{{#newline}}{{#newline}} var keyQuery = this.AsQueryable();{{#newline}} keyQuery = keyValues{{#newline}} .Select((t, i) => i){{#newline}} .Aggregate(keyQuery,{{#newline}} (current, x) =>{{#newline}} current.Where(entity => _primaryKeys[x].GetValue(entity, null).Equals(keyValues[x])));{{#newline}}{{#newline}} return keyQuery.SingleOrDefault();{{#newline}} }{{#newline}}{{#newline}} public override ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken){{#newline}} {{{#newline}} return new ValueTask(Task.Factory.StartNew(() => Find(keyValues), cancellationToken));{{#newline}} }{{#newline}}{{#newline}} public override ValueTask FindAsync(params object[] keyValues){{#newline}} {{{#newline}} return new ValueTask(Task.Factory.StartNew(() => Find(keyValues)));{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Add(TEntity entity){{#newline}} {{{#newline}} _data.Add(entity);{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} return new ValueTask>(Task>.Factory.StartNew(() => Add(entity), cancellationToken));{{#newline}} }{{#newline}}{{#newline}} public override void AddRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} foreach (var entity in entities){{#newline}} _data.Add(entity);{{#newline}} }{{#newline}}{{#newline}} public override void AddRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} foreach (var entity in entities){{#newline}} _data.Add(entity);{{#newline}} }{{#newline}}{{#newline}} public override Task AddRangeAsync(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} return Task.Factory.StartNew(() => AddRange(entities));{{#newline}} }{{#newline}}{{#newline}} public override Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} return Task.Factory.StartNew(() => AddRange(entities), cancellationToken);{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Attach(TEntity entity){{#newline}} {{{#newline}} if (entity == null) throw new ArgumentNullException(""entity"");{{#newline}} return Add(entity);{{#newline}} }{{#newline}}{{#newline}} public override void AttachRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} AddRange(entities);{{#newline}} }{{#newline}}{{#newline}} public override void AttachRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} AddRange(entities);{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Remove(TEntity entity){{#newline}} {{{#newline}} _data.Remove(entity);{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override void RemoveRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} foreach (var entity in entities.ToList()){{#newline}} _data.Remove(entity);{{#newline}} }{{#newline}}{{#newline}} public override void RemoveRange(IEnumerable entities){{#newline}} {{{#newline}} RemoveRange(entities.ToArray());{{#newline}} }{{#newline}}{{#newline}} public override EntityEntry Update(TEntity entity){{#newline}} {{{#newline}} _data.Remove(entity);{{#newline}} _data.Add(entity);{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override void UpdateRange(params TEntity[] entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} RemoveRange(entities);{{#newline}} AddRange(entities);{{#newline}} }{{#newline}}{{#newline}} public override void UpdateRange(IEnumerable entities){{#newline}} {{{#newline}} if (entities == null) throw new ArgumentNullException(""entities"");{{#newline}} var array = entities.ToArray(); RemoveRange(array);{{#newline}} AddRange(array);{{#newline}} }{{#newline}}{{#newline}} bool IListSource.ContainsListCollection => true;{{#newline}}{{#newline}} public IList GetList(){{#newline}} {{{#newline}} return _data;{{#newline}} }{{#newline}}{{#newline}} IList IListSource.GetList(){{#newline}} {{{#newline}} return _data;{{#newline}} }{{#newline}}{{#newline}} Type IQueryable.ElementType{{#newline}} {{{#newline}} get { return _query.ElementType; }{{#newline}} }{{#newline}}{{#newline}} Expression IQueryable.Expression{{#newline}} {{{#newline}} get { return _query.Expression; }{{#newline}} }{{#newline}}{{#newline}} IQueryProvider IQueryable.Provider{{#newline}} {{{#newline}} get { return new FakeDbAsyncQueryProvider(_data); }{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return _data.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return _data.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}}{{#newline}} public void ResetState(){{#newline}} {{{#newline}} _data = new ObservableCollection();{{#newline}} _query = _data.AsQueryable();{{#newline}} }{{#newline}}{{#newline}} public Task ResetStateAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.Factory.StartNew(() => ResetState());{{#newline}} }{{#newline}} {{#if DbContextClassIsPartial}} {{#newline}} partial void InitializePartial();{{#newline}} {{/if}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncQueryProvider : FakeQueryProvider, IAsyncEnumerable, IAsyncQueryProvider{{#newline}} {{{#newline}} public FakeDbAsyncQueryProvider(Expression expression) : base(expression){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public FakeDbAsyncQueryProvider(IEnumerable enumerable) : base(enumerable){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public TResult ExecuteAsync(Expression expression, CancellationToken cancellationToken){{#newline}} {{{#newline}} var expectedResultType = typeof(TResult).GetGenericArguments()[0];{{#newline}} var executionResult = typeof(IQueryProvider){{#newline}} .GetMethods(){{#newline}} .First(method => method.Name == nameof(IQueryProvider.Execute) && method.IsGenericMethod){{#newline}} .MakeGenericMethod(expectedResultType){{#newline}} .Invoke(this, new object[] { expression });{{#newline}}{{#newline}} return (TResult) typeof(Task).GetMethod(nameof(Task.FromResult)){{#newline}} ?.MakeGenericMethod(expectedResultType){{#newline}} .Invoke(null, new[] { executionResult });{{#newline}} }{{#newline}}{{#newline}} public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncEnumerable : EnumerableQuery, IAsyncEnumerable, IQueryable{{#newline}} {{{#newline}} public FakeDbAsyncEnumerable(IEnumerable enumerable){{#newline}} : base(enumerable){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public FakeDbAsyncEnumerable(Expression expression){{#newline}} : base(expression){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return new FakeDbAsyncEnumerator(this.AsEnumerable().GetEnumerator());{{#newline}} }{{#newline}}{{#newline}} IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken){{#newline}} {{{#newline}} return GetAsyncEnumerator(cancellationToken);{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} return this.AsEnumerable().GetEnumerator();{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeDbAsyncEnumerator : IAsyncEnumerator{{#newline}} {{{#newline}} private readonly IEnumerator _inner;{{#newline}}{{#newline}} public FakeDbAsyncEnumerator(IEnumerator inner){{#newline}} {{{#newline}} _inner = inner;{{#newline}} }{{#newline}}{{#newline}} public T Current{{#newline}} {{{#newline}} get { return _inner.Current; }{{#newline}} }{{#newline}}{{#newline}} public ValueTask MoveNextAsync(){{#newline}} {{{#newline}} return new ValueTask(_inner.MoveNext());{{#newline}} }{{#newline}}{{#newline}} public ValueTask DisposeAsync(){{#newline}} {{{#newline}} _inner.Dispose();{{#newline}} return new ValueTask(Task.CompletedTask);{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} public abstract class FakeQueryProvider : IOrderedQueryable, IQueryProvider{{#newline}} {{{#newline}} private IEnumerable _enumerable;{{#newline}}{{#newline}} protected FakeQueryProvider(Expression expression){{#newline}} {{{#newline}} Expression = expression;{{#newline}} }{{#newline}}{{#newline}} protected FakeQueryProvider(IEnumerable enumerable){{#newline}} {{{#newline}} _enumerable = enumerable;{{#newline}} Expression = enumerable.AsQueryable().Expression;{{#newline}} }{{#newline}}{{#newline}} public IQueryable CreateQuery(Expression expression){{#newline}} {{{#newline}} if (expression is MethodCallExpression m){{#newline}} {{{#newline}} var resultType = m.Method.ReturnType; // it should be IQueryable{{#newline}} var tElement = resultType.GetGenericArguments().First();{{#newline}} return (IQueryable) CreateInstance(tElement, expression);{{#newline}} }{{#newline}}{{#newline}} return CreateQuery(expression);{{#newline}} }{{#newline}}{{#newline}} public IQueryable CreateQuery(Expression expression){{#newline}} {{{#newline}} return (IQueryable) CreateInstance(typeof(TEntity), expression);{{#newline}} }{{#newline}}{{#newline}} private object CreateInstance(Type tElement, Expression expression){{#newline}} {{{#newline}} var queryType = GetType().GetGenericTypeDefinition().MakeGenericType(tElement);{{#newline}} return Activator.CreateInstance(queryType, expression);{{#newline}} }{{#newline}}{{#newline}} public object Execute(Expression expression){{#newline}} {{{#newline}} return CompileExpressionItem(expression);{{#newline}} }{{#newline}}{{#newline}} public TResult Execute(Expression expression){{#newline}} {{{#newline}} return CompileExpressionItem(expression);{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} if (_enumerable == null) _enumerable = CompileExpressionItem>(Expression);{{#newline}} return _enumerable.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} IEnumerator IEnumerable.GetEnumerator(){{#newline}} {{{#newline}} if (_enumerable == null) _enumerable = CompileExpressionItem>(Expression);{{#newline}} return _enumerable.GetEnumerator();{{#newline}} }{{#newline}}{{#newline}} public Type ElementType => typeof(T);{{#newline}}{{#newline}} public Expression Expression { get; }{{#newline}}{{#newline}} public IQueryProvider Provider => this;{{#newline}}{{#newline}} private static TResult CompileExpressionItem(Expression expression){{#newline}} {{{#newline}} var visitor = new FakeExpressionVisitor();{{#newline}} var body = visitor.Visit(expression);{{#newline}} var f = Expression.Lambda>(body ?? throw new InvalidOperationException(string.Format(""{0} is null"", nameof(body))), (IEnumerable) null);{{#newline}} return f.Compile()();{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} {{DbContextClassModifiers}} class FakeExpressionVisitor : ExpressionVisitor{{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public class FakeDatabaseFacade : DatabaseFacade{{#newline}} {{{#newline}} public FakeDatabaseFacade(DbContext context) : base(context){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public override bool EnsureCreated(){{#newline}} {{{#newline}} return true;{{#newline}} }{{#newline}}{{#newline}} public override Task EnsureCreatedAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(EnsureCreated());{{#newline}} }{{#newline}}{{#newline}} public override bool EnsureDeleted(){{#newline}} {{{#newline}} return true;{{#newline}} }{{#newline}}{{#newline}} public override Task EnsureDeletedAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(EnsureDeleted());{{#newline}} }{{#newline}}{{#newline}} public override bool CanConnect(){{#newline}} {{{#newline}} return true;{{#newline}} }{{#newline}}{{#newline}} public override Task CanConnectAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(CanConnect());{{#newline}} }{{#newline}}{{#newline}} public override IDbContextTransaction BeginTransaction(){{#newline}} {{{#newline}} return new FakeDbContextTransaction();{{#newline}} }{{#newline}}{{#newline}} public override Task BeginTransactionAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.FromResult(BeginTransaction());{{#newline}} }{{#newline}}{{#newline}} public override void CommitTransaction(){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public override Task CommitTransactionAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.CompletedTask;{{#newline}} }{{#newline}}{{#newline}} public override void RollbackTransaction(){{#newline}} {{{#newline}} }{{#newline}}{{#newline}} public override Task RollbackTransactionAsync(CancellationToken cancellationToken = new CancellationToken()){{#newline}} {{{#newline}} return Task.CompletedTask;{{#newline}} }{{#newline}}{{#newline}} public override IExecutionStrategy CreateExecutionStrategy(){{#newline}} {{{#newline}} return null;{{#newline}} }{{#newline}}{{#newline}} public override string ToString(){{#newline}} {{{#newline}} return string.Empty;{{#newline}} }{{#newline}} }{{#newline}}{{#newline}} public class FakeDbContextTransaction : IDbContextTransaction{{#newline}} {{{#newline}} public Guid TransactionId => Guid.NewGuid();{{#newline}} public void Commit() { }{{#newline}} public void Rollback() { }{{#newline}} public Task CommitAsync(CancellationToken cancellationToken = new CancellationToken()) => Task.CompletedTask;{{#newline}} public Task RollbackAsync(CancellationToken cancellationToken = new CancellationToken()) => Task.CompletedTask;{{#newline}} public void Dispose() { }{{#newline}} public ValueTask DisposeAsync() => default;{{#newline}} }"; } public override List PocoUsings(PocoModel data) { var usings = new List { "System", "System.Collections.Generic", "System.Threading", "System.Threading.Tasks", }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if(data.HasHierarchyId) usings.Add("Microsoft.EntityFrameworkCore"); return usings; } public override string Poco() { return @" {{#if UseHasNoKey}} {{#else}} {{#if HasNoPrimaryKey}} // The table '{{Name}}' is not usable by entity framework because it{{#newline}} // does not have a primary key. It is listed here for completeness.{{#newline}} {{/if}} {{/if}} {{ClassComment}} {{ExtendedComments}} {{ClassAttributes}} {{ClassModifier}} class {{NameHumanCaseWithSuffix}}{{BaseClasses}}{{#newline}} {{{#newline}} {{InsideClassBody}} {{#each Columns}} {{#if AddNewLineBefore}}{{#newline}}{{/if}} {{#if HasSummaryComments}} /// {{#newline}} /// {{SummaryComments}}{{#newline}} /// {{#newline}} {{/if}} {{#each Attributes}} {{this}}{{#newline}} {{/each}} public {{#if OverrideModifier}}override {{/if}}{{WrapIfNullable}} {{NameHumanCase}} { get; {{PrivateSetterForComputedColumns}}set; }{{PropertyInitialisers}}{{InlineComments}}{{#newline}} {{#if IncludeFieldNameConstants}} public const string {{NameHumanCase}}Field = ""{{NameHumanCase}}"";{{#newline}}{{/if}} {{/each}} {{#if HasReverseNavigation}} {{#newline}} // Reverse navigation{{#newline}} {{#each ReverseNavigationProperty}} {{#if ReverseNavHasComment}} {{#newline}} /// {{#newline}} /// {{ReverseNavComment}}{{#newline}} /// {{#newline}} {{/if}} {{#each AdditionalReverseNavigationsDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{#each AdditionalDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{Definition}}{{#newline}} {{/each}} {{/if}} {{#if HasForeignKey}} {{#newline}} {{ForeignKeyTitleComment}} {{#each ForeignKeys}} {{#if HasFkComment}} {{#newline}} /// {{#newline}} /// {{FkComment}}{{#newline}} /// {{#newline}} {{/if}} {{#each AdditionalForeignKeysDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{#each AdditionalDataAnnotations}} [{{this}}]{{#newline}} {{/each}} {{Definition}}{{#newline}} {{/each}} {{/if}} {{#if CreateConstructor}} {{#newline}} public {{NameHumanCaseWithSuffix}}(){{#newline}} {{{#newline}} {{#each ColumnsWithDefaults}} {{NameHumanCase}} = {{Default}};{{#newline}} {{/each}} {{#each ReverseNavigationCtor}} {{this}}{{#newline}} {{/each}} {{#if EntityClassesArePartial}} InitializePartial();{{#newline}} {{/if}} }{{#newline}} {{#if EntityClassesArePartial}} {{#newline}} partial void InitializePartial();{{#newline}} {{/if}} {{/if}} }{{#newline}} "; } public override List PocoConfigurationUsings(PocoConfigurationModel data) { var usings = new List { "Microsoft.EntityFrameworkCore", "Microsoft.EntityFrameworkCore.Metadata.Builders" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); if (Settings.TrimCharFields) usings.Add("Microsoft.EntityFrameworkCore.Storage.ValueConversion"); if(data.UsesDictionary) usings.Add("System.Collections.Generic"); return usings; } public override string PocoConfiguration() { return @" {{ClassComment}} {{ClassModifier}} class {{ConfigurationClassName}} : IEntityTypeConfiguration<{{NameHumanCaseWithSuffix}}>{{#newline}} {{{#newline}} public void Configure(EntityTypeBuilder<{{NameHumanCaseWithSuffix}}> builder){{#newline}} {{{#newline}} {{#if NotUsingDataAnnotations}} {{#if HasSchema}} builder.{{ToTableOrView}}(""{{Name}}"", ""{{Schema}}"");{{#newline}} {{#else}} builder.{{ToTableOrView}}(""{{Name}}"");{{#newline}} {{/if}} {{/if}} {{PrimaryKeyNameHumanCase}}{{#newline}}{{#newline}} {{#each Columns}} {{this}}{{#newline}} {{/each}} {{#if HasForeignKey}} {{#newline}} // Foreign keys{{#newline}} {{#each ForeignKeys}} {{this}}{{#newline}} {{/each}} {{/if}} {{#each MappingConfiguration}} builder.{{this}}{{#newline}} {{/each}} {{#if HasIndexes}} {{#newline}} {{#each Indexes}} {{this}}{{#newline}} {{/each}} {{/if}} {{#if ConfigurationClassesArePartial}} {{#newline}} InitializePartial(builder);{{#newline}} {{/if}} }{{#newline}} {{#if ConfigurationClassesArePartial}} {{#newline}} partial void InitializePartial(EntityTypeBuilder<{{NameHumanCaseWithSuffix}}> builder);{{#newline}} {{/if}} }{{#newline}}"; } public override List StoredProcReturnModelUsings() { var usings = new List { "System", "System.Collections.Generic" }; if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string StoredProcReturnModels() { return @" {{ResultClassModifiers}} class {{WriteStoredProcReturnModelName}}{{#newline}} {{{#newline}} {{#if SingleModel}} {{#each SingleModelReturnColumns}} {{this}}{{#newline}} {{/each}} {{#else}} {{#each MultipleModelReturnColumns}} public class ResultSetModel{{Model}}{{#newline}} {{{#newline}} {{#each ReturnColumns}} {{this}}{{#newline}} {{/each}} }{{#newline}} public List ResultSet{{Model}}{{PropertyGetSet}}{{#newline}} {{/each}} {{/if}} }{{#newline}} "; } public override List EnumUsings() { var usings = new List(); if (Settings.IncludeCodeGeneratedAttribute) usings.Add("System.CodeDom.Compiler"); return usings; } public override string Enums() { return @" {{#each EnumAttributes}} {{this}}{{#newline}} {{/each}} public enum {{EnumName}}{{#newline}} {{{#newline}} {{#each Items}} {{#each Attributes}} {{this}}{{#newline}} {{/each}} {{Key}} = {{Value}},{{#newline}} {{/each}} }{{#newline}} "; } } public static class TemplateFactory { public static Template Create() { switch (Settings.TemplateType) { case TemplateType.Ef6: return new TemplateEf6(); case TemplateType.EfCore3: return new TemplateEfCore3(); case TemplateType.EfCore6: return new TemplateEfCore6(); case TemplateType.EfCore7: return new TemplateEfCore7(); case TemplateType.EfCore8: return new TemplateEfCore8(); case TemplateType.FileBasedEf6: case TemplateType.FileBasedCore3: case TemplateType.FileBasedCore6: case TemplateType.FileBasedCore7: case TemplateType.FileBasedCore8: return new TemplateFileBased(); default: throw new ArgumentOutOfRangeException(); } } } /// /// {{Mustache}} template documentation available at https://github.com/jehugaleahsa/mustache-sharp /// public class TemplateFileBased : Template { private readonly Dictionary _cacheText; private readonly Dictionary> _cacheList; public TemplateFileBased() { _cacheText = new Dictionary(); _cacheList = new Dictionary>(); } public override string Usings() { return CacheText(TemplateFileBasedConstants.Mustache.Usings); } public override List DatabaseContextInterfaceUsings(InterfaceModel data) { return CacheList(TemplateFileBasedConstants.Text.DatabaseContextInterfaceUsings); } public override string DatabaseContextInterface() { return CacheText(TemplateFileBasedConstants.Mustache.DatabaseContextInterface); } public override List DatabaseContextUsings(ContextModel data) { return CacheList(TemplateFileBasedConstants.Text.DatabaseContextUsings); } public override string DatabaseContext() { return CacheText(TemplateFileBasedConstants.Mustache.DatabaseContext); } public override List DatabaseContextFactoryUsings(FactoryModel data) { return CacheList(TemplateFileBasedConstants.Text.DatabaseContextFactoryUsings); } public override string DatabaseContextFactory() { return CacheText(TemplateFileBasedConstants.Mustache.DatabaseContextFactory); } public override List FakeDatabaseContextUsings(FakeContextModel data, IDbContextFilter filter) { return CacheList(TemplateFileBasedConstants.Text.FakeDatabaseContextUsings); } public override string FakeDatabaseContext() { return CacheText(TemplateFileBasedConstants.Mustache.FakeDatabaseContext); } public override List FakeDbSetUsings(FakeDbSetModel data) { return CacheList(TemplateFileBasedConstants.Text.FakeDbSetUsings); } public override string FakeDbSet() { return CacheText(TemplateFileBasedConstants.Mustache.FakeDbSet); } public override List PocoUsings(PocoModel data) { return CacheList(TemplateFileBasedConstants.Text.PocoUsings); } public override string Poco() { return CacheText(TemplateFileBasedConstants.Mustache.Poco); } public override List PocoConfigurationUsings(PocoConfigurationModel data) { return CacheList(TemplateFileBasedConstants.Text.PocoConfigurationUsings); } public override string PocoConfiguration() { return CacheText(TemplateFileBasedConstants.Mustache.PocoConfiguration); } public override List StoredProcReturnModelUsings() { return CacheList(TemplateFileBasedConstants.Text.StoredProcReturnModelUsings); } public override string StoredProcReturnModels() { return CacheText(TemplateFileBasedConstants.Mustache.StoredProcReturnModels); } public override List EnumUsings() { return CacheList(TemplateFileBasedConstants.Text.EnumUsings); } public override string Enums() { return CacheText(TemplateFileBasedConstants.Mustache.Enums); } private string CacheText(string filename) { if (_cacheText.ContainsKey(filename)) return _cacheText[filename]; var file = Path.Combine(Settings.TemplateFolder, filename); var text = File.ReadAllText(file); _cacheText.Add(filename, text); return text; } private List CacheList(string filename) { if (_cacheList.ContainsKey(filename)) return _cacheList[filename]; var file = Path.Combine(Settings.TemplateFolder, filename); var lines = File.ReadLines(file).ToList(); _cacheList.Add(filename, lines); return lines; } } public static class TemplateFileBasedConstants { public static class Mustache { public const string Usings = "Usings.mustache"; public const string DatabaseContextInterface = "DatabaseContextInterface.mustache"; public const string DatabaseContext = "DatabaseContext.mustache"; public const string DatabaseContextFactory = "DatabaseContextFactory.mustache"; public const string FakeDatabaseContext = "FakeDatabaseContext.mustache"; public const string FakeDbSet = "FakeDbSet.mustache"; public const string Poco = "Poco.mustache"; public const string PocoConfiguration = "PocoConfiguration.mustache"; public const string StoredProcReturnModels = "StoredProcReturnModels.mustache"; public const string Enums = "Enums.mustache"; } public static class Text { public const string DatabaseContextInterfaceUsings = "DatabaseContextInterfaceUsings.txt"; public const string DatabaseContextUsings = "DatabaseContextUsings.txt"; public const string DatabaseContextFactoryUsings = "DatabaseContextFactoryUsings.txt"; public const string FakeDatabaseContextUsings = "FakeDatabaseContextUsings.txt"; public const string FakeDbSetUsings = "FakeDbSetUsings.txt"; public const string PocoUsings = "PocoUsings.txt"; public const string PocoConfigurationUsings = "PocoConfigurationUsings.txt"; public const string StoredProcReturnModelUsings = "StoredProcReturnModelUsings.txt"; public const string EnumUsings = "EnumUsings.txt"; } } public enum TemplateType { Ef6, EfCore3, EfCore6, EfCore7, EfCore8, FileBasedEf6, FileBasedCore3, FileBasedCore6, FileBasedCore7, FileBasedCore8 } /// /// Purpose of this class is to serve the plugin implementation. When reading MultiContextSettings /// from an external DLL the returned object cannot be directly casted because type differs. /// It has to be copied to the local implementation of the MultiContextSettings class /// public static class MultiContextSettingsCopy { /// /// Copies properties with the same Name and Type /// /// /// private static void CopyPropertiesFrom(object source, object dest) { var fromProperties = source.GetType().GetProperties(); var toProperties = dest .GetType().GetProperties(); foreach (var fromProperty in fromProperties) { var toProperty = toProperties.FirstOrDefault(x => fromProperty.Name == x.Name); if (toProperty != null) { if (toProperty.Name == fromProperty.Name && toProperty.PropertyType.FullName == fromProperty.PropertyType.FullName) toProperty.SetValue(dest, fromProperty.GetValue(source)); } } } public static void Copy(object source, MultiContextSettings dest) { CopyPropertiesFrom(source, dest); var fromProperties = source.GetType().GetProperties(); // Tables var fromProperty = fromProperties.FirstOrDefault(x => x.Name == nameof(MultiContextSettings.Tables)); if (fromProperty != null) { var listValue = (IList) fromProperty.GetValue(source); if (listValue != null) { dest.Tables = new List(); foreach (var item in listValue) { var tableSettings = new MultiContextTableSettings(); CopyMultiContextTableSettings(item, tableSettings); dest.Tables.Add(tableSettings); } } } // StoredProcedures fromProperty = fromProperties.FirstOrDefault(x => x.Name == nameof(MultiContextSettings.StoredProcedures)); if (fromProperty != null) { var listValue = (IList) fromProperty.GetValue(source); if (listValue != null) { dest.StoredProcedures = new List(); foreach (var item in listValue) { var spSettings = new MultiContextStoredProcedureSettings(); CopyPropertiesFrom(item, spSettings); dest.StoredProcedures.Add(spSettings); } } } // Functions fromProperty = fromProperties.FirstOrDefault(x => x.Name == nameof(MultiContextSettings.Functions)); if (fromProperty != null) { var listValue = (IList) fromProperty.GetValue(source); if (listValue != null) { dest.Functions = new List(); foreach (var item in listValue) { var functionSettings = new MultiContextFunctionSettings(); CopyPropertiesFrom(item, functionSettings); dest.Functions.Add(functionSettings); } } } // Enumerations fromProperty = fromProperties.FirstOrDefault(x => x.Name == nameof(MultiContextSettings.Enumerations)); if (fromProperty != null) { var listValue = (IList) fromProperty.GetValue(source); if (listValue != null) { dest.Enumerations = new List(); foreach (var item in listValue) { var enumSettings = new EnumerationSettings(); CopyPropertiesFrom(item, enumSettings); dest.Enumerations.Add(enumSettings); } } } // ForeignKeys fromProperty = fromProperties.FirstOrDefault(x => x.Name == nameof(MultiContextSettings.ForeignKeys)); if (fromProperty != null) { var listValue = (IList) fromProperty.GetValue(source); if (listValue != null) { dest.ForeignKeys = new List(); foreach (var item in listValue) { var fkSettings = new MultiContextForeignKeySettings(); CopyPropertiesFrom(item, fkSettings); dest.ForeignKeys.Add(fkSettings); } } } } private static void CopyMultiContextTableSettings(object source, MultiContextTableSettings dest) { CopyPropertiesFrom(source, dest); var fromProperties = source.GetType().GetProperties(); // Columns var fromProperty = fromProperties.FirstOrDefault(x => x.Name == nameof(MultiContextTableSettings.Columns)); if (fromProperty == null) return; var listValue = (IList) fromProperty.GetValue(source); if (listValue == null) return; dest.Columns = new List(); foreach (var item in listValue) { var columnsSettings = new MultiContextColumnSettings(); CopyPropertiesFrom(item, columnsSettings); dest.Columns.Add(columnsSettings); } } } public static void ArgumentNotNull(T arg, string name) where T : class { if (arg == null) { throw new ArgumentNullException(name); } } #>