2017-08-22 23 views
2

私は最近、Excelファイルをインポートするソフトウェアを書くことを任されました。異なるルールで異なるファイルをExcelからインポートする

私が解決しようとしている問題は、私の会社にはc100クライアントがあり、ファイルごとに異なるレイアウトのファイルを提供しているということです。ファイルの列はクライアントによって異なりますが、 。

このプロセスは、特定の操作を別のファイルに行う必要があるため、複雑です。

たとえば、1つのファイルでは、特定の列の後に列を挿入し、計算結果をその列に配置する必要があります。その同じシートでは、アドレスは9列にわたって供給され、このアドレスは9列のうち最後の6行に移動し、最初の3列を除去する必要があります。

私がしたくないことは、各ファイル(前述のようにc100)の処理ロジックを記述することで、このコードを維持して新しい顧客ファイルを追加する責任を負うことになります私がやりたいことは、「Insert Column」、「Remove Column」、「Insert Calculation」、「Format a、b、c、...」のような基本ルールを持つことができる、 d、e & f Columns To d、e & f " - 新しいファイルの読み込みと処理の設定は、エンドユーザーがフロントエンドのソフトウェアを使って行うことができるようになっています(明らかに、何をすべきか)。

これに適したパターンや戦略はありますか?私はルールエンジンについて読んだことがありますが、これらの最良の例は "Age = 15"や "Surname = 'Smith'"のような単純なブール比較ですが、 "Column Column Gの後にInsert Column" "G - 125をコラムHに入れる"。

ここでの助けや良いアプローチの指針は、大変ありがたく思います。

+0

ファイル全体で繰り返すことのできるパターンを特定できない場合は、手動でフォーマットを行う方がよい場合があります。すべての顧客に均一なテンプレートを提供する方法はありますか? Excelからデータをインポートしようとしていますか?なぜC#タグがリストされているのか分かりません。スプレッドシートに列を挿入して式を変更するコードを書くのは簡単ですが、完全に動的な変更が必要な場合は、さらに難しくなり(無意味になります)たぶん私はあなたがしようとしていることを正確に理解していません。 –

+0

これはあなたが探している答えか、どんな制約があるのか​​分かりませんが、私たちが通常行っていることは、顧客が使用するための標準テンプレートを提供することによって入力を標準化することです。 – ainwood

答えて

1

ここでお手伝いできるかどうかを見てみましょう。

私が間違っていると私を修正しますが、入力ファイルと出力ファイルのすべてに列と列のみのデータが含まれているようです。

その場合、X入力列をY出力列に変換するという問題を想像してみてください。クライアントごとに、トランスフォームを指定する構成が必要です。設定は次のようになります

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

ご覧のとおり、設定行は単純にC#式です。 LINQ Expression classを使用して、変換式から式ツリーを構築することができます。 Expressions hereについて学ぶことができます。これらの式をコンパイルして、実際の変換を行うために使用することができます。 C#に関して考えると、リストを入力として受け取り、各クライアントの出力としてリストを返す静的変換メソッドを構築します。式を使用する場合は、構成ファイルを自分で解析する必要があります。

適切なC#構文をサポートできるRoslyn Compiler Servicesを使用することもできます。このように、文字通り変換を行うことができる静的メソッドを持つことができます。これにより、解析の任務が軽減されます。

どちらの場合でも、次のようなことに対処する必要があります:列が文字列であると期待する必要がありますか(必要な列を解析して必要な列を数値に解析する必要があります)数字のような数字を自動的に数字に変換します(これは特別な設定をする必要はありませんが、IDのような数字を持つ列を扱うときに問題になるかもしれませんが、不適切な取り扱いを避けるため文字列として扱わなければなりません)。 。要約すると

、私のアプローチは次のとおりです。

  • クライアントごとに設定ファイルを作成します。
  • 式をC#メソッドに動的に変換する
  • この設定を生成するためのGUIを提供します。特殊な構文(式)またはC#構文(Roslyn)を知らなくてもサポートを簡単に指定できます。 。 configを保存するときは、クライアントごとに1つのアセンブリ(またはクライアントごとに別々のアセンブリ)で1つのメソッドを生成し、永続化することができます。それをクライアントライブラリと呼ぶことにしましょう。
  • メインアプリケーションは、Excel、Validatingなどの標準的な読み込みをすべて実行してから、クライアントライブラリメソッドを呼び出して、メインアプリケーションでさらに処理できる標準形式の出力を生成することができます。

あなたは要点を持っていますか?

編集:デモンストレーションするコードを追加してください。コードは少し長めですが、理解のためにコメントしました。

// 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)); 
} 

あなたが気づくと、心配する唯一の作品はの自動生成コードにGUIで

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; 

これをコンパイルするには、いくつかのnugetライブラリを必要とし、それらに対応する使用条項ます私はここで提供していません。単純な変換が必要な場合は非常に簡単ですが、複雑な変換の場合はもっと複雑になります

-2

各Excelファイルのxml構成ファイルを維持することをお勧めします。 xml構成は、ツールで読み取るか、コンソールアプリケーションにするか、xml構成に基づいて新しいCSVファイルを生成する必要があります。

XML設定ファイルは任意のテキストエディタで簡単に編集できるため、ユーザーは同じ設定を更新できます。

0

あなたが書いているこの設定メカニズムを理解するのに十分な技術的知識をエンドユーザに期待しているようです。そのレベルの技術的詳細を処理できる場合は、インポートブックに必要なすべての列を含むExcelブックと公式Excelテンプレートを与える方が簡単で、データを手動で仕様にマッサージできます。

それ以外の場合は、既知のフォーマットの「データマッサージ」クラスのライブラリを構築し、新しいフォーマットが発生したときに新しいクラスを追加するという戦略設計ベースのパターンソリューションを提案します。例えば

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; 

    } 
} 
関連する問題