Winget相关SQLite数据库递归路径提取、数据插入缺失及EF Core查询去重异常问题
Alright, let's break down your three problems and walk through practical solutions for each one, based on the details you've shared about working with the Winget SQLite database.
1. Recursive Path Extraction to Root Node (parent=1)
Since SQLite supports Common Table Expressions (CTEs), we can use a recursive CTE to traverse the pathparts table from any starting node up to the root (where parent=1). This handles variable-depth paths automatically, unlike your fixed-layer LINQ query.
First, define a helper class to hold our recursive path results:
public class PathResult { public long rowid { get; set; } public string FullPath { get; set; } }
Next, use a recursive CTE to build the full path by starting at the leaf node (your yml entry) and working upward, then reverse the concatenated parts to get the correct hierarchy:
var query = from item in msixDB.IdsMSIXTable from manifest in msixDB.Set<ManifestMSIXTable>().Where(e => e.id == item.rowid) from yml in msixDB.PathPartsMSIXTable.Where(e => e.rowid == manifest.pathpart) // Recursive CTE to build path from yml up to root from pathResult in msixDB.Set<PathResult>().FromSqlRaw($@" WITH RECURSIVE PathCTE AS ( SELECT rowid, parent, pathpart, CAST(pathpart AS TEXT) AS CurrentPath FROM pathparts WHERE rowid = {yml.rowid} UNION ALL SELECT p.rowid, p.parent, p.pathpart, CAST(p.pathpart || '\' || c.CurrentPath AS TEXT) FROM pathparts p JOIN PathCTE c ON p.rowid = c.parent WHERE p.parent != 1 -- Stop just before root ) SELECT rowid, (SELECT pathpart FROM pathparts WHERE parent = 1) || '\' || CurrentPath AS FullPath FROM PathCTE WHERE parent = 1 -- Get the final path connecting to root ") from version in msixDB.VersionsMSIXTable.Where(e => e.rowid == manifest.version).Take(1).DefaultIfEmpty() select new ManifestTable { PackageId = item.id, YamlName = pathResult.FullPath, Version = version.version };
This query will dynamically build the full path regardless of how many parent layers exist.
2. Troubleshooting Partial Data Insertion
If only one row is being inserted, here are the most likely fixes:
- Verify query results: First run
var results = await query.ToListAsync();and check the count. If it's only one row, your join logic is filtering out data—double-check that you're usingDefaultIfEmpty()where needed to handle optional relationships. - Check for constraint violations: If your target
ManifestTablehas a primary key or unique constraint onPackageId, duplicate values will cause silent failures. Add error handling to catch this:try { mydb.ManifestTable.AddRange(await query.ToListAsync()); await mydb.SaveChangesAsync(); // Don't forget this step! } catch (DbUpdateException ex) { Console.WriteLine($"Insert failed: {ex.InnerException?.Message}"); } - Ensure you're saving changes: Your original code shows
AddRangebut noSaveChangesAsync—this is a common oversight that prevents data from being persisted.
3. Fixing Distinct AggregateException
The Distinct method with a custom GenericCompare can't be translated to SQL by EF Core or LinqToDB, which triggers the exception. Instead, use GroupBy to deduplicate at the database level (more efficient and reliable):
// Deduplicate by PackageId and Version (adjust grouping key if needed) var deduplicatedQuery = query .GroupBy(m => new { m.PackageId, m.Version }) .Select(g => g.FirstOrDefault()); var data = await deduplicatedQuery.ToArrayAsyncLinqToDB(); mydb.AddRange(data); await mydb.SaveChangesAsync();
This approach runs the deduplication on the database server, avoiding client-side processing and the associated exception.
内容的提问来源于stack exchange,提问作者user13024846

