My coworkers and I are putting together a small reporting framework for an online storefront. We've built a library following the repository pattern, using "reports" as the repositories and a very light service layer to interact with said reports. Our code works great, and is pretty easy to use. However, there is one thing that is bothering me personally: at the service factory level, we need to declare our return type twice (it's not inferred). Here's the basis of our project:
The Report Interface
This is what is used as our "Repositories". They take in Data Access Objects, such as wrapper-classes to a SQL/Oracle Connection, or our Storefront's API.
internal interface IReport<T>
{
T GetReportData(dynamic options);
}
The Repository Factory
This provides an easy way to generate those reports by knowing the type.
internal interface IReportFactory
{
TR GenerateNewReport<T, TR>() where TR : IReport<T>;
}
internal class ReportFactory : IReportFactory
{
public ReportFactory()
{
// some initialization stuff
}
public TR GenerateNewReport<T, TR>() where TR : IReport<T>
{
try
{
return (TR)Activator.CreateInstance(typeof(TR));
}
catch(Exception ex)
{
// Logging
}
}
}
An Example Report (Repository)
Here is what a report would look like. Notice that it returns a DataTable, and is declared with that in the generic interface (this will come in to play shortly).
internal class ItemReport : IReport<DataTable>
{
public DataTable GetReportData(dynamic options)
{
return new DataTable();
}
}
The Report Service
A lightweight service that takes in a report (repository) and works with it. Light, but it allows for easy unit testing and such, and if we ever want to add additional processing after the reports are retrieved, it'd be easy to do so.
public interface IReportService<T>
{
T GetReportData(dynamic options);
}
public class ReportService<T> : IReportService<T>
{
private readonly IReport<T> _report;
public ReportService(IReport<T> report)
{
_report = report;
}
public T GetReportData(dynamic options)
{
return _report.GetReportData(options);
}
}
The Service Factory
We have the service factory setup as an abstract class (because all service factories will need to create the report factory), forcing a default constructor:
public abstract class ReportServiceFactory
{
protected IReportFactory ReportFactory;
protected ReportServiceFactory(connection strings and other stuff)
{
ReportFactory = new ReportFactory(connection strings and other stuff);
}
}
We can then create separate service factories based on function. For example, we have a "standard report" service factory, as well as customer specific service factories. The implementation here is where my question is.
public class SpecificUserServiceFactory : ReportServiceFactory
{
public SpecificUserServiceFactory(connection strings and other stuff) : base(connection strings and other stuff){}
public IReport<DataTable> GetItemReport()
{
return new ReportService<DataTable>(ReportFactory.GenerateNewReport<DataTable, ItemReport>());
}
}
Why must I be so verbose when creating the service factory? I declare the return type twice. Why can't I do something like this:
return new ReportService(ReportFactory.GenerateNewReport<ItemReport>());
Notice that I don't declare DataTable in this; I think that it should be inferred from the fact that ItemReport is IReport. Any suggestions on how to get it to work this way would be greatly appreciated.
Sorry for such the long explanation to ask such a simple question, but I figured all that backing code would help come up with a solution. Thanks again!
The reason you can't omit the DataTable generic type in the calling of GenerateNewReport is because it's a constraint on the other generic type in that function definition. Let's assume (for simplicity's sake) your IReport<T> actually was an interface with a function void Something(T input). A class could implement both IReport<int> and IReport<string>. Then, we build such a class called Foo : IReport<int>, IReport<string>. The compiler would not be able to compile bar.GenerateNewReport<Foo> because it doesn't know if it's binding to the IReport<int> or IReport<string> type and thus cannot determine the appropriate return type for that call.