Reputation: 8077
Lately, our EF architecture has migrated to this. I'm looking for suggestions on how to best change it (or not).
We have a "Data" project which contains our DataModel.EDMX file along with a Context.cs class which returns our entities object:
public static DataModelEntities getContext() {
return new DataModelEntities();
}
We then create a "Business" project which contains almost a duplicate of the classes that the EF generates. For example, if we have a customer table, we create a CustomerBO class to hold the properties:
public class CustomerBO {
public int customerId {get;set;}
public string firstName {get;set;}
public string lastName {get;set;}
public int orderCount {get;set;}
}
We could in theory use the Customer class created by the EF, but we don't because it usually contains a lot of extra stuff if we end up serializing it and passing it over the wire.
Inside our CustomerBO class, we have methods for retrieval/saving of our data. For example, a getCustomerList method:
public static List<CustomerBO> getCustomers() {
using (var context = Context.getContext()) {
var lst = from c in context.Customers
select c;
// Wrap the EF results to a List<Customer>
return toCustomerList(lst);
}
}
/// Wraps a queryable object to a List<Customer>
private static List<CustomerBO> toCustomerList(IEnumerable<Customer> queryCustomerData) {
var data = from c in queryCustomerData
select new CustomerBO() {
customerId = c.customerId,
firstName = c.firstName,
lastName = c.lastName,
orderCount = c.CustomerOrders.Count()
};
return data.ToList();
}
We always make sure to use the toCustomerList method to return ALL data back to the GUI. It assures us that any custom properties will be set, such as orderCount and doesn't require the developer to remember to add those custom properties to the main EF query.
Overall, the design is solid and has solved numerous problems that we've bumped into over the years. However, there is one particular problem area that bothers me. We call our layer "Business" yet we have some semblance of "query" logic in them. Would there be a better way to architect this to be in-line with an established pattern and still achieve the same result?
Upvotes: 1
Views: 346
Reputation: 73102
Your design looks fairly sound.
One comment i'd make - try not to use static helper methods for data retrieval, and try not to put them in the DTO classes (e.g CustomerBO).
Why not create a Repository in your Data project?
Call it CustomerRepository, and it's responsbility will be to translate business queries into EF queries, and project the results into the DTO's.
Much easier that way - you keep your DTO's nice and thin, they shouldn't have any logic.
Alternatively if you understand deferred execution and the risks of lazy loading, you can return IQueryable<T>
from your Repositories, and return concrete collections (e.g ICollection<T>
) from the Business layer - this is what we do for maximum flexibility. All the logic (query/business) is in our business/service layer.
Nothing wrong with your "Business" layer having query logic - we also do this. We build up queries in our Business layer and pass onto our repository.
The only pattern you could use that comes to mind is CQRS - where you seperate "queries" from "commands".
It's however quite a cumbersome/involved architecture.
I think abstracting your DAL code into a Repository would be sufficient.
HTH.
EDIT
Here is a very simple IQueryable<T>
Repository example:
CustomerRepository
public class CustomerRepository
{
private MyDataContext _ctx;
public CustomerRepository(MyDataContext ctx)
{
this._ctx = ctx;
}
public IQueryable<Order> FindOrders()
{
return _ctx.Orders;
}
}
CustomerDomainService
public class CustomerDomainService
{
public ICollection<Order> GetOrdersForCustomer(int customerId)
{
// You should in reality use interfaces and dependency injection here..
CustomerRepository repo = new CustomerRepository(new MyContext());
var orders = repo.FindOrders().Where(x => x.CustomerId == customerId).ToList();
}
}
As i said, be very careful with IQueryable repositories. Only defer the queries to your domain service, not later. Furthermore, only your domain service should reference the repository.
So your presentations request data from your domain service, which queries against the Repository and returns the result.
Upvotes: 1
Reputation: 1332
I guess you could use EF entities (if you say your versions are almost duplicates), and just make partial classes of them with extra functionality that you need, such as serialization for sending them over the wire. I don't see anything wrong with it. Everytime you update your edmx your partial classes will stay intact.
Upvotes: 0