Dynamic swappable Data Access Layer
I'm writing a data driven WPF client. The client will typically pull data from a WCF service, which queries a SQL db, but I'd like the option to pull the data directly from SQL or other arbitrary data sources.
I've come up with this design and would like to hear your opinion on whether it is the best design.
First, we have some data object we'd like to extract from SQL.
// The Data Object with a single property
public class Customer
{
private string m_Name = string.Empty;
public string Name
{
get { return m_Name; }
set { m_Name = value;}
}
}
Then I plan on using an interface which all data access layers should implement. Suppose one could also use an abstract class. Thoughts?
// The interface with a single method
interface ICustomerFacade
{
List<Customer> GetAll();
}
One can create a SQL implementation.
// Sql Implementation
public class SqlCustomrFacade : ICustomerFacade
{
public List<Customer> GetAll()
{
// Query SQL db and return something useful
// ...
return new List<Customer>();
}
}
We can also create a WCF implementation. The problem with WCF is is that it doesn't use the same data object. It creates its own local version, so we would have to copy the details over somehow. I suppose one could use reflection to copy the values of similar fields across. Thoughts?
// Wcf Implementation
public class WcfCustomrFacade : ICustomerFacade
{
public List<Customer> GetAll()
{
// Get date from the Wcf Service (not defined here)
List<WcfService.Customer> wcfCustomers = wcfService.GetAllCustomers();
// The list we're going to return
List<Customer> customers = new List<Customer>();
// This is horrible
foreach(WcfService.Customer wcfCustomer in wcfCustomers)
{
Customer customer = new Customer();
customer.Name = wcfCustomer.Name;
customers.Add(customer);
}
return customers;
}
}
I also plan on using a factory to decide which facade to use.
// Factory pattern
public class FacadeFa开发者_C百科ctory()
{
public static ICustomerFacade CreateCustomerFacade()
{
// Determine the facade to use
if (ConfigurationManager.AppSettings["DAL"] == "Sql")
return new SqlCustomrFacade();
else
return new WcfCustomrFacade();
}
}
This is how the DAL would typically be used.
// Test application
public class MyApp
{
public static void Main()
{
ICustomerFacade cf = FacadeFactory.CreateCustomerFacade();
cf.GetAll();
}
}
I appreciate your thoughts and time.
You have a very good start on a really flexible approach to your software. You've already hit the main problem with your approach: your data provider contract (ICustomerFacade
) must specify data objects that are used by all implementers. Both your SQL and WCF data providers must return the same data objects.
That part you label as "this is horrible"? It's not actually all that bad. You're iterating twice, yes, but you're doing so to provide a stronger, more flexible software architecture. Performance will not be that bad (unless you're iterating over many many items in the list), and your system will be able to switch between calling a web service and calling SQL server directly (whether or not it's a good idea in general) at will.
One thing that you could do to eliminate the double-iteration would be to have your data contract depend on abstractions of your data objects. They'd return ICustomer
rather than Customer
, for example. Then your SQL Server objects and your WCF data objects could be different objects altogether, as long as they implemented the ICustomer
, etc. interfaces.
Additional suggestions:
- You should consider returning
IList
(or evenIEnumerable
) rather thanList
for your collection-returning methods. - Your factory pattern is a good start, but factories are so 2000s. :) You might want to consider going the full Dependency Injection route; I'd suggest Unity in the Microsoft Enterprise Library.
There are two ways to address the difference between the data objects in the WCF implementation and what is returned from the datastore:
when you create the WCF proxy, ensure that you Reuse all types in referenced assemblies (on the Advanced dialog if using the VS option instead of calling svcutil directly).
have a Clone() or CopyFrom() type method on your DTOs (data objects), so that you can map the objects from the locally generated namespace to the regular project namespace and back again
I would implement option 1 - option 2 is sure to work but is a very slow way to do it. Occassionally when you tell VS to reuse the referenced types when generating the proxies, it will still generate local definitions of the DTOs - in this case you can just go in to the generated Reference.cs
class file and delete all the definitions, and carry on using the versions that are defined in the regular project namespace.
Apart from the WCF issues, you appear to be on the right track.
I personally consider svcutil.exe to be an anti-pattern if you are talking .NET to .NET. It's an extra breaking point for no real value. If you are integrating with an external service or a different platform svcutil.exe is a good choice.
Generally for each service there should be an additional Contracts assembly that contains all the service interfaces and datacontracts for the service. This assembly is referenced by both the service and the client. This way when changes are made they will be reflected in both the client and the server.
http://blog.walteralmeida.com/2010/08/wcf-tips-and-tricks-share-types-between-server-and-client.html
精彩评论