我有一个包装函数,它接受函数进行一个返回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
- 如果没有适当的上下文,我无法判断何时抛出NotFoundException.但根据经验推测,它是在数据库函数否则将返回NULL时抛出的.但Null和空集合是不同的.
首先判断它是否为空:
if (result.Count == 0)
throw new NotFoundException("Some error");
return result;
这是不是足够好的判断容易出错的result.First().MyInteger
?
- 我不是百分百确定这是什么意思.你能换个说法吗?(关于处理其他类型)
当然,到目前为止我所做的是工作,但我很清楚这可能是非常值得怀疑的设计.
有时候我想从数据库返回,不仅是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返回不同的值,所以希望使函数尽可能通用,但保持枯燥.我不知道这是不是很糟糕的设计,也许我应该为每种类型创建不同的功能,或者有其他方法.
- 关于KeepTrying()引发的异常
当我在测试中运行它,并且KeepTrying不会返回任何东西时,测试不会失败,所以我假设它不会抛出异常,但它可能只会在某个地方丢失.我想我可以按照您的建议在KeepTrying上使用try/Except,或者在测试中调用KeepTrying(这样我就可以删除下面的Assert.That语句).但现在这件事并不那么重要.
- 使用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
标志解决方案是否足够好,可以忽略判断解决方案?
无论如何,如果在这一点上太混乱了,别费心了,你已经帮我完成了,让函数工作起来了!非常感谢你这么做.