From a672685c9be7c78c5d556525bb1ba95adc407c07 Mon Sep 17 00:00:00 2001 From: Ivan Ivon Date: Mon, 8 Mar 2021 01:17:02 +0200 Subject: [PATCH] Add single class generation for the similar CSV files. --- README.md | 9 ++-- .../CodeGen/CsvCSharpCodeGenerator.cs | 52 +++++++++++++------ Src/CsvLINQPadDriver/ConnectionDialog.xaml | 19 +++---- .../CsvDataContextDriverProperties.cs | 6 +++ Src/CsvLINQPadDriver/CsvLINQPadDriver.csproj | 2 +- .../DataModel/CsvDataModelGenerator.cs | 50 ++++++++++++------ Src/CsvLINQPadDriver/DataModel/CsvTable.cs | 8 +++ Src/CsvLINQPadDriver/Helpers/FileUtils.cs | 4 +- .../ICsvDataContextDriverProperties.cs | 7 ++- Src/CsvLINQPadDriver/SchemaBuilder.cs | 37 ++++++++++--- .../CsvLINQPadDriverTest/SchemaBuilderTest.cs | 13 +++-- 11 files changed, 146 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 54c8806..f839823 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ CsvLINQPadDriver For LINQPad 6 CsvLINQPadDriver is LINQPad 6 data context dynamic driver for querying CSV files. -You can query data in CSV files with LINQ, just like it would be regular database. No need to write custom data model, mappings etc. - -Driver automatically generates new data types for every CSV file with corresponding properties and mappings for all columns. -Based on column and file names, possible relations between CSV tables are detected and generated. +- You can query data in CSV files with LINQ, just like it would be regular database. No need to write custom data model, mappings, etc. +- Driver automatically generates new data types for every CSV file with corresponding properties and mappings for all columns. +- Based on column and file names, possible relations between CSV tables are detected and generated. +- Single class generation allows to join similar files and query over them. Might not work well for files with relations. Website -- @@ -154,6 +154,7 @@ Known Issues -- - Some strange Unicode characters in column names may cause errors in generated data context source code. - Writing changed objects back to CSV is not directly supported, there is no `.SubmitChanges()` . But you can use LINQPad's `Util.WriteCsv`. +- Similar files single class generation might not work well for files with relations. Author -- diff --git a/Src/CsvLINQPadDriver/CodeGen/CsvCSharpCodeGenerator.cs b/Src/CsvLINQPadDriver/CodeGen/CsvCSharpCodeGenerator.cs index a89a210..df80827 100644 --- a/Src/CsvLINQPadDriver/CodeGen/CsvCSharpCodeGenerator.cs +++ b/Src/CsvLINQPadDriver/CodeGen/CsvCSharpCodeGenerator.cs @@ -1,5 +1,6 @@ using System; using System.CodeDom; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Security; @@ -29,11 +30,21 @@ private CsvCSharpCodeGenerator(string contextNameSpace, string contextTypeName, } // ReSharper disable once RedundantAssignment - public static string GenerateCode(CsvDatabase db, ref string nameSpace, ref string typeName, ICsvDataContextDriverProperties props) => + public static (string Code, IReadOnlyCollection> CodeGroups) + GenerateCode(CsvDatabase db, ref string nameSpace, ref string typeName, ICsvDataContextDriverProperties props) => new CsvCSharpCodeGenerator(nameSpace, typeName = DefaultContextTypeName, props).GenerateSrcFile(db); - private string GenerateSrcFile(CsvDatabase csvDatabase) => - $@"using System; + private (string, IReadOnlyCollection>) GenerateSrcFile(CsvDatabase csvDatabase) + { + var (_, csvTables) = csvDatabase; + + var groups = csvTables + .Select(table => GenerateTableRowDataTypeClass(table, _properties.HideRelationsFromDump)) + .GroupBy(typeCode => typeCode.Type) + .ToList(); + + return ($@" +using System; using System.Linq; using System.Collections.Generic; @@ -41,17 +52,17 @@ namespace {_contextNameSpace} {{ /// CSV Data Context public class {_contextTypeName} : {typeof(CsvDataContextBase).GetCodeTypeClassName()} - {{ {string.Join(string.Empty, csvDatabase.Tables.Select(table => $@" + {{ {string.Join(string.Empty, csvTables.Select(table => $@" /// File: {SecurityElement.Escape(table.FilePath)} public {typeof(CsvTableBase<>).GetCodeTypeClassName(table.GetCodeRowClassName())} {table.CodeName} {{ get; private set; }}") )} public {_contextTypeName}() {{ - //Init tables data {string.Join(string.Empty, csvDatabase.Tables.Select(table => $@" + //Init tables data {string.Join(string.Empty, csvTables.Select(table => $@" this.{table.CodeName} = {typeof(CsvTableFactory).GetCodeTypeClassName()}.CreateTable<{table.GetCodeRowClassName()}>( - {(_properties.IsStringInternEnabled ? "true" : "false")}, - {(_properties.IsCacheEnabled ? "true" : "false")}, + {GetBoolConst(_properties.IsStringInternEnabled)}, + {GetBoolConst(_properties.IsCacheEnabled)}, {table.CsvSeparator.AsValidCSharpCode()}, {table.FilePath.AsValidCSharpCode()}, new {typeof(CsvColumnInfoList<>).GetCodeTypeClassName(table.GetCodeRowClassName())}() {{ @@ -66,12 +77,16 @@ public class {_contextTypeName} : {typeof(CsvDataContextBase).GetCodeTypeClassNa }} }}//context class - //Data types {string.Join(string.Empty, csvDatabase.Tables.Select(table => GenerateTableRowDataTypeClass(table, _properties.HideRelationsFromDump)))} + //Data types {string.Join(string.Empty, groups.Select(grouping => grouping.First().Code))} }}//namespace -"; +", groups); - private static string GenerateTableRowDataTypeClass(CsvTable table, bool hideRelationsFromDump) => - $@" + static string GetBoolConst(bool val) => + val ? "true" : "false"; + } + + private static (string Type, string Code, string CodeName) GenerateTableRowDataTypeClass(CsvTable table, bool hideRelationsFromDump) => + (table.GetCodeRowClassName(), $@" public class {table.GetCodeRowClassName()} : {typeof(ICsvRowBase).GetCodeTypeClassName()} {{{string.Join(string.Empty, table.Columns.Select(c => $@" public string {c.CodeName} {{ get; set; }} ") @@ -80,16 +95,23 @@ public class {table.GetCodeRowClassName()} : {typeof(ICsvRowBase).GetCodeTypeCla [{typeof(HideFromDumpAttribute).GetCodeTypeClassName()}]" : string.Empty)} public IEnumerable<{csvRelation.TargetTable.GetCodeRowClassName()}> {csvRelation.CodeName} {{ get; set; }} ") )} - }} "; + }} ", table.CodeName!); } internal static class CsvCSharpCodeGeneratorExtensions { - public static string GetCodeRowClassName(this CsvTable table) => - $"T{table.CodeName}"; + public static string GetCodeRowClassName(this CsvTable table) + { + return ToClassName(table.ClassName); + + static string ToClassName(string? name) => + string.IsNullOrEmpty(name) + ? throw new ArgumentNullException(nameof(name), "Name is null or empty") + : $"T{name}"; + } public static string GetCodeTypeClassName(this Type type, params string[] genericParameters) => - type!.FullName!.Split('`')[0] + (genericParameters.Any() ? $"<{string.Join(",", genericParameters)}>" : string.Empty); + type.FullName!.Split('`').First() + (genericParameters.Any() ? $"<{string.Join(",", genericParameters)}>" : string.Empty); public static string AsValidCSharpCode(this T input) { diff --git a/Src/CsvLINQPadDriver/ConnectionDialog.xaml b/Src/CsvLINQPadDriver/ConnectionDialog.xaml index df265fb..6ea0c73 100644 --- a/Src/CsvLINQPadDriver/ConnectionDialog.xaml +++ b/Src/CsvLINQPadDriver/ConnectionDialog.xaml @@ -35,7 +35,7 @@ HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Text="{Binding Files, UpdateSourceTrigger=PropertyChanged}" - ToolTip="CSV files. Drag&drop (use Ctrl to add files) or type one file per line. Supports mask '*.csv' or recursive '**.csv'." + ToolTip="CSV files. Drag&drop (use Ctrl to add files) or type one file per line. Supports mask '*.csv' or recursive '**.csv'" AllowDrop="True" PreviewDragEnter="FilesTextBox_DragEnter" PreviewDragOver="FilesTextBox_DragEnter" @@ -48,23 +48,24 @@ - Ignore files with invalid format + Ignore files with invalid format - Cache CSV data in memory - String interning + Cache CSV data in memory + Intern CSV strings - Detect relations - Hide relations from .Dump() - Debug info + Generate single class for similar files + Detect relations + Hide relations from .Dump() + Debug info - Remember this connection + Remember this connection