Сгенерированный запрос из приведенного ниже кода в порядке, я могу успешно запустить его в Pgadmin, он возвращает 2 ряда, но Dapper не заполняет OBJS [0] с детьми в родительском объекте. И objs [1] - это объект типа дочернего типа, который имеет все нулевое и идентификатор 0.
Код: Выделить всё
public async Task GetByIdAsync(DynamicGetByIdRequest getByIdReq, CancellationToken cancellationToken = default)
{
Type rootType = ResolveEntityType(getByIdReq.DynamicGetParameter!.EntityName);
var sqlMeta = _metaExtractor.Extract(rootType);
var queryParts = BuildQuery(getByIdReq.DynamicGetParameter, sqlMeta, rootType, "T0");
string sql = $@"SELECT {queryParts.SelectClause}
FROM {sqlMeta.Table.Quote()} AS {"T0".Quote()}
{queryParts.JoinClause}
WHERE {"T0".Quote()}.{"Id".Quote()} = @id";
var lookup = new Dictionary();
await _db.QueryAsync(
sql,
types: queryParts.Types.ToArray(),
map: (object[] objs) =>
{
var root = objs[0];
var rootId = (int)root.GetType().GetProperty("Id").GetValue(root);
if (!lookup.TryGetValue(rootId, out object existing))
{
lookup.Add(rootId, root);
existing = root;
// Initialize collection properties
foreach (var prop in root.GetType().GetProperties())
{
if (prop.PropertyType.IsGenericType &&
prop.PropertyType.GetGenericTypeDefinition() == typeof(List))
{
prop.SetValue(existing, Activator.CreateInstance(prop.PropertyType));
}
}
}
// Handle child objects
for (int i = 1; i < objs.Length; i++)
{
var child = objs[i];
if (child == null)
continue;
var childType = queryParts.Types[i];
var navProp = rootType.GetProperties()
.FirstOrDefault(p => p.PropertyType.IsGenericType &&
p.PropertyType.GetGenericTypeDefinition() == typeof(List) &&
p.PropertyType.GetGenericArguments()[0] == childType);
if (navProp != null)
{
var collection = navProp.GetValue(existing) as IList;
var childId = (int)child.GetType().GetProperty("Id").GetValue(child);
if (!collection.Cast().Any(x =>
(int)x.GetType().GetProperty("Id").GetValue(x) == childId))
{
collection.Add(child);
}
}
}
return existing;
},
param: new { id = getByIdReq.Id },
splitOn: string.Join(",", queryParts.SplitOnColumns)
);
return lookup.Values.FirstOrDefault();
}
private (List Types, string SelectClause, string JoinClause, List SplitOnColumns)
BuildQuery(DynamicGetParameter param, SqlMeta meta, Type entityType, string alias, string? parentAlias = null)
{
var types = new List { entityType };
var selectParts = new List();
var joinParts = new List();
var splitOnColumns = new List();
// 1. Select all scalar properties for root entity
foreach (var prop in meta.ScalarProps)
{
if (param.Properties == null || param.Properties.Contains(prop.Name, StringComparer.OrdinalIgnoreCase))
{
selectParts.Add($"{alias.Quote()}.{prop.Name.Quote()} AS \"{prop.Name}\"");
}
}
// Include root ID (only once)
if (!selectParts.Any(sp => sp.Contains($"\"Id\"")))
{
selectParts.Add($"{alias.Quote()}.{"Id".Quote()} AS \"Id\"");
}
// 2. Handle nested properties
if (param.Navigation != null)
{
foreach (var nav in param.Navigation)
{
Type navType = ResolveEntityType(nav.EntityName);
var navMeta = _metaExtractor.Extract(navType);
string navAlias = $"{alias}_{nav.EntityName}";
types.Add(navType);
// Join clause
joinParts.Add($"LEFT JOIN {navMeta.Table.Quote()} AS {navAlias.Quote()} " +
$"ON {alias.Quote()}.{"Id".Quote()} = {navAlias.Quote()}.{"ReferralId".Quote()}");
// Select child properties
foreach (var prop in navMeta.ScalarProps)
{
if (nav.Properties == null || nav.Properties.Contains(prop.Name, StringComparer.OrdinalIgnoreCase))
{
selectParts.Add($"{navAlias.Quote()}.{prop.Name.Quote()} AS \"{prop.Name}\"");
}
}
// Select child ID with correct alias for mapping (only once)
//selectParts.Add($"{navAlias.Quote()}.{"Id".Quote()} AS \"Id\"");
// Select child ID again with unique alias for splitOn
selectParts.Add($"{navAlias.Quote()}.{"Id".Quote()} AS \"{navAlias}_Id\"");
splitOnColumns.Add($"{navAlias}_Id");
}
}
return (
Types: types,
SelectClause: string.Join(", ", selectParts),
JoinClause: string.Join(" ", joinParts),
SplitOnColumns: splitOnColumns
);
}
< /code>
Сгенерированный запрос SQL: < /p>
SELECT
"r"."Id",
"r"."ReservationStatus",
"r"."Language",
"r"."ReferralSource",
"ra"."Id",
"ra"."AppointmentDate",
"ra"."AppointmentTime",
"ra"."LegSequence",
"ra"."LegStatus",
"ra"."ReferralId"
FROM
"Referral" AS "r"
LEFT JOIN "ReferralAppointment" AS "ra" ON "r"."Id" = "ra"."ReferralId"
WHERE
"r"."Id" = 1
Тот же запрос работал со статическими типами и жестким запросом. Я использовал тот же жестко -кодированный запрос с динамической функцией, с тем же Sploton. Но динамическая функция не заполняет дочерние коллекции. Это пусто, а не ноль. AI говорит, что в случае статического Dapper знает (_db.queryasync ), типы статически. Это проблема.
Я спросил, могу ли я использовать свои динамические типы в общей функции (_db.queryasync ), он предложил мне вызвать QueryAsync через отражение. Но после возвращения null: < /p>
Код: Выделить всё
var method = typeof(IDbConnection).GetMethods()
.FirstOrDefault(m => m.Name == "QueryAsync" && m.IsGenericMethod);
Подробнее здесь: https://stackoverflow.com/questions/796 ... -correctly