Coincidentemente, tenho trabalhado em uma situação semelhante ultimamente - longa história: projetar e implementar um Extensão de Processamento de Dados para SSRS que permitiria agregar dados (para relatórios) provenientes de vários tipos de loja: CSV ou JSON localizados no sistema de arquivos ou SQL simples (via System.Data.SqlClient ), ou pontos de extremidade de serviço do WCF, ou algumas interfaces de serviço em processo (através de reflexão padrão), ou algumas outras fontes / formatos mais estranhos.
Eu não tenho IP na fonte, por isso não posso divulgar mais do que o abaixo - se apenas para uma visualização de 20.000 pés, resolvi algo parecido com isto:
public class DataLocatorFactory
{
public IDataLocator GetDataLocator(string dataUri)
{
// After parsing our more-or-less standard or custom URI scheme,
// use reflection over custom attribute on DataLocator classes
// to find the IDataLocator implementation responsible for that sort
// (read: syntax) of dataUri and instantiate it
}
}
public interface IDataLocator
{
IDataProvider GetDataProvider(string dataUri);
}
Então, mais tarde:
[DataLocator("file")] // for file://<file path> URIs
public class FileDataLocator : DataLocatorBase
{
// Can create CsvDataTableProvider, JsonDataTableProvider, etc... (all DataTable-centric)
}
[DataLocator("http")] // for http://... URLs
[DataLocator("https")]
public class WebDataLocator : DataLocatorBase
{
// Can create WcfDataProvider, etc... (not DataTable-centric)
}
[DataLocator("sql")] // for sql://<connection name or string> URIs
// knows only about a single DataTable-centric provider, SqlDataTableProvider
public class SqlDataLocator : DataLocatorBase
{
public override IDataProvider GetDataProvider(string dataUri)
{
if (string.IsNullOrEmpty(dataUri))
{
throw new ArgumentException("cannot be null or empty", "dataUri");
}
var provider = CreateProvider();
// Here, we're just caching the DataProviders into a ConcurrentDictionary
// (map from data URI to the provider thereof)
// If the freshly created provider can't be bound to dataUri,
// we just throw it away and return the provider already in cache for that dataUri;
// of course, that means the DataProvider constructor cannot be resource acquisition- or computation-intensive,
// but the call to Configure may be, as in WcfDataProvider creating a WS client on the fly,
// while SqlDataTableProvider simply caches the dataUri/connection string
// in an instance member
if (!_dataProviders.TryAdd(dataUri, provider))
{
provider = _dataProviders[dataUri];
}
else
{
// A specific data locator knows how to downcast the IDataProvider into
// its specific data provider and configure it depending on dataUri and
// possibly from other info coming from its own configuration, or the call context, or etc
((SqlDataTableProvider)provider).Configure(dataUri, ...); // relies on a lock(...) { if (!_configured) { ... } } internally
}
return provider;
}
}
Onde, sem surpresa:
public interface IDataProvider
{
IDataOperation GetOperation(string query);
}
Depois, mais uma vez:
// Clients interested in getting their data in the form of System.Data.DataTable,
// but not especially (or at all) interested in knowing how the data is being queried
// and/or from which type of store it will come from, will just go like:
//
// var locator = new DataLocatorFactory().GetDataLocator(uri); // "opaque" URI
// var provider = locator.GetDataProvider(uri);
// if (provider is IDataTableProvider)
// {
// // Nice, I can work with that provider!
// var operation = provider.GetOperation(query); // "opaque" query
// var dataTable = ((IDataTableProvider)provider).GetData(operation, parameters); // "opaque" operation and params
// // etc...
// }
// else
// {
// // Bummer!
// }
public interface IDataTableProvider : IDataProvider
{
DataTable GetData(IDataOperation operation, object[] parameters);
}
Onde, naturalmente:
public class SqlDataTableProvider : DataProviderBase, IDataTableProvider
{
public override IDataOperation GetOperation(string query)
{
// Parse query and create our own dog food here, as, say,
// a SqlOperation
}
public virtual DataTable GetData(IDataOperation operation, object[] parameters)
{
// Attempts to downcast operation into, say, a SqlOperation, and if satisfied,
// interprets it accordingly to emit SQL, run it, and shape the results
// into the promised DataTable
}
}
Ou também:
public class JsonDataTableProvider : DataProviderBase, IDataTableProvider
{
public override IDataOperation GetOperation(string query)
{
// Parse query and create our own dog food here, as, say,
// a JsonPathQuery
}
public virtual DataTable GetData(IDataOperation operation, object[] parameters)
{
// Attempts to downcast operation into, say, a JsonPathQuery, and if satisfied,
// interprets it accordingly to delegate the job to a JsonPath implementation,
// in some JSON parser, and shape the results into the promised DataTable
}
}
Etc, etc
Até agora, ele está sendo dimensionado bem o suficiente, ou seja, não é muito dele.
Finalmente, embora eu goste deles, não senti a necessidade de introduzir nenhum genérico neste (ainda / se algum).
'Espero que isso ajude.