3

I have a common Repository with Add, Update, Delete. We'll name it CustomerRepository.

I have a entity (POCO) named Customer, which is an aggregate root, with Addresses.

public class Customer
{
     public Address Addresses { get; set; }
}

I am in a detached entity framework 5 scenario.

Now, let's say that after getting the customer, I choose to delete a client address. I submit the Customer aggregate root to the repository, by the Update method.

How can I save the modifications made on the addresses ?

  1. If the address id is 0, I can suppose that the address is new.
  2. For the rest of the address, I can chose to attach all the addresses, and mark it as updated no matter what.
  3. For deleted addresses I can see no workaround...

We could say this solution is incomplete and inefficient.

So how the updates of aggregate root childs should be done ?

Do I have to complete the CustomerRepository with methods like AddAddress, UpdateAddress, DeleteAddress ?

It seems like it would kind of break the pattern though...

Do I put a Persistence state on each POCO:

public enum PersistanceState
{
     Unchanged,
     New,
     Updated,
     Deleted
}

And then have only one method in my CustomerRepository, Save ?

In this case it seems that I am reinventing the Entity "Non-POCO" objects, and adding data access related attribute to a business object...

Roubachof
  • 3,351
  • 3
  • 23
  • 34
  • Are these EF entities? Correct me if I'm wrong, but if you load your aggregate root (`Customer`) and access its navigation properties (i.e. `Addresses`), shouldn't these be lazy loaded, attached to the context and thus, be saved/deleted automatically when calling `SaveChanges`? – khellang Dec 13 '12 at 14:10
  • In my case, Customer is detached, and then attached, not loaded from the DbContext. Poco entities are totally independant from EF, there could be no lazy loading. – Roubachof Dec 13 '12 at 14:26
  • OK, so what you are saying is that in your repository, you load the customer (ENTITY POCO) and map it to a *DOMAIN POCO*, `Customer`? Then you do some operations on it before you want to map it back to an ENTITY POCO and save it? – khellang Dec 13 '12 at 14:31
  • 1
    nope, the POCO is the entity, thanks to the POCO T4 template. – Roubachof Dec 13 '12 at 14:33
  • Jeez... Are you working with proxies (loaded from `DbContext`) or not? – khellang Dec 13 '12 at 14:34
  • The persistence state option is elaborated by Lerman and Miller in their book `DbContext`. It is positioned as a solution for an otherwise clumsy way of setting the states of objects in a graph. I recommend you read it (if you didn't already). The implication is that building a generic solution inevitably breaks persistence ignorance. Usually that is not as bas as it sounds. – Gert Arnold Dec 13 '12 at 21:08
  • It seems that there is no other way that the State property approach... however it looks odd, since many people are using detached pocos, it's weird that i don't see that many references on the web regarding this issue. – Roubachof Dec 18 '12 at 16:04

2 Answers2

2

First, you should keep your repository with Add, Update, and Delete methods, although I personally prefer Add, indexer set, and Remove so that the repository looks like an in memory collection to the application code.

Secondly, the repository should be responsible for tracking persistence states. I don't even clutter up my domain objects with

object ID { get; }

like some people do. Instead, my repositories look like this:

public class ConcreteRepository : List<AggregateRootDataModel>, IAggregateRootRepository

The AggregateRootDataModel class is what I use to track the IDs of my in-memory objects as well as track any persistence information. In your case, I would put a property of

List<AddressDataModel> Addresses { get; }

on my CustomerDataModel class which would also hold the Customer domain object as well as the database ID for the customer. Then, when a customer is updated, I would have code like:

public class ConcreteRepository : List<AggregateRootDataModel>, IAggregateRootRepository
{
    public Customer this[int index]
    {
        set
        {
            //Lookup the data model
            AggregateRootDataModel model = (from AggregateRootDataModel dm in this
                                           where dm.Customer == value
                                           select dm).SingleOrDefault();
            //Inside the setter for this property, run your comparison 
            //and mark addresses as needing to be added, updated, or deleted.
            model.Customer = value;
            SaveModel(model); //Run your EF code to save the model back to the database.
        }
    }
}

The main caveat with this approach is that your Domain Model must be a reference type and you shouldn't be overriding GetHashCode(). The main reason for this is that when you perform the lookup for the matching data model, the hash code can't be dependent upon the values of any changeable properties because it needs to remain the same even if the application code has modified the values of properties on the instance of the domain model. Using this approach, the application code becomes:

IAggregateRootRepository rep = new ConcreteRepository([arguments that load the repository from the db]);
Customer customer = rep[0]; //or however you choose to select your Customer.
customer.Addresses = newAddresses;  //change the addresses
rep[0] = customer;
Aaron Hawkins
  • 2,611
  • 1
  • 20
  • 24
  • In an n-tier scenario, the concrete repository is your data tier and would be in your DAL project. The Customer object is part of your application tier and would be in your application project. The code that calls your Customer object is also part of your application tier. That being said, you get more benefits out of onion architecture with DDD. You also have more flexibility for physical deployment environments. The whole idea of onion architecture is that your business logic (Customer object) is dependent upon nothing. This way, the Customer object is independent of deployment scenario. – Aaron Hawkins Dec 18 '12 at 14:09
  • [Onion Architecture](http://www.develop.com/onionarchitecture) Here is a good starting post on Onion architecture. You may also link from this post to Jeffrey Palermo's blog who originally coined the term. – Aaron Hawkins Dec 18 '12 at 14:19
  • But you're using object reference to compare Entities, or in a n-itier, the object is serialized/deserialized, the entity loose its reference when travelling. You also have to be in a stateful scenario. – Roubachof Dec 18 '12 at 16:01
  • You shouldn't be serializing/deserializing domain entities. If your physical architecture means that the presentation layer resides on a different machine than your application layer, you should be using DTOs and service calls to communicate your presentation concerns back to the application layer. In this instance, each service call deals with the state of the object during that service call. For example, if you have a service method SaveCustomer(CustomerDTO), that service method will then see if the domain entity exists, and either update the existing entity or add a new one. – Aaron Hawkins Dec 18 '12 at 16:34
  • To be clear, each context is a stateful scenario. Even if you're writing the presentation layer on a web server, each postback is its own state. In a web environment, you cannot get around having to load the repository, display the object(s) to the user and having to reload the repository whenever the user makes a change. You can, however, track WHAT the user is changing so you only need to load the necessary objects on each post back. This way, on the postback, you are only loading the repository with the objects the user made changes to or asked to perform some action on. – Aaron Hawkins Dec 18 '12 at 16:49
1

The easy way is using Self Tracking entities What is the purpose of self tracking entities? (I don't like it, because tracking is different responsability).

The hard way, you take the original collection and you compare :-/

Update relationships when saving changes of EF4 POCO objects

Other way may be, event tracking ?

Community
  • 1
  • 1
rad
  • 1,857
  • 1
  • 20
  • 30
  • 2
    I won't be using STE as it will defeat my POCO approach, moreover it is now no more recommended by microsoft (http://msdn.microsoft.com/en-us/data/jj613668). It looks like I'll have to load my aggregate root and compare it manually to the detached one... – Roubachof Dec 13 '12 at 16:04
  • I do somethings like that in the past : http://stackoverflow.com/questions/6125871/ef-4-1-removing-child-object-from-collection-does-not-delete-it-why – rad Dec 13 '12 at 16:50