fix: resolve JSON and SQLite trimming warnings (#10513)

Use generated JSON type metadata and propagate mapped SQLite types through CRUD and transactional bulk operations. Cover serialization options, affected counts, rollback and native test compatibility without changing UI models or persistence entities.
This commit is contained in:
Miheichev Aleksandr Sergeevich
2026-11-04 09:56:43 +00:00
committed by GitHub
parent aa00b2c614
commit 9a029a3f7e
7 changed files with 421 additions and 28 deletions
+7 -5
View File
@@ -1,3 +1,5 @@
using System.Text.Json.Serialization.Metadata;
namespace ServiceLib.Common;
public class JsonUtils
@@ -77,7 +79,7 @@ public class JsonUtils
{
return default;
}
return JsonSerializer.Deserialize<T>(strJson, _defaultDeserializeOptions);
return JsonSerializer.Deserialize(strJson, (JsonTypeInfo<T>)_defaultDeserializeOptions.GetTypeInfo(typeof(T)));
}
catch
{
@@ -130,7 +132,7 @@ public class JsonUtils
(false, true) => _defaultSerializeOptions,
_ => _defaultSerializeNoIndentedOptions
};
result = JsonSerializer.Serialize(obj, options);
result = JsonSerializer.Serialize(obj, (JsonTypeInfo<T>)options.GetTypeInfo(typeof(T)));
}
catch (Exception ex)
{
@@ -161,7 +163,7 @@ public class JsonUtils
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
TypeInfoResolver = AppJsonContext.Default,
};
result = JsonSerializer.Serialize(obj, options);
result = JsonSerializer.Serialize(obj, (JsonTypeInfo<T>)options.GetTypeInfo(typeof(T)));
}
catch (Exception ex)
{
@@ -179,7 +181,7 @@ public class JsonUtils
public static JsonNode? SerializeToNode<T>(T? obj, bool nullValue = false)
{
var options = nullValue ? _nullValueSerializeOptions : _defaultSerializeOptions;
return JsonSerializer.SerializeToNode(obj, options);
return JsonSerializer.SerializeToNode(obj, (JsonTypeInfo<T>)options.GetTypeInfo(typeof(T)));
}
public static JsonArray? SerializeToArray(IEnumerable<string>? values)
@@ -191,7 +193,7 @@ public class JsonUtils
var array = new JsonArray();
foreach (var value in values)
{
var valueNode = JsonSerializer.SerializeToNode(value, _defaultSerializeOptions);
var valueNode = JsonSerializer.SerializeToNode(value, (JsonTypeInfo<string>)_defaultSerializeOptions.GetTypeInfo(typeof(string)));
array.Add(valueNode);
}
return array;
+45 -19
View File
@@ -1,4 +1,4 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
namespace ServiceLib.Helper;
@@ -18,47 +18,73 @@ public sealed class SQLiteHelper
_dbAsync = new SQLiteAsyncConnection(_connstr, false);
}
public CreateTableResult CreateTable<T>()
public CreateTableResult CreateTable<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>()
{
return _db.CreateTable<T>();
}
public async Task<int> InsertAllAsync(IEnumerable models)
public async Task<int> InsertAllAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(IEnumerable<T> models)
{
return await _dbAsync.InsertAllAsync(models, runInTransaction: true).ConfigureAwait(false);
var count = 0;
await _dbAsync.RunInTransactionAsync(connection =>
{
foreach (var model in models)
{
count += connection.Insert(model, typeof(T));
}
}).ConfigureAwait(false);
return count;
}
public async Task<int> ReplaceAllAsync(IEnumerable models)
public async Task<int> ReplaceAllAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(IEnumerable<T> models)
{
return await _dbAsync.InsertAllAsync(models, extra: "OR REPLACE", runInTransaction: true).ConfigureAwait(false);
var count = 0;
await _dbAsync.RunInTransactionAsync(connection =>
{
foreach (var model in models)
{
count += connection.InsertOrReplace(model, typeof(T));
}
}).ConfigureAwait(false);
return count;
}
public async Task<int> InsertAsync(object model)
public async Task<int> InsertAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(T model)
{
return await _dbAsync.InsertAsync(model);
return await _dbAsync.InsertAsync(model, typeof(T));
}
public async Task<int> ReplaceAsync(object model)
public async Task<int> ReplaceAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(T model)
{
return await _dbAsync.InsertOrReplaceAsync(model);
return await _dbAsync.InsertOrReplaceAsync(model, typeof(T));
}
public async Task<int> UpdateAsync(object model)
public async Task<int> UpdateAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(T model)
{
return await _dbAsync.UpdateAsync(model);
return await _dbAsync.UpdateAsync(model, typeof(T));
}
public async Task<int> UpdateAllAsync(IEnumerable models)
public async Task<int> UpdateAllAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(IEnumerable<T> models)
{
return await _dbAsync.UpdateAllAsync(models, runInTransaction: true).ConfigureAwait(false);
var count = 0;
await _dbAsync.RunInTransactionAsync(connection =>
{
foreach (var model in models)
{
count += connection.Update(model, typeof(T));
}
}).ConfigureAwait(false);
return count;
}
public async Task<int> DeleteAsync(object model)
public async Task<int> DeleteAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(T model)
{
return await _dbAsync.DeleteAsync(model);
var mapping = await _dbAsync.GetMappingAsync(typeof(T));
var primaryKey = mapping.PK ?? throw new NotSupportedException($"Cannot delete {mapping.TableName}: it has no PK");
return await _dbAsync.DeleteAsync(primaryKey.GetValue(model), mapping);
}
public async Task<int> DeleteAllAsync<T>()
public async Task<int> DeleteAllAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>()
{
return await _dbAsync.DeleteAllAsync<T>();
}
@@ -68,12 +94,12 @@ public sealed class SQLiteHelper
return await _dbAsync.ExecuteAsync(sql);
}
public async Task<List<T>> QueryAsync<T>(string sql) where T : new()
public async Task<List<T>> QueryAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string sql) where T : new()
{
return await _dbAsync.QueryAsync<T>(sql);
}
public AsyncTableQuery<T> TableAsync<T>() where T : new()
public AsyncTableQuery<T> TableAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : new()
{
return _dbAsync.Table<T>();
}