views:

2376

answers:

3

I'm working on my first .NET project (.NET 3.5, ADO.NET and C#). We've built our entity models and are trying to build a clean business objects layer.

We've got our basic entity model and we want to add certain business-level semantics to the default data accessors (navigation properties, etc).

For example, let's assume that we have a many-to-many relationship between Person and BankAccounts. Let's assume that at the business layer we would like to add the ability to freeze an account. We now want the ability to navigate from a Person to:

  • all their bank accounts,
  • their non-frozen bank accounts, and
  • their frozen bank accounts.

Naturally we'd like to make the nominal case the default: If I navigate Person.BankAccounts() I'd like it to return their non-frozen accounts. I could add the navigation properties Person.FrozenBankAccounts() and Person.AllBankAccounts().

Both approaches we've come up with seem to have a fair amount of code smell.

  1. We can't find a way to override the entity model's methods. So, leave Person.BankAccounts() as the accessor that returns all bank accounts. Then we add a Person.FrozenBankAccounts() and a Person.NonFrozenBankAccounts().
  2. Add another explicit layer to the code base that wraps all accesses to BankAccounts.

With approach 1, the problem is that the nominal business case (accessing unfrozen bank accounts) is the most unintuitive method name of the lot.

With approach 2, when we subclass the objects from the entity model layer we have to override every method to ensure that it won't return objects from the underlying layer. So we create a BL_Person which has a BankAccounts() method that returns a collection of BL_BankAccount objects. But in this case, all that code seems a little silly.

Is there a better approach than the two we've considered? If there isn't a better approach, which of the two I've outlined seem like the better solution (given that we've got something like 50+ classes we need to work with)?

Note: While doing a web search, I did find an open letter to Microsoft titled ADO .NET Entity Framework Vote of No Confidence that seems to imply that there isn't a good way to add in a clear separation of concerns.

A: 

Ditch EF in favor of NHibernate.

The NHibernate way is: you create business objects, and you tell NHibernate how these business objects are to be saved to the database. The business objects themselves have no knowledge of how they are saved or loaded, or that NHibernate is being used. This is called "Persistence Ignorance". Plus, you can tell NHibernate to save and load your business objects almost any way you like. It has nice support for the scenario you describe.

Stop writing data-access layers and stop code-genning them. Use a real man's ORM.

Justice
+1  A: 

I don't have experience with LINQ to Entities but your question rang a bell. In my last project I had pretty much the same problems with another ORM. Instead of letting clients of the business object layer use the ORM generated classes directly or duplicating all classes and implement a ton of forwarding functions, I've defined interfaces. The clients of your business object layer would only see those interfaces and your Entity classes would implement those interfaces, with the following advantages:

  • In the straight-forward case (no business logic), the development overhead for the interfaces is minimal. For most members you don't need to implement forwarding functions, just "derive" the entity class from the interface and be done with it
  • In the case you are mentioning, in the interface you can have a property BankAccounts and through explicit interface implementation let it forward to the NonFrozenBankAccounts of the implementing entity. You can of course also add any kind of checks you want
  • As an added benefit, you can easily exchange the underlying persistence layer without visibly changing anything for the client code
Andreas Huber
A: 

Hi Bradley,

One way to do this would be like so:

  1. Define your AllBankAccounts property (possibly even make it private).
  2. Define the BankAccounts property in the partial Person class. The property can be expressed in terms of AllBankAccounts using LINQ, i.e. AllBankAccounts.Where(a => !a.IsFrozen)
  3. Define the FrozenBankAccounts property in the partial Person class like step 2.

Hope that helps.

Andrew Peters