我有一个包装函数,它接受函数进行一个返回IEnumerable<T>的数据库调用.

此类型T可以是存储从数据库返回的integer的任何特定类.

我做了它的工作,但目前它只接受特定的IEnumerable<MyOwnClass>.如何使我能通过我的任何课程的任何IEnumerable<T>

还有如何处理OnRetry分的成绩呢?因为它应该可以访问IEnumerable<T>的参数,该参数在每个类中以不同的方式命名.


public static async ValueTask<TResult> KeepTrying<TResult>(Func<TResult> func, int expectedInteger) where TResult : IEnumerable<MyOwnClass>
{

var pipeline = new ResiliencePipelineBuilder<IEnumerable<MyOwnClass>>()
.AddRetry( new RetryStrategyOptions<IEnumerable<MyOwnClass>>(){

    ShouldHandle = new PredicateBuilder<IEnumerable<MyOwnClass>>().Handle<NotFoundException>()
    .HandleResult(result => result.First().MyInteger != expectedInteger),
    MaxRetryAttempts = 3,
    Delay = TimeSpan.FromSeconds(2)
})
.Build();

return await pipeline.ExecuteAsync(token => new ValueTask<TResult>(func()));

}

调用这个包装器:

var result = await KeepTrying(() => DB.MyFunction(), expectedInteger: 100);

回复更新2


  1. 如果没有适当的上下文,我无法判断何时抛出NotFoundException.但根据经验推测,它是在数据库函数否则将返回NULL时抛出的.但Null和空集合是不同的.

首先判断它是否为空:

if (result.Count == 0)
    throw new NotFoundException("Some error");

return result;

这是不是足够好的判断容易出错的result.First().MyInteger

  1. 我不是百分百确定这是什么意思.你能换个说法吗?(关于处理其他类型)

当然,到目前为止我所做的是工作,但我很清楚这可能是非常值得怀疑的设计.

有时候我想从数据库返回,不仅是integer,而且是bool,或字符串.

所以我所做的是将新属性添加到IMyInteger(实际上将其重命名为IReturnedValueFromDatabase,因为它不仅仅是int).

public interface IReturnedValueFromDatabase
{
    public int ReturnedInteger { get; set; } // was MyInteger
    public bool ReturnedBool { get; set; }
    public string ReturnedString { get; set; }
}

现在,我在KeepTrying()中添加了一些内容:

添加了新的可空参数,以便在HandleResults中我知道应该判断哪个参数:


public static async ValueTask<TResult> KeepTrying<TResult>(..., int? expectedNumber = null, bool? expectedBool = null, bool handleResult = true) where TResult : IReturnedValueFromDb
{
    // skipping to .HandleResult()
    .HandleResult(result => {
   
        if (!handleResult)
           // Sometimes I want to only handle Exception and not to check result, so added this check, but maybe there is better way?
           return false;

        if (expectedNumber != null)
           // I am passing int so lets check int
           return result.First().ReturnedInteger != expectedNumber;

        if (expectedBool != null)
           // I am passing bool so lets check bool
           return result.First().ReturnedBool != expectedBool;

        // all else fails, throw exception
        throw new InvalidOperationException("Unable to determine result");
    })
}

希望我不会再一次太困惑了.基本上,我从DB返回不同的值,所以希望使函数尽可能通用,但保持枯燥.我不知道这是不是很糟糕的设计,也许我应该为每种类型创建不同的功能,或者有其他方法.

  1. 关于KeepTrying()引发的异常

当我在测试中运行它,并且KeepTrying不会返回任何东西时,测试不会失败,所以我假设它不会抛出异常,但它可能只会在某个地方丢失.我想我可以按照您的建议在KeepTrying上使用try/Except,或者在测试中调用KeepTrying(这样我就可以删除下面的Assert.That语句).但现在这件事并不那么重要.

  1. 使用Execute而不是ExecuteAsync

如果没有问题,我可以按照你的建议使用Execute.我还有很多东西要学.到目前为止谢谢!

更新3


您可以避免引发异常.您只需在HandleResult中使用.Any

抱歉,这可能因为相同的命名result而令人困惑-它是从我的数据库函数中摘录的,在该函数中,如果Result.Count==0则抛出异常,否则返回Result.

我创造了dotnetfiddle with current version

因此,我现在也变得困惑,不确定你是否指的是异常内部.HandleContact()—我可以从KeepTrying()中删除标志bool? handleResult = true,然后在HandleContact内部这样做吗:

if (!result.Any()) // In case I don't want to check result?
    return false;

我认为最好你能用你的建议重写一下小提琴,以免引起进一步的混淆.

使用属性 Select 器

当然,我可以试一试,至少它现在起作用了.我会继续调整和观察.

根据这个描述,我有根据的猜测是,你没有等待在你的测试中的KeepTrying方法.等待它,它应该抛出异常.

我实际上使用的是await(参见fiddle中的Test class)——如果我删除了await,我会从第Assert.That(myNumber.First().ReturnedInteger行得到错误Can not resolve symbol 'First',这很奇怪,因为我把KeepTrying()改为sync,但这可能是因为Test是cupc?

奇怪的行为是,当结果不正确时,它会正确重试,但不会抛出异常,这就是为什么我也使用Assert.That在下面的行—如果KeepTrying会抛出异常,我可以删除它.

只有当我传递一个参数为null时,我才会从HandleResponse()中抛出异常,例如expectedNumber = null.有一些奇怪的行为,但如果我只按预期使用函数,我可以避免这些行为—也许还有一个问题:

对于DB函数,当我不判断结果时,我只想确保返回了一些东西(即没有抛出NotFoundException),我的handleResult标志解决方案是否足够好,可以忽略判断解决方案?

无论如何,如果在这一点上太混乱了,别费心了,你已经帮我完成了,让函数工作起来了!非常感谢你这么做.

推荐答案

如何使我能通过任何IEnumerable<T>门课程?

您应该更改方法的签名

ValueTask<TResult> KeepTrying<TResult>(
   Func<TResult> func, int expectedInteger) 
   where TResult : IEnumerable<MyOwnClass>

ValueTask<IEnumerable<TResult>> KeepTrying<TResult>(
  Func<IEnumerable<TResult>> func, int expectedInteger) 
  where TResult : IMyInteger

所以,基本上你的TResult不是可重写的集合,而是它的泛型类型参数.

static async ValueTask<IEnumerable<TResult>> KeepTrying<TResult>(
  Func<IEnumerable<TResult>> func, int expectedInteger) 
  where TResult : IMyInteger
{
    var pipeline = new ResiliencePipelineBuilder<IEnumerable<TResult>>()
    .AddRetry(new()
    {
        ShouldHandle = new PredicateBuilder<IEnumerable<TResult>>()
            .Handle<NotFoundException>()
            .HandleResult(result => result.First().MyInteger != expectedInteger),
        MaxRetryAttempts = 3,
        Delay = TimeSpan.FromSeconds(2)
    })
    .Build();

    return await pipeline.ExecuteAsync(_ => ValueTask.FromResult(func()));
}
  • 请记住,result.First().MyInteger很容易出错,因为它assumes集合中始终至少有1个元素

IMyInteger是一个简单的接口,它定义了MyInteger属性的getter

interface IMyInteger
{
    public int MyInteger { get; }
}

And also how 到 handle the result in OnRetry?

OnRetry = static args =>
{
     Console.WriteLine(args.Outcome.Result);
     return default;
}

argsOutcome property它的ExceptionResult属性根据触发重试的Handle方法来填充.


对于完整性的动摇这里是完整的示例应用程序

await KeepTrying<A>(() => [new ()], 1); //no retry
await KeepTrying<B>(() => [new ()], 1); //three retries

async ValueTask<IEnumerable<TResult>> KeepTrying<TResult>(
  Func<IEnumerable<TResult>> func, int expectedInteger) 
  where TResult : IMyInteger
{
    var pipeline = new ResiliencePipelineBuilder<IEnumerable<TResult>>()
    .AddRetry(new()
    {
        ShouldHandle = new PredicateBuilder<IEnumerable<TResult>>()
            .Handle<NotFoundException>()
            .HandleResult(result => result.First().MyInteger != expectedInteger),
        MaxRetryAttempts = 3,
        Delay = TimeSpan.FromSeconds(2),
        OnRetry = static args =>
        {
            Console.WriteLine(args.Outcome.Result);
            return default;
        }
    })
    .Build();

    return await pipeline.ExecuteAsync(_ => ValueTask.FromResult(func()));
}    

interface IMyInteger
{
    public int MyInteger { get; }
}

class A : IMyInteger
{
    public int MyInteger { get; } = 1;
}

class B : IMyInteger
{
    public int MyInteger { get; } = 2;
}

UPDATE #1

I forgot 到 mention 到 please prefer Execute over ExecuteAsync in this scenario:

return ValueTask.FromResult(pipeline.Execute(func));

UPDATE #2

  1. If result.First().MyInteger很容易出错,什么是更好的方法?我首先处理的是NotFoundException,所以它确实应该在那里.或者这还不够?

如果没有适当的上下文,我不知道什么时候会抛出NotFoundException.但根据有根据的猜测,它是在数据库函数否则将返回null时抛出的.但null和空集是不一样的.

如果数据库函数可以返回空集合,如new MyOwnClass[],则.First将返回thrown an InvalidOperationException.如果集合为空,FirstOrDefault不会抛出任何异常,但会返回default(T).

  1. Now Im trying 到 implement not only integer, but also bool and string - can I expand HandleResult and insert if statements? If (expectedNumber != null) check number, etc? If you understand what I mean, if not I can reply later on PC.

我不是百分之百确定这到底是什么意思.你能不能换个说法?

  1. How 到 throw an exception 从 this KeepTrying function if it fails?

如果所有重试try 都失败,则重试策略将传播最后一个异常.看看this sequence diagram个,它完全描述了这个场景.第Execute{Async}章会帮你做的,不用问.

If you want 到 change it the exception 到 a cus到m one then

try
{
   return await pipeline.ExecuteAsync(到ken => new ValueTask<TResult>(func()));
   //OR
   //return ValuTask.FromResult(pipeline.Execute(func));
}
catch(NotFoundException)
{
   return ValueTask.FromException(new Cus到mException(ex));
}

另一种解决方案可以利用ExecuteOutcomeAsync

  1. 关于用Execute代替ExecuteAsync,你介意解释一下原因吗?我正在运行一个测试,所有东西都是async,我还应该只使用Execute吗?

Even though Polly V8 is async by its core it does not mean that you have 到 always use ExecuteAsync. Execute is designed 到 decorate synchronous code invocation. So, Execute is absolutely a good choice 到 decorate your func().

Also your KeepTrying is implemented in a synchronous fashion so, it feels more natural 到 use Execute for retry as well.

Dotnet fiddle:https://dotnetfiddle.net/M0lViU


UPDATE #3

if (result.Count == 0)
   throw new NotFoundException("Some error");

return result;

这是不是足够好的判断容易出错的result.First().MyInteger

你可以避免抛出异常.你可以简单地在HandleResult里面使用.Any,就像这样:

ShouldHandle = new PredicateBuilder<IEnumerable<TResult>>()
    .HandleResult(result => !result.Any() || result.First().MyInteger != expectedInteger)

So what I did is I added new property 到 IMyInteger (actually renamed it 到 IReturnedValueFromDatabase as it's not only int).

Instead of having multiple if statements you can pass a property selec到r 到 your function: Expression<Func<IReturnedValueFromDatabase,TProperty>> selec到r

因此,方法的签名可以看起来像这样:

ValueTask<IEnumerable<TResult>> KeepTrying<TResult, TProperty>(
  Func<IEnumerable<TResult>> func,
  Expression<Func<TResult,TProperty>> selec到r 
  TProperty expectedValue) 
  where TResult : IReturnedValueFromDatabase

I know I know it's getting more and more complicated, but if you wish 到 have a really generic solution then that's the way how you should approach the problem.

当我在测试中运行它时,KeepTrying不会返回任何东西,测试不会失败,所以我假设它不会抛出异常,但它可能只会在某个地方迷路.

根据这个描述,我有根据的猜测是,你没有awaiting KeepTrying方法在你的测试.await它,它应该抛出异常.

Csharp相关问答推荐

为什么.Equals(SS,StringComparison. ClientCultureIgnoreCase)在Net 4.8和6.0之间不同?

等待限制选项似乎不适用于频道

PredicateBuilder不是循环工作,而是手动工作

Serilog SQL服务器接收器使用UTC作为时间戳

应该使用哪一个?"_counter += 1 OR互锁增量(ref_counter)"""

并行令牌更新

我需要两个属性类吗

如何修改中间件或其注册以正确使用作用域服务?

实体核心框架--HasColumnType和HasPrecision有什么不同?

在LINQ Where子句中使用新的DateTime

Appsettings.json未加载.Net 8 Blaazor Web程序集

未找到任何HTTP触发器.成功部署Azure Functions Project后(c#)

在使用UserManager时,如何包含与其他实体的关系?

源代码生成器项目使用`dotnet build`编译,而不是在Visual Studio中编译?

Visual Studio,Docker容器-容器调用:连接被拒绝

c#在后台实现类型化数组

从HTML元素获取 colored颜色

C#-如何将int引用获取到byte[]

C#If条件格式

为什么使用User.IsInRole()总是返回FALSE?