Situation: I am using LinqToSql (could be considered irrelevant) for my 'persistence layer' and am trying to work out some architectural concerns I have about where certain questionably business-related logic should go.
The scenario: the user of the application creates a new order. When they do so, a collection of product keys needs to be associated with that order.
My first attempt was to put all of this jazz in an OrderService class. Then, I attempted to incorporate it with my DataContext by using a partial method:
partial void InsertOrder(Order instance)
{
var productKeys = this.ProductKeys
.Where(x => x.DeleteDate == null && x.Order == null && !x.Issued)
.Take(instance.NumberOfProductKeys);
if (productKeys.Count() != instance.NumberOfProductKeys)
throw new NotSupportedException("There needs to be more product keys in the database.");
instance.ProductKeys.AddRange(productKeys);
this.ExecuteDynamicInsert(instance);
}
Disregarding the fact that this doesn't work as intended (the product keys are never actually associated with the order), I feel that this is stripping logic away from my business domain and pushing it to my 'persistence layer'. I also thought about putting it in a OrderService class, but felt that it too just took away logic from the domain entities and resulted in a transaction script. Introducing an Order Factory just circumvents the issue: the data and the logic are once again separated.
So my question is this: to avoid an anemic domain model and to hopefully have order doing something besides being a glorified data structure, is there a proper way of integrating this logic into my domain model?
The best solution that I have come up with yet is to put the logic to the Order class and verify that it was done in the validation hook:
public partial class Order
{
public void AssociateWithProductKeys(WIIIPDataContext context)
{
var productKeys = context.ProductKeys
.Where(x => x.DeleteDate == null && !x.Issued && x.Order == null && x.ProductType == ProductType)
.Take(NumberOfProductKeys);
if (productKeys.Count() != NumberOfProductKeys)
throw new ValidationException("There needs to be more product keys in the database for product type: " + ProductType.Description);
ProductKeys.AddRange(productKeys);
foreach (ProductKey productKey in ProductKeys)
{
productKey.Issued = true;
}
}
partial void OnValidate(ChangeAction action)
{
if (action == ChangeAction.Insert)
{
if (ProductType.Id == 3 && ProductKeys.Count() != 1)
throw new ValidationException("Attempted to associated more than 1 product key with a CD version.");
if (ProductKeys.Count() != NumberOfProductKeys)
throw new ValidationException("Number of product keys doesn't match expected value");
}
}
}
Consuming code would look like so:
// The Unit of Work
using (var context = new MyDataContext())
{
...
order.AssociateWithProductKeys(context);
context.Orders.InsertOnSubmit(order);
context.SubmitChanges();
}
== Update 3/29/2012 ==
I have adopted a command/query pattern when using LinqToSQL (and Web Forms) and no longer use the entities created by LinqToSql DataContext as anything more than a mapping to my data store. All my rules and what not go into into the command objects, making them, in a way, the real core of the application.