internal static void Generate(
            DBConnection cn, TextWriter writer, string namespaceDeclaration, IDatabase database, IEnumerable <string> tableNames, Database configuration)
        {
            writer.WriteLine(namespaceDeclaration);
            foreach (var table in tableNames)
            {
                try {
                    CodeGenerationStatics.AddSummaryDocComment(writer, "Contains logic that retrieves rows from the " + table + " table.");
                    writer.WriteLine("public static partial class " + GetClassName(cn, table) + " {");

                    var isRevisionHistoryTable = DataAccessStatics.IsRevisionHistoryTable(table, configuration);
                    var columns = new TableColumns(cn, table, isRevisionHistoryTable);

                    // Write nested classes.
                    DataAccessStatics.WriteRowClasses(
                        writer,
                        columns.AllColumns,
                        localWriter => {
                        if (!isRevisionHistoryTable)
                        {
                            return;
                        }
                        writer.WriteLine(
                            "public UserTransaction Transaction { get { return RevisionHistoryStatics.UserTransactionsById[ RevisionHistoryStatics.RevisionsById[ System.Convert.ToInt32( " +
                            Utility.GetCSharpIdentifier(columns.PrimaryKeyAndRevisionIdColumn.PascalCasedNameExceptForOracle) + " ) ].UserTransactionId ]; } }");
                    },
                        localWriter => {
                        if (!columns.DataColumns.Any())
                        {
                            return;
                        }

                        var modClass = "Modification." + StandardModificationStatics.GetClassName(cn, table, isRevisionHistoryTable, isRevisionHistoryTable);
                        var revisionHistorySuffix = StandardModificationStatics.GetRevisionHistorySuffix(isRevisionHistoryTable);
                        writer.WriteLine("public " + modClass + " ToModification" + revisionHistorySuffix + "() {");
                        writer.WriteLine(
                            "return " + modClass + ".CreateForSingleRowUpdate" + revisionHistorySuffix + "( " +
                            StringTools.ConcatenateWithDelimiter(
                                ", ",
                                columns.AllColumnsExceptRowVersion.Select(i => Utility.GetCSharpIdentifier(i.PascalCasedNameExceptForOracle)).ToArray()) + " );");
                        writer.WriteLine("}");
                    });
                    writeCacheClass(cn, writer, database, table, columns, isRevisionHistoryTable);

                    var isSmallTable = configuration.SmallTables != null && configuration.SmallTables.Any(i => i.EqualsIgnoreCase(table));

                    var tableUsesRowVersionedCaching = configuration.TablesUsingRowVersionedDataCaching != null &&
                                                       configuration.TablesUsingRowVersionedDataCaching.Any(i => i.EqualsIgnoreCase(table));
                    if (tableUsesRowVersionedCaching && columns.RowVersionColumn == null && !(cn.DatabaseInfo is OracleInfo))
                    {
                        throw new ApplicationException(
                                  cn.DatabaseInfo is MySqlInfo
                                                                ? "Row-versioned data caching cannot currently be used with MySQL databases."
                                                                : "Row-versioned data caching can only be used with the {0} table if you add a rowversion column.".FormatWith(table));
                    }

                    if (isSmallTable)
                    {
                        writeGetAllRowsMethod(writer, isRevisionHistoryTable, false);
                    }
                    writeGetRowsMethod(
                        cn,
                        writer,
                        database,
                        table,
                        columns,
                        isSmallTable,
                        tableUsesRowVersionedCaching,
                        isRevisionHistoryTable,
                        false,
                        configuration.CommandTimeoutSecondsTyped);
                    if (isRevisionHistoryTable)
                    {
                        if (isSmallTable)
                        {
                            writeGetAllRowsMethod(writer, true, true);
                        }
                        writeGetRowsMethod(
                            cn,
                            writer,
                            database,
                            table,
                            columns,
                            isSmallTable,
                            tableUsesRowVersionedCaching,
                            true,
                            true,
                            configuration.CommandTimeoutSecondsTyped);
                    }

                    writeGetRowMatchingPkMethod(
                        cn,
                        writer,
                        database,
                        table,
                        columns,
                        isSmallTable,
                        tableUsesRowVersionedCaching,
                        isRevisionHistoryTable,
                        configuration.CommandTimeoutSecondsTyped);

                    if (isRevisionHistoryTable)
                    {
                        DataAccessStatics.WriteGetLatestRevisionsConditionMethod(writer, columns.PrimaryKeyAndRevisionIdColumn.Name);
                    }

                    if (tableUsesRowVersionedCaching)
                    {
                        var keyTupleTypeArguments = getPkAndVersionTupleTypeArguments(cn, columns);

                        writer.WriteLine($"private static Cache<{TypeNames.Tuple}<{keyTupleTypeArguments}>, BasicRow> getRowsByPkAndVersion() {{");
                        var first  = $"VersionedRowDataCache<{TypeNames.Tuple}<{getPkTupleTypeArguments( columns )}>, {TypeNames.Tuple}<{keyTupleTypeArguments}>, BasicRow>";
                        var second = table.TableNameToPascal(cn) + "TableRetrievalRowsByPkAndVersion";
                        var third  = StringTools.ConcatenateWithDelimiter(", ", Enumerable.Range(1, columns.KeyColumns.Count()).Select(i => "i.Item{0}".FormatWith(i)));
                        writer.WriteLine(
                            $@"return AppMemoryCache.GetCacheValue<{first}>( ""{second}"", () => new {first}( i => {TypeNames.Tuple}.Create( {third} ) ) ).RowsByPkAndVersion;");
                        writer.WriteLine("}");
                    }

                    // Initially we did not generate this method for small tables, but we found a need for it when the cache is disabled since that will cause
                    // GetRowMatchingId to repeatedly query.
                    if (columns.KeyColumns.Count() == 1 && columns.KeyColumns.Single().Name.ToLower().EndsWith("id"))
                    {
                        writeToIdDictionaryMethod(writer, columns);
                    }

                    writer.WriteLine("}");                       // class
                }
                catch (Exception e) when(!(e is UserCorrectableException))
                {
                    throw new ApplicationException($"An error occurred while generating TableRetrieval logic for the '{table}' table.", e);
                }
            }
            writer.WriteLine("}");               // namespace
        }
        internal static void Generate(
            DBConnection cn, TextWriter writer, string namespaceDeclaration, Database database,
            RedStapler.StandardLibrary.Configuration.SystemDevelopment.Database configuration)
        {
            writer.WriteLine(namespaceDeclaration);
            foreach (var table in DatabaseOps.GetDatabaseTables(database))
            {
                CodeGenerationStatics.AddSummaryDocComment(writer, "Contains logic that retrieves rows from the " + table + " table.");
                writer.WriteLine("public static partial class " + GetClassName(cn, table) + " {");

                var isRevisionHistoryTable = DataAccessStatics.IsRevisionHistoryTable(table, configuration);
                var columns = new TableColumns(cn, table, isRevisionHistoryTable);

                // Write nested classes.
                DataAccessStatics.WriteRowClasses(
                    writer,
                    columns.AllColumns,
                    localWriter => {
                    if (!columns.DataColumns.Any())
                    {
                        return;
                    }

                    var modClass = database.SecondaryDatabaseName + "Modification." +
                                   StandardModificationStatics.GetClassName(cn, table, isRevisionHistoryTable, isRevisionHistoryTable);
                    var revisionHistorySuffix = StandardModificationStatics.GetRevisionHistorySuffix(isRevisionHistoryTable);
                    writer.WriteLine("public " + modClass + " ToModification" + revisionHistorySuffix + "() {");
                    writer.WriteLine(
                        "return " + modClass + ".CreateForSingleRowUpdate" + revisionHistorySuffix + "( " +
                        StringTools.ConcatenateWithDelimiter(
                            ", ",
                            columns.AllColumnsExceptRowVersion.Select(i => StandardLibraryMethods.GetCSharpIdentifierSimple(i.PascalCasedNameExceptForOracle)).ToArray()) +
                        " );");
                    writer.WriteLine("}");
                });
                writeCacheClass(cn, writer, database, table, columns, isRevisionHistoryTable);

                var isSmallTable = configuration.SmallTables != null && configuration.SmallTables.Any(i => i.EqualsIgnoreCase(table));

                var tableUsesRowVersionedCaching = configuration.TablesUsingRowVersionedDataCaching != null &&
                                                   configuration.TablesUsingRowVersionedDataCaching.Any(i => i.EqualsIgnoreCase(table));
                if (tableUsesRowVersionedCaching && columns.RowVersionColumn == null && !(cn.DatabaseInfo is OracleInfo))
                {
                    throw new UserCorrectableException(
                              cn.DatabaseInfo is MySqlInfo
                                                        ? "Row-versioned data caching cannot currently be used with MySQL databases."
                                                        : "Row-versioned data caching can only be used with the {0} table if you add a rowversion column.".FormatWith(table));
                }

                if (isSmallTable)
                {
                    writeGetAllRowsMethod(writer, isRevisionHistoryTable, false);
                }
                writeGetRowsMethod(cn, writer, database, table, columns, isSmallTable, tableUsesRowVersionedCaching, isRevisionHistoryTable, false);
                if (isRevisionHistoryTable)
                {
                    if (isSmallTable)
                    {
                        writeGetAllRowsMethod(writer, true, true);
                    }
                    writeGetRowsMethod(cn, writer, database, table, columns, isSmallTable, tableUsesRowVersionedCaching, true, true);
                }

                if (columns.KeyColumns.Count() == 1 && columns.KeyColumns.Single().Name.ToLower().EndsWith("id"))
                {
                    writeGetRowMatchingIdMethod(cn, writer, database, table, columns, isSmallTable, tableUsesRowVersionedCaching, isRevisionHistoryTable);
                }

                if (isRevisionHistoryTable)
                {
                    DataAccessStatics.WriteGetLatestRevisionsConditionMethod(writer, columns.PrimaryKeyAndRevisionIdColumn.Name);
                }

                if (tableUsesRowVersionedCaching)
                {
                    var keyTupleTypeArguments = getPkAndVersionTupleTypeArguments(cn, columns);

                    writer.WriteLine("private static " + "Cache<System.Tuple<" + keyTupleTypeArguments + ">, BasicRow>" + " getRowsByPkAndVersion() {");
                    writer.WriteLine(
                        "return AppMemoryCache.GetCacheValue<{0}>( \"{1}\", () => new {0}( i => System.Tuple.Create( {2} ) ) ).RowsByPkAndVersion;".FormatWith(
                            "VersionedRowDataCache<System.Tuple<{0}>, System.Tuple<{1}>, BasicRow>".FormatWith(getPkTupleTypeArguments(columns), keyTupleTypeArguments),
                            database.SecondaryDatabaseName + table.TableNameToPascal(cn) + "TableRetrievalRowsByPkAndVersion",
                            StringTools.ConcatenateWithDelimiter(", ", Enumerable.Range(1, columns.KeyColumns.Count()).Select(i => "i.Item{0}".FormatWith(i)).ToArray())));
                    writer.WriteLine("}");
                }

                // Initially we did not generate this method for small tables, but we found a need for it when the cache is disabled since that will cause
                // GetRowMatchingId to repeatedly query.
                if (columns.KeyColumns.Count() == 1 && columns.KeyColumns.Single().Name.ToLower().EndsWith("id"))
                {
                    writeToIdDictionaryMethod(writer, columns);
                }

                writer.WriteLine("}");           // class
            }
            writer.WriteLine("}");               // namespace
        }