Skip to content

Commit 4c66da0

Browse files
authored
Add TypeBinder prototype (Azure#20494)
1 parent 7915d24 commit 4c66da0

30 files changed

+3333
-120
lines changed

sdk/monitor/Azure.Monitory.Query/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ You can map query results to a model using the `LogsClient.QueryAsync<T>` method
3030
public class MyLogEntryModel
3131
{
3232
public string ResourceGroup { get; set; }
33-
public string Count { get; set; }
33+
public int Count { get; set; }
3434
}
3535
```
3636

sdk/monitor/Azure.Monitory.Query/api/Azure.Monitory.Query.netstandard2.0.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,26 @@ internal LogsQueryResultRow() { }
7575
public object this[string name] { get { throw null; } }
7676
public bool GetBoolean(int index) { throw null; }
7777
public bool GetBoolean(string name) { throw null; }
78+
public System.DateTimeOffset GetDateTimeOffset(int index) { throw null; }
79+
public System.DateTimeOffset GetDateTimeOffset(string name) { throw null; }
7880
public decimal GetDecimal(int index) { throw null; }
7981
public decimal GetDecimal(string name) { throw null; }
82+
public double GetDouble(int index) { throw null; }
83+
public double GetDouble(string name) { throw null; }
84+
public System.Guid GetGuid(int index) { throw null; }
85+
public System.Guid GetGuid(string name) { throw null; }
8086
public int GetInt32(int index) { throw null; }
8187
public int GetInt32(string name) { throw null; }
8288
public long GetInt64(int index) { throw null; }
8389
public long GetInt64(string name) { throw null; }
8490
public object GetObject(int index) { throw null; }
8591
public object GetObject(string name) { throw null; }
86-
public float GetSingle(int index) { throw null; }
87-
public float GetSingle(string name) { throw null; }
8892
public string GetString(int index) { throw null; }
8993
public string GetString(string name) { throw null; }
94+
public System.TimeSpan GetTimeSpan(int index) { throw null; }
95+
public System.TimeSpan GetTimeSpan(string name) { throw null; }
96+
public bool IsNull(int index) { throw null; }
97+
public bool IsNull(string name) { throw null; }
9098
}
9199
public partial class LogsQueryResultTable
92100
{

sdk/monitor/Azure.Monitory.Query/src/LogsClient.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class LogsClient
1616
private readonly QueryRestClient _queryClient;
1717
private readonly ClientDiagnostics _clientDiagnostics;
1818
private readonly HttpPipeline _pipeline;
19+
private readonly RowBinder _rowBinder;
1920

2021
public LogsClient(TokenCredential credential) : this(credential, null)
2122
{
@@ -30,6 +31,7 @@ public LogsClient(TokenCredential credential, LogsClientOptions options)
3031
_clientDiagnostics = new ClientDiagnostics(options);
3132
_pipeline = HttpPipelineBuilder.Build(options, new BearerTokenAuthenticationPolicy(credential, "https://api.loganalytics.io//.default"));
3233
_queryClient = new QueryRestClient(_clientDiagnostics, _pipeline);
34+
_rowBinder = new RowBinder();
3335
}
3436

3537
protected LogsClient()
@@ -40,14 +42,14 @@ public virtual Response<IReadOnlyList<T>> Query<T>(string workspaceId, string qu
4042
{
4143
Response<LogsQueryResult> response = Query(workspaceId, query, timeSpan, cancellationToken);
4244

43-
return Response.FromValue(RowBinder.BindResults<T>(response), response.GetRawResponse());
45+
return Response.FromValue(_rowBinder.BindResults<T>(response), response.GetRawResponse());
4446
}
4547

4648
public virtual async Task<Response<IReadOnlyList<T>>> QueryAsync<T>(string workspaceId, string query, TimeSpan? timeSpan = null, CancellationToken cancellationToken = default)
4749
{
4850
Response<LogsQueryResult> response = await QueryAsync(workspaceId, query, timeSpan, cancellationToken).ConfigureAwait(false);
4951

50-
return Response.FromValue(RowBinder.BindResults<T>(response), response.GetRawResponse());
52+
return Response.FromValue(_rowBinder.BindResults<T>(response), response.GetRawResponse());
5153
}
5254

5355
public virtual Response<LogsQueryResult> Query(string workspaceId, string query, TimeSpan? timeSpan = null, CancellationToken cancellationToken = default)
@@ -82,7 +84,7 @@ public virtual async Task<Response<LogsQueryResult>> QueryAsync(string workspace
8284

8385
public virtual LogsBatchQuery CreateBatchQuery()
8486
{
85-
return new LogsBatchQuery(_clientDiagnostics, _queryClient);
87+
return new LogsBatchQuery(_clientDiagnostics, _queryClient, _rowBinder);
8688
}
8789

8890
internal static QueryBody CreateQueryBody(string query, TimeSpan? timeSpan)

sdk/monitor/Azure.Monitory.Query/src/Models/LogsBatchQuery.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ public class LogsBatchQuery
1414
{
1515
private readonly ClientDiagnostics _clientDiagnostics;
1616
private readonly QueryRestClient _restClient;
17+
private readonly RowBinder _rowBinder;
1718
private readonly BatchRequest _batch;
1819
private int _counter;
1920

20-
internal LogsBatchQuery(ClientDiagnostics clientDiagnostics, QueryRestClient restClient)
21+
internal LogsBatchQuery(ClientDiagnostics clientDiagnostics, QueryRestClient restClient, RowBinder rowBinder)
2122
{
2223
_clientDiagnostics = clientDiagnostics;
2324
_restClient = restClient;
25+
_rowBinder = rowBinder;
2426
_batch = new BatchRequest();
2527
}
2628

@@ -47,7 +49,9 @@ public virtual Response<LogsBatchQueryResult> Submit(CancellationToken cancellat
4749
scope.Start();
4850
try
4951
{
50-
return _restClient.Batch(_batch, cancellationToken);
52+
var response = _restClient.Batch(_batch, cancellationToken);
53+
response.Value.RowBinder = _rowBinder;
54+
return response;
5155
}
5256
catch (Exception e)
5357
{
@@ -62,7 +66,9 @@ public virtual async Task<Response<LogsBatchQueryResult>> SubmitAsync(Cancellati
6266
scope.Start();
6367
try
6468
{
65-
return await _restClient.BatchAsync(_batch, cancellationToken).ConfigureAwait(false);
69+
var response = await _restClient.BatchAsync(_batch, cancellationToken).ConfigureAwait(false);
70+
response.Value.RowBinder = _rowBinder;
71+
return response;
6672
}
6773
catch (Exception e)
6874
{

sdk/monitor/Azure.Monitory.Query/src/Models/LogsBatchQueryResult.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public partial class LogsBatchQueryResult
1212
{
1313
internal IReadOnlyList<LogQueryResponse> Responses { get; }
1414
internal BatchResponseError Error { get; }
15+
internal RowBinder RowBinder { get; set; }
1516

1617
public LogsQueryResult GetResult(string queryId)
1718
{

sdk/monitor/Azure.Monitory.Query/src/Models/LogsQueryResultRow.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Globalization;
67
using System.Text.Json;
8+
using Azure.Core;
79

810
namespace Azure.Monitory.Query.Models
911
{
@@ -23,16 +25,25 @@ internal LogsQueryResultRow(Dictionary<string, int> columns, JsonElement row)
2325
public int GetInt32(int index) => _row[index].GetInt32();
2426
public long GetInt64(int index) => _row[index].GetInt64();
2527
public bool GetBoolean(int index) => _row[index].GetBoolean();
26-
public decimal GetDecimal(int index) => _row[index].GetDecimal();
27-
public float GetSingle(int index) => _row[index].GetSingle();
28+
public decimal GetDecimal(int index) => decimal.Parse(_row[index].GetString(), CultureInfo.InvariantCulture);
29+
public double GetDouble(int index) => _row[index].GetDouble();
2830
public string GetString(int index) => _row[index].GetString();
31+
public DateTimeOffset GetDateTimeOffset(int index) => _row[index].GetDateTimeOffset();
32+
public TimeSpan GetTimeSpan(int index) => _row[index].GetTimeSpan("c");
33+
public Guid GetGuid(int index) => _row[index].GetGuid();
34+
35+
public bool IsNull(int index) => _row[index].ValueKind == JsonValueKind.Null;
2936

3037
public int GetInt32(string name) => GetInt32(_columns[name]);
3138
public long GetInt64(string name) => GetInt64(_columns[name]);
3239
public bool GetBoolean(string name) => GetBoolean(_columns[name]);
3340
public decimal GetDecimal(string name) => GetDecimal(_columns[name]);
34-
public float GetSingle(string name) => GetSingle(_columns[name]);
41+
public double GetDouble(string name) => GetDouble(_columns[name]);
3542
public string GetString(string name) => GetString(_columns[name]);
43+
public DateTimeOffset GetDateTimeOffset(string name) => GetDateTimeOffset(_columns[name]);
44+
public TimeSpan GetTimeSpan(string name) => GetTimeSpan(_columns[name]);
45+
public Guid GetGuid(string name) => GetGuid(_columns[name]);
46+
public bool IsNull(string name) => IsNull(_columns[name]);
3647

3748
public object GetObject(int index)
3849
{
@@ -69,5 +80,7 @@ public object GetObject(int index)
6980

7081
public object this[int index] => GetObject(index);
7182
public object this[string name] => GetObject(name);
83+
84+
internal bool TryGetColumn(string name, out int column) => _columns.TryGetValue(name, out column);
7285
}
7386
}

sdk/monitor/Azure.Monitory.Query/src/RowBinder.cs

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,14 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Globalization;
7-
using System.Linq;
8-
using System.Reflection;
96
using Azure.Monitory.Query.Models;
107

118
namespace Azure.Monitory.Query
129
{
13-
internal class RowBinder
10+
internal class RowBinder: TypeBinder<LogsQueryResultRow>
1411
{
15-
internal static IReadOnlyList<T> BindResults<T>(LogsQueryResult response)
12+
internal IReadOnlyList<T> BindResults<T>(LogsQueryResult response)
1613
{
17-
// TODO: this is very slow
1814
List<T> results = new List<T>();
1915
if (typeof(IDictionary<string, object>).IsAssignableFrom(typeof(T)))
2016
{
@@ -34,42 +30,75 @@ internal static IReadOnlyList<T> BindResults<T>(LogsQueryResult response)
3430
}
3531
}
3632
}
37-
else if (typeof(T).IsValueType || typeof(T) == typeof(string))
33+
else
3834
{
3935
foreach (var table in response.Tables)
4036
{
4137
foreach (var row in table.Rows)
4238
{
43-
// TODO: Validate
44-
results.Add((T)Convert.ChangeType(row.GetObject(0), typeof(T), CultureInfo.InvariantCulture));
39+
results.Add(Deserialize<T>(row));
4540
}
4641
}
4742
}
48-
else
43+
44+
return results;
45+
}
46+
47+
protected override void Set<T>(LogsQueryResultRow destination, T value, BoundMemberInfo memberInfo)
48+
{
49+
throw new NotSupportedException();
50+
}
51+
52+
protected override bool TryGet<T>(BoundMemberInfo memberInfo, LogsQueryResultRow source, out T value)
53+
{
54+
int column;
55+
56+
// Binding entire row to a primitive
57+
if (memberInfo == null)
4958
{
50-
foreach (var table in response.Tables)
51-
{
52-
var columnMap = table.Columns
53-
.Select((column, index) => (Property: typeof(T).GetProperty(column.Name, BindingFlags.Instance | BindingFlags.Public), index))
54-
.Where(columnMapping => columnMapping.Property?.SetMethod != null)
55-
.ToArray();
59+
column = 0;
60+
}
61+
else if (!source.TryGetColumn(memberInfo.Name, out column))
62+
{
63+
value = default;
64+
return false;
65+
}
5666

57-
foreach (var row in table.Rows)
58-
{
59-
T rowObject = Activator.CreateInstance<T>();
67+
if (source.IsNull(column) &&
68+
(!typeof(T).IsValueType ||
69+
typeof(T).IsGenericType &&
70+
typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>)))
71+
{
72+
value = default;
73+
return true;
74+
}
6075

61-
foreach (var (property, index) in columnMap)
62-
{
63-
property.SetValue(rowObject, Convert.ChangeType(row.GetObject(index), property.PropertyType, CultureInfo.InvariantCulture));
64-
}
76+
if (typeof(T) == typeof(int)) value = (T)(object)source.GetInt32(column);
77+
else if (typeof(T) == typeof(string)) value = (T)(object)source.GetString(column);
78+
else if (typeof(T) == typeof(bool)) value = (T)(object)source.GetBoolean(column);
79+
else if (typeof(T) == typeof(long)) value = (T)(object)source.GetInt64(column);
80+
else if (typeof(T) == typeof(decimal)) value = (T)(object)source.GetDecimal(column);
81+
else if (typeof(T) == typeof(double)) value = (T)(object)source.GetDouble(column);
82+
else if (typeof(T) == typeof(object)) value = (T)source.GetObject(column);
83+
else if (typeof(T) == typeof(Guid)) value = (T)(object)source.GetGuid(column);
84+
else if (typeof(T) == typeof(DateTimeOffset)) value = (T)(object)source.GetDateTimeOffset(column);
85+
else if (typeof(T) == typeof(TimeSpan)) value = (T)(object)source.GetTimeSpan(column);
6586

66-
results.Add(rowObject);
67-
}
68-
}
87+
else if (typeof(T) == typeof(int?)) value = (T)(object)(int?)source.GetInt32(column);
88+
else if (typeof(T) == typeof(bool?)) value = (T)(object)(bool?)source.GetBoolean(column);
89+
else if (typeof(T) == typeof(long?)) value = (T)(object)(long?)source.GetInt64(column);
90+
else if (typeof(T) == typeof(decimal?)) value = (T)(object)(decimal?)source.GetDecimal(column);
91+
else if (typeof(T) == typeof(double?)) value = (T)(object)(double?)source.GetDouble(column);
92+
else if (typeof(T) == typeof(Guid?)) value = (T)(object)(Guid?)source.GetGuid(column);
93+
else if (typeof(T) == typeof(DateTimeOffset?)) value = (T)(object)(DateTimeOffset?)source.GetDateTimeOffset(column);
94+
else if (typeof(T) == typeof(TimeSpan?)) value = (T)(object)(TimeSpan?)source.GetTimeSpan(column);
95+
96+
else
97+
{
98+
throw new NotSupportedException($"The {typeof(T)} type is not supported as a deserialization target.");
6999
}
70100

71-
// TODO: Maybe support record construction
72-
return results;
101+
return true;
73102
}
74103
}
75104
}

0 commit comments

Comments
 (0)