在Task中使用DbContext无法正常工作,只能工作一次,使用两次就会崩溃

问题描述 投票:0回答:1

我正在开发一个带有 EF core 和 sqlite db 的 Winui/c# 应用程序。 我正在一个包含多个表的数据库中搜索,每个表包含 9M 条记录 (9,000,000)。

对于这个繁重的搜索,我使用了 Task.Run 和 async/await。现在有一个问题,当我搜索数据时,我可以看到应用程序运行正常并显示结果。 但如果我再做一次搜索,我会得到一些例外:

抛出异常:WinRT.Runtime.dll 中的“System.InvalidOperationException” 抛出异常:WinRT.Runtime.dll 中的“System.ArgumentOutOfRangeException”

我将所有代码放入 Dispatcher.TryEnqueue 并修复了问题,但是 Task.Run 没有任何效果并且 ui 被阻塞。

那么我该如何解决这个问题呢?看来db在Task.Run中无法正常工作

await Task.Run(async () =>
 {
     await PerformSearchAsync(query, progress, cancellationTokenSource.Token);
 });

private Dictionary<TableType, Func<DbContext, IQueryable<object>>> tableMappings = new Dictionary<TableType, Func<DbContext, IQueryable<object>>>
{
    { TableType.T935, db => db.Set<Person935>() },
    { TableType.T936, db => db.Set<Person936>() },
    { TableType.T937, db => db.Set<Person937>() },
    { TableType.T938, db => db.Set<Person938>() },
    { TableType.T939, db => db.Set<Person939>() },
    { TableType.T93033, db => db.Set<Person93033>() }
};

public async Task PerformSearchAsync(string query, IProgress<int> progress, CancellationToken cancellationToken)
{
    var db = new IFDBDbContext();

    var tableQueries = new List<IAsyncEnumerable<object>>();
    
    var tableType = GetTableType(SearchQuery);

    if (tableMappings.TryGetValue(tableType, out var dbQuery))
    {
        var dbSet = dbQuery(db);
        tableQueries.Add(GetSearchResultsAsync(dbSet, query, cancellationToken));
    }

    // Track progress
    int totalTables = tableQueries.Count;
    int completedTables = 0;
    int completedItems = 0;

    foreach (var tableQuery in tableQueries)
    {
        await foreach (var item in tableQuery.WithCancellation(cancellationToken))
        {
            completedItems++;

            dispatcherQueue.TryEnqueue(() =>
            {
                DataList.Add(item);
                _tmpCompletedItems = completedItems;
            });
        }

        completedTables++;
        progress.Report((completedTables * 100) / totalTables);
    }

    dispatcherQueue.TryEnqueue(() =>
    {
        ShowStatus(DataList.Count);
    });
}

private async IAsyncEnumerable<object> GetSearchResultsAsync(IQueryable<object> dbSet, string query, [EnumeratorCancellation] CancellationToken cancellationToken)
{
    await foreach (var item in dbSet
        .Where(x => EF.Property<string>(x, nameof(BasePerson.Mobile)) != null && EF.Property<string>(x, nameof(BasePerson.Mobile)).Contains(query))
        .AsAsyncEnumerable()
        .WithCancellation(cancellationToken))
    {
        yield return item;
    }
}
c# entity-framework-core task ef-code-first winui-3
1个回答
0
投票

我已经写了很多这样的代码来进行优化。我没有完整的代码可供分析,我建议进行以下更改。

  1. 首先也是最重要的,拆分单个表以返回批量数据,例如每次获取 50K 记录。为此,请编写一个返回 Task Tuple> 的方法。您可以将 9M 记录分成批次并在列表中获取结果。为此,您需要一个标识符列来将表中的数据拆分为批次。

这只是我如何看待代码可能有点问题的示例,但我希望您能理解其中的逻辑

课程

public class Records
{
}
public class QueryResult
{
    public QueryResult(string query, List<Records> results)
    {
        this.QueryToExecute = query;
        this.Results = results;
    }
    public string QueryToExecute { get; set; }
    public List<Records> Results { get; set; } = new List<Records>();

    public string FailedMessage { get; set; } = string.Empty;
}

功能

public async void Hello()
{
    var queryDict = new ConcurrentDictionary<string, List<QueryResult>>();
    queryDict.TryAdd("Table1", new List<QueryResult>()
    {
        new QueryResult("Select XXX FROM Table1 WHERE ID>1 AND ID<50000", new List<Records>()),
        new QueryResult("Select XXX FROM Table1 WHERE ID>50001 AND ID<100000", new List<Records>()),
        new QueryResult("Select XXX FROM Table1 WHERE ID>100000 AND ID<150000", new List<Records>()),
        new QueryResult("Select XXX FROM Table1 WHERE ID>150001 AND ID<200000", new List<Records>()),
    });
    queryDict.TryAdd("Table2", new List<QueryResult>()
    {
        new QueryResult("Select XXX FROM Table2 WHERE ID>1 AND ID<50000", new List<Records>()),
        new QueryResult("Select XXX FROM Table2 WHERE ID>50001 AND ID<100000", new List<Records>()),
        new QueryResult("Select XXX FROM Table2 WHERE ID>100000 AND ID<150000", new List<Records>()),
        new QueryResult("Select XXX FROM Table2 WHERE ID>150001 AND ID<200000", new List<Records>()),
    });

    foreach (var record in queryDict.Values) 
    {
        foreach (var item in record)
        {
            await Task.WhenAll(
            Task.Run(() => RunQuery(item)),
            Task.Run(() => RunQuery(item)));
        }
    }
}

private void RunQuery(QueryResult queryToExecute)
{
    // Execute queryusing EF
}
  1. 数据分割后,使用 Task.WhenAll() 循环运行每个表的批次
  2. 始终为每个表创建一个数据库上下文,并在读取所有数据后将其释放。
© www.soinside.com 2019 - 2024. All rights reserved.