我已在我的应用程序的DAL中实现了2个提供程序。
一个是Redis缓存提供程序,另一个是数据库提供程序。
public class CacheProvider : IProvider
{
public List<int> GetCustomerIds()
{
return cache.GetCustomerIds();
}
}
public class DBProvider : IProvider
{
public List<int> GetCustomerIds()
{
return db.GetCustomerIds();
}
}
我已经为这些
实现了一个接口public interface IProvider
{
List<int> GetCustomerIds();
}
我有以下情况。
如果缓存功能以某种方式失败或缓存在尝试执行时过期,我想退回并在函数上调用db版本。
将会实现许多功能,所以我想创建一个网关,其中所有功能都将作为参数传递,如果失败则回退到db版本
public List<int> RunTheMethod(Func<int> myMethodName)
{
// Run method from cache
myMethodName()
if method fails, run method from db
myMethodName()
}
有没有办法实现这种功能?我知道我可能必须实施一些这些方法,因为参数会有所不同。
答案 0 :(得分:2)
虽然我不确定性能,但有可能通过一些反思来完成这项工作
我们有 IDataProvider ,它由缓存提供程序,数据库提供程序和“网关”实现(我在IDataProvider中包含了3个方法,以显示不同返回/参数和重载的示例)
Gateway不必实现IDataProvider,但这样做会让生活变得更轻松,因为Gateway上的方法需要具有与提供者上调用的签名相同的签名。
网关包含一个IDataProviders列表,每次调用它都会通过列表并尝试执行。它返回第一次成功,如果没有成功则抛出异常。
Execute&lt;&gt;()方法是一种快速连接所有内容的方法,我们每次都可以调用它,让它处理与IDataProviders上的方法匹配并重新尝试失败。
出于测试目的,我创建了一种方法来强制第一个(缓存提供程序)失败。
interface IDataProvider {
List<int> Method1();
List<string> Method2(string parameter1);
List<string> Method2(string parameter1, string parameter2);
}
class DataProvider1 : IDataProvider {
private readonly string[] Strings = { "A", "B", "C" };
private bool _callFails;
public DataProvider1(bool callFails) {
_callFails = callFails;
}
public List<int> Method1() {
if (_callFails) {
throw new Exception();
}
return new List<int>(){1,2,3};
}
public List<string> Method2(string parameter1) {
if (_callFails) {
throw new Exception();
}
return Strings.Select(s => s + parameter1).ToList();
}
public List<string> Method2(string parameter1, string parameter2) {
if (_callFails) {
throw new Exception();
}
return Strings.Select(s => s + parameter1 + parameter2).ToList();
}
}
class DataProvider2 : IDataProvider {
private readonly string[] Strings = { "D", "E", "F" };
public List<int> Method1() {
return new List<int>(){4,5,6};
}
public List<string> Method2(string parameter1) {
return Strings.Select(s => s + parameter1).ToList();
}
// overload
public List<string> Method2(string parameter1, string parameter2) {
return Strings.Select(s => s + parameter1 + parameter2).ToList();
}
}
class Gateway : IDataProvider {
private readonly List<IDataProvider> _dataProviders;
public Gateway(IEnumerable<IDataProvider> dataProviders) {
_dataProviders = new List<IDataProvider>(dataProviders);
}
public List<int> Method1() {
return Execute<List<int>>();
}
public List<string> Method2(string parameter1) {
return Execute<List<string>>(parameter1);
}
public List<string> Method2(string parameter1, string parameter2) {
return Execute<List<string>>(parameter1, parameter2);
}
private T Execute<T>(params object[] parameters) {
StackTrace stackTrace = new StackTrace();
MethodBase methodBase = stackTrace.GetFrame(1).GetMethod();
var methodInfo = typeof(IDataProvider).GetMethod(methodBase.Name, methodBase.GetParameters().Select(p => p.ParameterType).ToArray());
var index = 0;
while (index < _dataProviders.Count) {
try {
return(T)methodInfo.Invoke(_dataProviders[index], parameters);
} catch (Exception) {
index++;
}
}
throw new Exception("None of the methods succeeded");
}
}
单元测试
[TestClass]
public class DataProviderFixture {
#region Create
private Gateway Create(bool firstCallFails = false) {
return new Gateway(new IDataProvider []{
new DataProvider1(firstCallFails),
new DataProvider2()});
}
#endregion
[TestMethod]
public void ExecuteNoProblems() {
var gateway = Create();
var numbers = gateway.Method1();
CollectionAssert.AreEqual(new[] { 1, 2, 3 }, numbers);
var letters = gateway.Method2("1");
CollectionAssert.AreEqual(new[] { "A1", "B1", "C1" }, letters);
letters = gateway.Method2("1", "a");
CollectionAssert.AreEqual(new[] { "A1a", "B1a", "C1a" }, letters);
}
[TestMethod]
public void ExecuteFirstCallFails() {
var gateway = Create(true);
var numbers = gateway.Method1();
CollectionAssert.AreEqual(new[] { 4, 5, 6 }, numbers);
var letters = gateway.Method2("2");
CollectionAssert.AreEqual(new[] { "D2", "E2", "F2" }, letters);
letters = gateway.Method2("1", "b");
CollectionAssert.AreEqual(new[] { "D1b", "E1b", "F1b" }, letters);
}
}