如何构造委托函数以便在每个用例中 运行 不同的代码行

How to construct delegate function in order to run different lines of code in each use case

我是委托 Func<> 和 Action<> 的新手。我已尝试阅读多个堆栈溢出 post 和文档,但无济于事。

这个问题同样旨在了解我应该如何将问题概念化,或者我是否应该以完全不同的方式思考。无论如何,我们走了...

我有一个 BaseRepository class 连接到 mongoDB。下面我们看到一个与此 BaseClass

关联的方法之一的示例
public async Task<IEnumerable<TEntity>> GetAll(int skip = 0, int limit = 100)
{
    return await DefaultCollection
        .Find(Filter.Empty)
        .Skip(skip)
        .Limit(limit)
        .ToListAsync();
}

我想做的是实现一些跟踪,以跟踪 API 和数据库之间经过的毫秒数的通信时间,以便我更好地确定优化的优先级。所以简单来说,该方法应该如下所示:

public async Task<IEnumerable<TEntity>> GetAll(int skip = 0, int limit = 100)
{
    var sw = new Stopwatch();

    sw.Start();
    var result = await DefaultCollection
        .Find(Filter.Empty)
        .Skip(skip)
        .Limit(limit)
        .ToListAsync();
    sw.Stop();

    Logger.PushContext("Elapsed Milles to DB", sw.ElapsedMilliseconds);
    return result;

}

然而,将其写入每个方法会很繁琐,所以我想知道最佳实践是什么,并考虑制作如下内容:警告伪代码传入;)

public async Task<IEnumerable<TEntity>> GetAll(int skip = 0, int limit = 100)
    {
        return await DefaultCollection
            .Find(Filter.Empty)
            .Skip(skip)
            .Limit(limit)
            .ToListAsync();
    }

    /// <summary>
    /// PSEUDO CODE. ONLY PSEUDO CODE
    /// </summary>
    /// <returns></returns>
    protected async Task<TResult> ExecuteCmd(* Inject code into this method* injectedCode)
    {

        var sw = new Stopwatch();

        sw.Start();

        var result = injectedCode.Run();

        sw.Stop();

        Logger.PushContext("Elapsed milli to db", sw.ElapsedMilliseconds);

        return result;
    }

这样每个请求都会被记录下来,我应该做的唯一改变是将每个方法放在 ExecuteCmd 中。

也许是这样的:

public async Task<IEnumerable<TEntity>> GetAll(int skip = 0, int limit = 100)
{
    return await ExecuteCmd(c =>
    {
        DefaultCollection
            .Find(Filter.Empty)
            .Skip(skip)
            .Limit(limit)
            .ToListAsync();
    });
}

此代码在 ExecuteCmd 中 运行 为 "injectedCode.run"。

我想象 ExecuteCmd 可以采用不同类型的语句并分别 return 不同的结果...

请让我知道这是否是模棱两可的方式。如果是的话,我很抱歉,如果我问了一些更强调的问题,请告诉我,这样我就可以重新措辞。

此致!提前致谢

整个基础存储库都可以在这里看到以供参考:

编辑 这是更新后的基础存储库,希望对其他人有所帮助!

public abstract class MongoReadmodelRepository<TEntity> : IMongoReadmodelRepository<TEntity> where TEntity : IEntity
{
    protected readonly ILogger Logger;
    protected readonly IMongoDatabase DefaultDatabase;
    protected readonly string CollectionName = $"rm-{typeof(TEntity).Name.ToLower()}";
    protected IMongoCollection<TEntity> DefaultCollection =>
        DefaultDatabase.GetCollection<TEntity>(CollectionName);


    protected UpdateDefinitionBuilder<TEntity> Update => Builders<TEntity>.Update;
    protected SortDefinitionBuilder<TEntity> Sort => Builders<TEntity>.Sort;
    protected FilterDefinitionBuilder<TEntity> Filter => Builders<TEntity>.Filter;
    protected ProjectionDefinitionBuilder<TEntity> Projection => Builders<TEntity>.Projection;

    public MongoReadmodelRepository(IMongoClient client, IOptions<ProjectionsPersistenceConfiguration> config, ILogger logger)
    {
        Logger = logger;
        DefaultDatabase = client.GetDatabase(config.Value.DefaultProjectionsDatabaseName);

        if (!CollectionExists(DefaultDatabase, CollectionName))
            DefaultDatabase.CreateCollection(CollectionName);
    }


    public async Task<bool> Delete(Guid id)
    {
        Logger.Information("Trying to delete {Entity} with {Id}", typeof(TEntity).Name, id);
        return (await DefaultCollection.DeleteOneAsync(Filter.Eq(x => x.Id, id)))
            .IsAcknowledged;
    }

    public async Task<IEnumerable<TEntity>> GetAll(int skip = 0, int limit = 100)
    {
        return await ExecuteCmd(
            () =>
                DefaultCollection
                    .Find(Filter.Empty)
                    .Skip(skip)
                    .Limit(limit)
                    .ToListAsync()
        );
    }

    public async Task<TEntity> GetByIndex(int index, int collectionSize)
    {
        return await DefaultCollection.Find(Filter.Empty)
            .Skip(index)
            .Limit(1)
            .FirstOrDefaultAsync();

    }

    public async Task<IEnumerable<TEntity>> GetPaged(int page, int pageSize)
    {
        return await GetAll(page * pageSize, pageSize);
    }

    public async Task<TEntity> GetById(Guid id)
    {
        return await DefaultCollection.Find(b => b.Id == id).SingleOrDefaultAsync();
    }

    public async Task<Guid> Insert(TEntity entity)
    {
        await DefaultCollection.InsertOneAsync(entity, new InsertOneOptions());

        Logger.Information("Saved {@Entity}", entity);
        return entity.Id;
    }

    private bool CollectionExists(IMongoDatabase db, string collectionName)
    {
        var filter = new BsonDocument("name", collectionName);
        var collections = db.ListCollections(new ListCollectionsOptions { Filter = filter });
        return collections.Any();
    }

    protected async Task<TResult> ExecuteCmd<TResult>(Func<Task<TResult>> query)
    {
        var sw = new Stopwatch();

        //Start stopwatch
        sw.Start();

        var result = await query();

        sw.Stop();

        Console.WriteLine("Logging execution time between API and mongoDB: Execution time in millis = " + sw.ElapsedMilliseconds);

        return result;
    }
}

这是一个相当简单的翻译。您想要一个不带参数的方法(如 injectedCode.Run() 所示,是可等待的,因此必须 return 一个像 Task<T> 这样的可等待对象,并且具有 TResult 类型的结果。因此,您的委托参数应为 Func<Task<TResult>>.

类型
protected async Task<TResult> ExecuteCmd<TResult>(Func<Task<TResult>> query)
{
    // ...
    var result = await query();
    // ....
}

然后您可以通过将 GetAll 中的查询转换为兼容的 lambda 来调用它。 ToListAsync<T> returns a Task<List<T>> 可以用作 Task<IEnumerable<T>>(因为异步方法是如何被编译器翻译的)。 TDefaultCollection 中的内容,即 TEntity 中的内容,ExecuteCmd 编辑的任务对象 return 是 GetAll 中等待的内容。 =30=]

public async Task<IEnumerable<TEntity>> GetAll(int skip = 0, int limit = 100)
{
    return await ExecuteCmd(
        () => DefaultCollection
            .Find(Filter.Empty)
            .Skip(skip)
            .Limit(limit)
            .ToListAsync()
        );
}

skiplimit 被 lambda 捕获,这就是委托不接受参数的原因。

您可以像这样直接传递任务对象:

public Task<IEnumerable<TEntity>> GetAll(int skip = 0, int limit = 100)
{
    return ExecuteCmd(
        () => DefaultCollection
            .Find(Filter.Empty)
            .Skip(skip)
            .Limit(limit)
            .ToListAsync()
        );
}

这看起来更简洁,因为您不需要 asyncawait 关键字。缺点是您会丢失异常中的堆栈信息,并且更难追踪异常发生的位置。我认为大多数专家会反对这样做,但最终决定权在您。