web-dev-qa-db-ja.com

C#でAvroファイルを逆シリアル化する

C#でApache Avroファイルを逆シリアル化する方法が見つかりません。 Avroファイルは、Microsoft Azure Event Hubsの アーカイブ機能 によって生成されたファイルです。

Java Apacheから Avro Tools を使用してファイルをJSONに変換できます:

Java -jar avro-tools-1.8.1.jar tojson --pretty inputfile > output.json

NuGetパッケージの使用Microsoft.Hadoop.AvroSequenceNumberOffsetEnqueuedTimeUtcを抽出できますが、Bodyに使用する型がわからないため例外がスローされます。私はDictionary<string, object>やその他のタイプを試しました。

static void Main(string[] args)
{
    var fileName = "...";

    using (Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
    {
        using (var reader = AvroContainer.CreateReader<EventData>(stream))
        {
            using (var streamReader = new SequentialReader<EventData>(reader))
            {
                var record = streamReader.Objects.FirstOrDefault();
            }
        }
    }
}

[DataContract(Namespace = "Microsoft.ServiceBus.Messaging")]
public class EventData
{
    [DataMember(Name = "SequenceNumber")]
    public long SequenceNumber { get; set; }

    [DataMember(Name = "Offset")]
    public string Offset { get; set; }

    [DataMember(Name = "EnqueuedTimeUtc")]
    public string EnqueuedTimeUtc { get; set; }

    [DataMember(Name = "Body")]
    public foo Body { get; set; }

    // More properties...
}

スキーマは次のようになります。

{
  "type": "record",
  "name": "EventData",
  "namespace": "Microsoft.ServiceBus.Messaging",
  "fields": [
    {
      "name": "SequenceNumber",
      "type": "long"
    },
    {
      "name": "Offset",
      "type": "string"
    },
    {
      "name": "EnqueuedTimeUtc",
      "type": "string"
    },
    {
      "name": "SystemProperties",
      "type": {
        "type": "map",
        "values": [ "long", "double", "string", "bytes" ]
      }
    },
    {
      "name": "Properties",
      "type": {
        "type": "map",
        "values": [ "long", "double", "string", "bytes" ]
      }
    },
    {
      "name": "Body",
      "type": [ "null", "bytes" ]
    }
  ]
}    
13

dynamicを使用して、完全なデータアクセスを機能させることができました。これは、バイトの配列として格納されている生のbodyデータにアクセスするためのコードです。私の場合、これらのバイトにはUTF8でエンコードされたJSONが含まれていますが、もちろん、イベントハブに公開したEventDataインスタンスを最初に作成した方法によって異なります。

using (var reader = AvroContainer.CreateGenericReader(stream))
{
    while (reader.MoveNext())
    {
        foreach (dynamic record in reader.Current.Objects)
        {
            var sequenceNumber = record.SequenceNumber;
            var bodyText = Encoding.UTF8.GetString(record.Body);
            Console.WriteLine($"{sequenceNumber}: {bodyText}");
        }
    }
}

誰かが静的に型付けされたソリューションを投稿できる場合、私はそれを賛成しますが、どのシステムでも大きな待機時間がEvent Hub Archive BLOBへの接続になることを考えると、パフォーマンスの解析について心配する必要はありません。 :)

6
Lars Kemmann

この Gist は、Microsoft.Hadoop.Avro2を使用してC#でイベントハブキャプチャを逆シリアル化する方法を示しています。これには、.NET Framework 4.5と.NET Standard 1.6の両方に準拠するという利点があります。

 var connectionString = "<Azure event hub capture storage account connection string>";
 var containerName = "<Azure event hub capture container name>";
 var blobName = "<Azure event hub capture BLOB name (ends in .avro)>";

 var storageAccount = CloudStorageAccount.Parse(connectionString);
 var blobClient = storageAccount.CreateCloudBlobClient();
 var container = blobClient.GetContainerReference(containerName);
 var blob = container.GetBlockBlobReference(blobName);
 using (var stream = blob.OpenRead())
 using (var reader = AvroContainer.CreateGenericReader(stream))
     while (reader.MoveNext())
         foreach (dynamic result in reader.Current.Objects)
         {
             var record = new AvroEventData(result);
             record.Dump();
         }

 public struct AvroEventData
 {
     public AvroEventData(dynamic record)
     {
         SequenceNumber = (long) record.SequenceNumber;
         Offset = (string) record.Offset;
         DateTime.TryParse((string) record.EnqueuedTimeUtc, out var enqueuedTimeUtc);
         EnqueuedTimeUtc = enqueuedTimeUtc;
         SystemProperties = (Dictionary<string, object>) record.SystemProperties;
         Properties = (Dictionary<string, object>) record.Properties;
         Body = (byte[]) record.Body;
     }
     public long SequenceNumber { get; set; }
     public string Offset { get; set; }
     public DateTime EnqueuedTimeUtc { get; set; }
     public Dictionary<string, object> SystemProperties { get; set; }
     public Dictionary<string, object> Properties { get; set; }
     public byte[] Body { get; set; }
 }
  • NuGetの参照:

    • Microsoft.Hadoop.Avro2(1.2.1の機能)
    • WindowsAzure.Storage(8.3.0は動作します)
  • 名前空間:

    • Microsoft.Hadoop.Avro.Container
    • Microsoft.WindowsAzure.Storage
6
pshros

これでようやくこれをApache C#ライブラリ/フレームワークで動作させることができました。
Azure Event Hubsのキャプチャ機能がメッセージコンテンツのないファイルを出力することがあるので、しばらくの間行き詰まっていました。メッセージがEventDataオブジェクトに最初にシリアル化された方法にも問題があった可能性があります。
以下のコードは、キャプチャーBLOBコンテナーからディスクに保存されたファイル用です。

var dataFileReader = DataFileReader<EventData>.OpenReader(file);
foreach (var record in dataFileReader.NextEntries)
{
   // Do work on EventData object
}

これは、GenericRecordオブジェクトを使用しても機能します。

var dataFileReader = DataFileReader<GenericRecord>.OpenReader(file);

これは理解するのにいくらかの努力を要しました。ただし、このAzure Event Hubs Capture機能がすべてのイベントをバックアップする優れた機能であることに今、同意します。 Stream Analyticジョブ出力で行ったように、形式をオプションにする必要があると私はまだ感じていますが、多分Avroに慣れるでしょう。

4
user2697956

NullableSchema属性を使用して、本文をバイトの結合およびnullとしてマークすることもできます。これにより、厳密に型指定されたインターフェイスを使用できるようになります。

[DataContract(Namespace = "Microsoft.ServiceBus.Messaging")]
public class EventData
{
    [DataMember(Name = "SequenceNumber")]
    public long SequenceNumber { get; set; }

    [DataMember(Name = "Offset")]
    public string Offset { get; set; }

    [DataMember(Name = "EnqueuedTimeUtc")]
    public string EnqueuedTimeUtc { get; set; }

    [DataMember(Name = "Body")]
    [NullableSchema]
    public foo Body { get; set; }
}
0
StanislawSwierc

残りのタイプは、次のように定義する必要があると思います。

[DataContract(Namespace = "Microsoft.ServiceBus.Messaging")]
[KnownType(typeof(Dictionary<string, object>))]
public class EventData
{
    [DataMember]
    public IDictionary<string, object> SystemProperties { get; set; }

    [DataMember]
    public IDictionary<string, object> Properties { get; set; }

    [DataMember]
    public byte[] Body { get; set; }
}

Bodynullbytesの和集合ですが、これはnullablebyte[]にマップされます。

C#では、配列は常に参照型であるため、nullにすることができ、契約が満たされます。

0
amcc

https://github.com/AdrianStrugala/AvroConvert を使用することをお勧めします

そして単に:

byte[] avroFileContent = File.ReadAllBytes(fileName);
var result = AvroConvert.Deserialize<EventData>(avroFileContent);

ライブラリ自体は、Avro形式を使用して開発フローを改善することを目的としています。モデルのスキーマや属性も必要ありません。 (私はこのライブラリの寄稿者です)

0
Adrian