Kami memiliki antarmuka dan kelas dasar dengan beberapa tipe turunan.

public interface IEvent
{
    [JsonProperty("id")]
    public string Id { get; set; }

    string Type { get; }
}

public abstract class EventBase: IEvent
{
    public string Id { get; set; }
    public abstract string Type { get; }
}

public class UserCreated : EventBase
{
    public override string Type { get; } = typeof(UserCreated).AssemblyQualifiedName;
}

public class UserUpdated : EventBase
{
    public override string Type { get; } = typeof(UserUpdated).AssemblyQualifiedName;
}

Kami menyimpan peristiwa ini dari jenis turunan yang berbeda dalam wadah yang sama di Cosmos DB menggunakan v3 dari .Net SDK Microsoft.Azure.Cosmos. Kami kemudian ingin membaca semua acara dan membuatnya deserialized ke jenis yang benar.

public class CosmosDbTests
{
    [Fact]
    public async Task TestFetchingDerivedTypes()
    {
        var endpoint = "";
        var authKey = "";
        var databaseId ="";
        var containerId="";

        var client = new CosmosClient(endpoint, authKey);

        var container = client.GetContainer(databaseId, containerId);

        await container.CreateItemAsync(new UserCreated{ Id = Guid.NewGuid().ToString() });
        await container.CreateItemAsync(new UserUpdated{ Id = Guid.NewGuid().ToString() });

        var queryable = container.GetItemLinqQueryable<IEvent>();

        var query = queryable.ToFeedIterator();
        var list = new List<IEvent>();

        while (query.HasMoreResults)
        {
            list.AddRange(await query.ReadNextAsync());
        }

        Assert.NotEmpty(list);
    }
}

Tampaknya tidak ada opsi untuk memberi tahu GetItemLinqQueryable cara menangani tipe. Apakah ada metode atau pendekatan lain untuk mendukung beberapa tipe turunan dalam satu kueri?

Tidak apa-apa untuk menempatkan acara di semacam entitas pembungkus jika itu akan membantu, tetapi mereka tidak diizinkan untuk disimpan sebagai sengatan bersambung di dalam properti.

0
Lillvik 8 Mei 2021, 13:37

1 menjawab

Jawaban Terbaik

Komentar dari Stephen Jelas mengarahkan saya ke arah yang benar dan dengan bantuan blog ini https://thomaslevesque.com/2019/10/15/handling-type-hierarchies-in-cosmos-db-part-2/ Saya berakhir dengan solusi yang mirip dengan contoh berikut adalah kami memiliki CosmosSerializer khusus yang menggunakan JsonConverter khusus yang membaca properti Type.

public interface IEvent
{
    [JsonProperty("id")]
    public string Id { get; set; }

    [JsonProperty("$type")]
    string Type { get; }
}

public abstract class EventBase: IEvent
{
    public string Id { get; set; }
    public string Type => GetType().AssemblyQualifiedName;
}

public class UserCreated : EventBase
{
}

public class UserUpdated : EventBase
{
}

EventJsonConverter membaca properti Type.

public class EventJsonConverter : JsonConverter
{
    // This converter handles only deserialization, not serialization.
    public override bool CanRead => true;
    public override bool CanWrite => false;

    public override bool CanConvert(Type objectType)
    {
        // Only if the target type is the abstract base class
        return objectType == typeof(IEvent);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // First, just read the JSON as a JObject
        var obj = JObject.Load(reader);

        // Then look at the $type property:
        var typeName = obj["$type"]?.Value<string>();

        return typeName == null ? null : obj.ToObject(Type.GetType(typeName), serializer);

    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotSupportedException("This converter handles only deserialization, not serialization.");
    }
}

NewtonsoftJsonCosmosSerializer mengambil JsonSerializerSettings yang digunakan untuk serialisasi.

public class NewtonsoftJsonCosmosSerializer : CosmosSerializer
{
    private static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true);

    private readonly JsonSerializer _serializer;

    public NewtonsoftJsonCosmosSerializer(JsonSerializerSettings settings)
    {
        _serializer = JsonSerializer.Create(settings);
    }

    public override T FromStream<T>(Stream stream)
    {
        if (typeof(Stream).IsAssignableFrom(typeof(T)))
        {
            return (T)(object)stream;
        }

        using var sr = new StreamReader(stream);
        using var jsonTextReader = new JsonTextReader(sr);

        return _serializer.Deserialize<T>(jsonTextReader);
    }

    public override Stream ToStream<T>(T input)
    {
        var streamPayload = new MemoryStream();
        using var streamWriter = new StreamWriter(streamPayload, encoding: DefaultEncoding, bufferSize: 1024, leaveOpen: true);
        using JsonWriter writer = new JsonTextWriter(streamWriter);

        writer.Formatting = _serializer.Formatting;
        _serializer.Serialize(writer, input);
        writer.Flush();
        streamWriter.Flush();

        streamPayload.Position = 0;
        return streamPayload;
    }
}

CosmosClient sekarang dibuat dengan NewtonsoftJsonCosmosSerializer dan EventJsonConverter kita sendiri.

public class CosmosDbTests
{
    [Fact]
    public async Task TestFetchingDerivedTypes()
    {
        var endpoint = "";
        var authKey = "";
        var databaseId ="";
        var containerId="";

        var client = new CosmosClient(endpoint, authKey, new CosmosClientOptions
        {
            Serializer = new NewtonsoftJsonCosmosSerializer(new JsonSerializerSettings
            {
                Converters = { new EventJsonConverter() }
            })
        });

        var container = client.GetContainer(databaseId, containerId);

        await container.CreateItemAsync(new UserCreated{ Id = Guid.NewGuid().ToString() });
        await container.CreateItemAsync(new UserUpdated{ Id = Guid.NewGuid().ToString() });

        var queryable = container.GetItemLinqQueryable<IEvent>();

        var query = queryable.ToFeedIterator();
        var list = new List<IEvent>();

        while (query.HasMoreResults)
        {
            list.AddRange(await query.ReadNextAsync());
        }

        Assert.NotEmpty(list);
    }
}
0
Lillvik 9 Mei 2021, 07:08