I have a wrapper function, which takes in function making a database call returning IEnumerable<T>
.
This type T
can be any of my specific class that stores returned integer
from the database.
I made it work, but currently it only accepts specific IEnumerable<MyOwnClass>
. How to make it so that I can pass any IEnumerable<T>
of any of my classes?
And also how to handle the result in OnRetry
? As it should have access to parameter of the IEnumerable<T>
which is named differently in each class.
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()));}
Calling this wrapper:
var result = await KeepTrying(() => DB.MyFunction(), expectedInteger: 100);
Answers to Update 2
- Without proper context I can't tell when NotFoundException is thrown. But by educated guess is that it is thrown when the database function would otherwise return null. But null and empty collection are not the same.
Correct I first check if it's empty:
if (result.Count == 0) throw new NotFoundException("Some error");return result;
Is this good enough check for the error-prone result.First().MyInteger
?
- I'm not 100% sure what do mean by this. Could you please rephrase it? (Regarding handling other types)
Sure, what I have done so far is working, but I am well aware this might be very questionable design.
Sometimes I want to return from database not only integer, but also bool, or string.
So what I did is I added new property to IMyInteger
(actually renamed it to IReturnedValueFromDatabase
as it's not only int).
public interface IReturnedValueFromDatabase{ public int ReturnedInteger { get; set; } // was MyInteger public bool ReturnedBool { get; set; } public string ReturnedString { get; set; }}
Now I added few things to KeepTrying():
Added new nullable params so in HandleResult I know which one should be checked:
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"); })}
Hopefully I am not too confusing again. Basically I return different values from DB so want to make the function as generic as possible but remain DRY. I don't know if this is terrible design and maybe I should create different functions for each type, or there is other way.
- Regarding exception thrown from KeepTrying()
When I run it in test, and KeepTrying would not return anything, test wouldnt fail, so I assume it doesn't throw an exception, but it probably only gets lost somewhere. I guess I can either use try/except on KeepTrying as you proposed or in the test where I call KeepTrying (so that I can remove following Assert.That statement). But this is not that important right now.
- using Execute instead of ExecuteAsync
If it's not a problem, I can use Execute as you are suggesting. I still got lots to learn about these things. Thanks so far!
Update 3
You can avoid throwing exception. You can simply use .Any inside the HandleResult
Sorry this might have been confusing because of same naming result
- it was excerpt from my database function where I throw exception if result.Count == 0, or return result otherwise.
I have created dotnetfiddle with current version
Hence I am now also getting confused, not sure if you meant exception inside .HandleResult() - can I remove flag bool? handleResult = true
from KeepTrying()
and inside HandleResult do instead:
if (!result.Any()) // In case I don't want to check result? return false;
I think it would be best if you could just rewrite the fiddle with your suggestions to avoid further confusion.
using property selector
Sure, I can try, at least its working now. I will keep adjusting and see.
Based on this description my educated guess is that you are not awaiting the KeepTrying method inside your test. await it and it should throw the exception.
I am actually using await
(see Test class
in fiddle) - if I remove await
, I get error that Can not resolve symbol 'First' from nextline Assert.That(myNumber.First().ReturnedInteger
, which is strange, because I changed KeepTrying() to sync, but it might be because Test is async?
The strange behaviour is that when result is incorrect, it will correctly retry, but will not throw exception, thats why I am also using Assert.That
in following line - if KeepTrying would throw exception I could remove it.
I would get exception thrown from HandleResult() only when I passed one of the arguments as null, for example expectedNumber = null
. There are some strange behaviours but I can avoid these if I only use the function as expected - maybe one more question:
For DB functions when I am not checking results, where I just want to make sure something was returned (i.e. no NotFoundException was thrown), is my handleResult
flag good enough solution to ignore checking solution?
Anyway, if it's too confusing at this point, don't bother, you already helped me ton and made the function work! Thanks a lot for that.