web-dev-qa-db-fra.com

Désérialiser un fichier Avro avec C #

Je n'arrive pas à trouver un moyen de désérialiser un fichier Apache Avro avec C #. Le fichier Avro est un fichier généré par Fonction d'archivage dans Microsoft Azure Event Hubs.

Avec Java, je peux utiliser Avro Tools d’Apache pour convertir le fichier au format JSON:

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

Utilisation du package NuGet Microsoft.Hadoop.Avro ​​Je peux extraire SequenceNumber, Offset et EnqueuedTimeUtc, mais je ne sais pas quel type utiliser pour Body une exception est levée. J'ai essayé avec Dictionary<string, object> et d'autres types.

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...
}

Le schéma ressemble à ceci:

{
  "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" ]
    }
  ]
}    
10

J'ai pu obtenir un accès complet aux données en utilisant dynamic. Voici le code pour accéder aux données brutes body, stockées sous forme de tableau d'octets. Dans mon cas, ces octets contiennent du JSON codé en UTF8, mais cela dépend évidemment de la façon dont vous avez initialement créé vos instances EventData que vous avez publiées sur le Hub d'événements:

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

Si quelqu'un peut publier une solution à typage statique, je la relèverai, mais étant donné que la latence la plus importante sur tout système correspondra très certainement à la connexion aux blobs d'archivage Event Hub, je ne m'inquiéterais pas des performances d'analyse. :)

5
Lars Kemmann

This Gist montre comment désérialiser une capture de concentrateur d’événements avec C # en utilisant Microsoft.Hadoop.Avro2, qui présente l’avantage d’être à la fois compatible avec .NET Framework 4.5 et .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; }
 }
  • Références NuGet:

    • Microsoft.Hadoop.Avro2 (travaux 1.2.1)
    • WindowsAzure.Storage (travaux 8.3.0)
  • Espaces de noms:

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

J'ai finalement réussi à faire en sorte que cela fonctionne avec la bibliothèque/framework Apache C #.
Je suis resté bloqué pendant un certain temps car la fonctionnalité de capture d’Azure Event Hubs produisait parfois un fichier sans contenu de message. J'ai peut-être également eu un problème avec la façon dont les messages ont été sérialisés à l'origine dans l'objet EventData.
Le code ci-dessous s’applique à un fichier enregistré sur le disque depuis un conteneur de blob de capture.

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

Cela fonctionne également à l'aide de l'objet GenericRecord.

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

Cela a pris quelques efforts pour comprendre. Cependant, je conviens maintenant que cette fonctionnalité de capture Azure Event Hubs est une fonctionnalité très utile pour sauvegarder tous les événements. Je pense toujours qu'ils devraient rendre le format facultatif comme ils l'avaient fait avec la sortie du travail Stream Analytic, mais je vais peut-être m'habituer à Avro.

4
user2697956

Je suppose que vos types restants devraient être définis comme suit:

[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; }
}

Même si Body est une union de null et bytes, cela correspond à un nullablebyte[].

En C #, les tableaux sont toujours des types de référence, ils peuvent donc être null et le contrat rempli.

0
amcc