用不同的规则从Excel导入不同的文件

最近我的任务是编写一个能导入Excel文件的软件。

我试图解决的问题是,我的公司有c100客户端,每个客户端提供一个不同的布局文件,在一个文件中的列将在不同的客户端不同,但相关的信息是在每个文件中。

这个过程很复杂,因为某些操作需要对不同的文件进行。

例如,在1个文件中,需要在特定列之后插入列,然后将计算结果放入该列中。 在同一张表中,地址是通过9列提供的,这个地址需要移动到9列的最后6列,然后去掉前3列。

我不想做的是为每个文件编写处理逻辑(如前所述,c 100),从而陷入必须维护这些代码的苦差事中,并且负责在进入时添加新的客户文件。

我想要做的是创build一个规则或处理引擎的种类,我可以有基本的规则,如“插入列”,“删除列”,“插入计算”,“格式a,b,c,d,e&f列使用d,e&f“ – 这是因为configuration读取和处理任何新文件可以通过一个前端软件由terminal用户完成(显然有一些培训要做什么)。

有没有一种模式或战略可能适合这个? 我已经阅读了有关规则引擎,但最好的例子是像“Age = 15”或“Surname ='Smith'”这样简单的布尔比较,但是找不到像“插入列G之后的列” “把G-125放到H列”。

任何帮助,或指向一个好的方法,将不胜感激。

让我看看我能不能在这里帮助你。

纠正我,如果我错了,但似乎所有的input和输出文件只包含列和列的数据。

在这种情况下,您应该将您的问题想象为Xinput列到Y输出列的转换。 对于每个客户端,您将需要一个将指定转换的configuration。 configuration可能如下所示

Y1 = X1 Y2 = X1 + X2 Y3 = X3 + " some string" 

正如你所看到的,你的configuration行就是C#expression式。 您可以使用LINQ Expression类从您的转换公式构buildexpression式树。 你可以在这里了解expression式 。 这些expression式可以被编译并用来做实际的转换。 如果你用C#来思考,你将build立一个静态转换方法,它将一个列表作为input,并为每个客户返回一个列表作为输出。 当你使用expression式时,你将不得不自己parsingconfiguration文件。

您也可以使用Roslyn编译器服务 ,它可以支持正确的C#语法。 这样,你可以从字面上有一个可以做转换的静态方法。 这也解除了你的parsing责任。

在这两种情况下,你仍然需要处理的事情,如:我应该期望列是一个string(这意味着您的支持需要明确指示configurationGUI分析所需的列到数字),或者我应该自动转换数字(现在的支持不需要做额外的configuration,但是在处理带有数字的列(如ID)时可能会遇到问题,但应该将其视为string以避免不当处理)等。

总之,我的做法是:

  • 为每个客户创buildconfiguration文件。
  • 使用Expressions或Roslyndynamic地将configuration文件转换为C#方法
  • 提供一个用于生成此configuration的GUI – 这种方式支持人员可以在不知道特殊语法(Expressions)或C#语法(Roslyn)的情况下轻松指定转换。 保存configuration时,可以在一个程序集(或每个客户端的单独程序集)中为每个客户端生成一个方法,并将其保留。 我们称之为客户端库。
  • 您的主应用程序可以执行所有从Excel读取,validation等标准内容,然后调用客户端库方法以标准格式生成输出,这可以在主应用程序中进一步处理。

希望你有主意。

编辑:添加一些代码来演示。 代码有点冗长,但是为了理解而评论。

 // this data represents your excel data var data = new string[][] { new string [] { "col_1_1", "10", "09:30" }, new string [] { "col_2_1", "12", "09:40" } }; // you should read this from your client specific config file/section // Remember: you should provide a GUI tool to build this config var config = @" output.Add(input[0]); int hours = int.Parse(input[1]); DateTime date = DateTime.Parse(input[2]); date = date.AddHours(hours); output.Add(""Custom Text: "" + date); "; // this template code should be picked up from a // non client specific config file/section var code = @" using System; using System.Collections.Generic; using System.Linq; namespace ClientLibrary { static class ClientLibrary { public static List<string> Client1(string[] input) { var output = new List<string>(); <<code-from-config>> return output; } } } "; // Inject client configuration into template to form full code code = code.Replace(@"<<code-from-config>>", config); // Compile your dynamic method and get a reference to it var references = new MetadataReference[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location) }; CSharpCompilation compilation = CSharpCompilation.Create( null, syntaxTrees: new[] { CSharpSyntaxTree.ParseText(code) }, references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); MethodInfo clientMethod = null; using (var ms = new MemoryStream()) { EmitResult result = compilation.Emit(ms); if (!result.Success) { foreach (Diagnostic diagnostic in result.Diagnostics) { Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage()); } } else { ms.Seek(0, SeekOrigin.Begin); Assembly assembly = Assembly.Load(ms.ToArray()); clientMethod = assembly.GetType("ClientLibrary.ClientLibrary").GetMethod("Client1"); } } if (clientMethod == null) return; // Do transformation foreach (string[] row in data) { var output = clientMethod.Invoke(null, new object[] { row }) as List<string>; Console.WriteLine(string.Join("|", output)); } 

你将需要一些nuget库来编译它,以及它们相应的使用子句

 nuget install Microsoft.Net.Compilers # Install C# and VB compilers nuget install Microsoft.CodeAnalysis # Install Language APIs and Services using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; 

正如你注意到的,唯一需要担心的是GUI自动生成转换的代码 – 我没有在这里提供。 如果你想要简单的转换,这应该是非常容易的,但是对于一个复杂的转换,它会涉及更多

这听起来像是你期望你的最终用户足够精通技术,来理解你将要写的configuration机制。 如果他们能够处理这样的技术细节,给他们一个Excel书和一个官方的excel模板可能会更简单,该模板包含你的导入应用程序需要的所有列,并且他们可以手动将数据按摩到规范。

否则,我会build议一些基于策略devise的模式解决scheme,以build立一个已知格式的“数据按摩器”类库,并且在遇到新格式时添加新的类。 例如

 public interface IClientDataImporter { List<MyCustomRowStructure> Import(string filename); } // client 1 importer public class ClientOneImporter : IClientDataImporter { public List<MyCustomRowStructure> Import(string filename) { var result = new List<MyCustomRowStructure>(); // ..... insert custom logic here return result; } } // client 2 importer public class ClientTwoImporter : IClientDataImporter { public List<MyCustomRowStructure> Import(string filename) { var result = new List<MyCustomRowStructure>(); // ..... insert custom logic here return result; } } // repeat up to however many formats you need // then..... public class ExcelToDatabaseImporter { public void ImportExcelFile(string filename, string clientName) { var myValidData = GetClientDataImporter(clientName).Import(filename); StickMyDataToMyDatabase(myValidData); // this is where you would load the structure into the db... won't need to touch every time a new format is encountered } public IClientDataImporter GetClientDataImporter(string clientName) { switch (clientName): case "ClientOne": return new ClientOneImporter(); break; case "ClientTwo": return new ClientTwoImporter(); break; default: throw new ArgumentException("No importer for client"); break; } } 

我build议你维护每个Excel文件的XMLconfiguration文件。 xmlconfiguration必须由工具读取,可以是控制台应用程序,并根据xmlconfiguration生成新的CSV文件。

由于XMLconfiguration文件可以通过任何文本编辑器轻松编辑,用户可以更新。