When using DTOs Automapper & Nhibernate reflects changes to child DTO collections in the updatable domain object

I'm not very familiar with this design, but I hope to get some guidance.

I have a backend service that sends DTOs to a WPF smart client. On a WPF smart client, the user will modify, delete and modify items and then the changes are sent back (client -> server). For example, I am currently working on a customer information form and the user has the ability to add, remove and modify categories owned by the customer in the datagrid. When the DTO is posted back to the server, I would like to load the domain object associated with the identifier in the DTO and apply the changes made in the DTO to the domain object, including all child collections.

I tried to do something like this in the code below using the UpdateCustomer method . However, I think I am on the sidelines. When the code runs and doesn't end with the {Individual, Company, NGO, Government} list, I get the {Individual, B2B, Company, NGO, Government} list because it did not explicitly remove the B2B entry from the original list.

One option that happened to me was to skip the DTO collection and compare it to the collection from the domain object, and add, remove and update depending on what was changed. However, it seemed very cumbersome.

What do I need to do to apply changes from DTO to child collections in my domiain object?

Thanks a lot for any help it will be fully appreciated

Alex

    public class Customer
        {
            public virtual int Id { get; set; }
            public virtual IList<Category> Categories { get; private set; }
            public virtual string Code { get; set; }
            public virtual string Description { get; set; }

            public Customer()
            {
                Categories = new List<Category>();
            }

            public virtual void AddCategory(string categoryName)
            {
                Categories.Add(new Category(categoryName));
            }
        }

        public class Category
        {

            public virtual string CategoryName { get; private set; }
            public virtual Customer Customer {get;set;}
            public virtual int Id { get; set; }

            protected Category(){}

            public Category(string name)
            {
                CategoryName = name;
            }
        }
    }

    public void SetUpAutoMapper()
    {
        Mapper.CreateMap<Category, CategoryDto>();
        Mapper.CreateMap<Customer, CustomerDto>();
        Mapper.CreateMap<CategoryDto, Category>();
        Mapper.CreateMap<CustomerDto, Customer>();
        Mapper.AssertConfigurationIsValid();
    }
       public void SaveCustomer()
        {
            var customer = new Customer{Code="TESTCUST",Description="TEST CUSTOMER"};
             customer.AddCategory("Individual");
             customer.AddCategory("B2B");
             customer.AddCategory("Healthcare");
             customer.AddCategory("NGO");
             repository.Save(customer);
        }

     public CustomerDto GetCustomer(int customerId)
     { 
        var customer = repository.GetCustomer(customerId);
        var customerDto = Mapper.Map<Customer,CustomerDto>(customer);
        return customerDto;
     }

    public void UpateCustomer(CustomerDto customerToUpdate)
    {
        /*imagine that the dto incoming has had the following operations performed on it
        -----add new category----
        customerToUpdate.Categories.Add(new CategoryDto {CategoryName = "Government"});
        ---update existing category---
        customerToUpdate.Categories[2].CategoryName = "Company";
        ---remove category---
        customerToUpdate.Categories.RemoveAt(1);*/

        var customer = repository.GetCustomer(customerToUpdate.Id);
        /* How in this bit do I ensure that the child collection changes are
        propogated into the underlying customer object retrieved from the database*/
        var customer = Mapper.Map<CustomerDto,Customer>(customerToUpdate);
        repository.Save(customer);
    }

public class CustomerDto
    {
        public int Id { get; set; }
        public string Code { get; set; }
        public string Description { get; set; }
        public List<CategoryDto> Categories { get; set; }
    }

    public class CategoryDto
    {
        public int Id { get; set; }
        public string CategoryName { get; set; }
    }

    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
      <class name="Customer" table="Customer">
        <id name="Id" column="CustomerId">
          <generator class="native"/>
        </id>
        <property name="Code" />
        <property name="Description" />
        <bag name="Categories" table="Categories" cascade="all" inverse="false">
          <key column="FK_CustomerID" />
          <one-to-many class="Category"/>
        </bag>
      </class>

      <class name="Category" table="Categories">
        <id name="Id" column="CategoryId">
          <generator class="native"/>
        </id>
        <many-to-one name="Customer" column="FK_CustomerId" not-null="true" class="Customer"></many-to-one>
        <property name="CategoryName" />
      </class>
    </hibernate-mapping>

      

+3


source to share


2 answers


I recently did something similar, but with EF as the datatier. I don't know to find out if the same approach would work.

The main steps were



  • Make sure target collection is loaded from database and attached to object graph to track changes
  • .ForMember(dest => dest.Categories, opt => opt.UseDestinationValue())

  • Then create a custom IObjectMapper to map IList <> to IList <T> where T: Entity
  • Custom IObject Mapper used some code from http://groups.google.com/group/automapper-users/browse_thread/thread/8c7896fbc3f72514

    foreach (var child in source.ChildCollection)
    { 
        var targetChild = target.ChildCollection.SingleOrDefault(c => c.Equals(child)); // overwrite Equals or replace comparison with an Id comparison
        if (targetChild == null)
        { 
            target.ChildCollection.Add(Mapper.Map<SourceChildType, TargetChildType>(child));
        } 
        else
        { 
            Mapper.Map(child, targetChild);
        } 
    } 
    
          

  • Finally, the last piece of logic to check for all ids in targetCollection exist in sourceCollection and removes them if they don't.

After all, there isn't much code and can be used in other activities.

+5


source


Mapper.CreateMap<Customer, CustomerDto>()
    .ForMember(dest => dest.Categories, opt => opt.MapFrom(src =>src.Categories));

      

or



Mapper.CreateMap<IList<Category>, IList<CategoryDto>>();

      

something like this to tell the automapper to display the list as well.

+1


source







All Articles