The Customer -> Contacts issue again...

The Customer -> Contacts issue again...

Old forum URL: forums.lhotka.net/forums/t/2522.aspx


Gareth posted on Wednesday, March 14, 2007

This has been covered a little before but I've still not got my head round it yet. A simple issue but I've not quite worked it out. I hope someone can point me in the right direction.

I have a Customers table and a Contacts table in my database. There is a 1-to-Many relationship between them. The database is modelled as shown below:

Customers Table
---------------
Id           (int identity)
Name         (varchar(50))
LastChanged  (timestamp)

Contacts Table
--------------
Id           (int identity)
CustomerId   (int)
Name         (varchar(50))
Phone        (varchar(50))
Mobile       (varchar(50))
Email        (varchar(50))
LastChanged  (timestamp)


In my C# project I have modelled this as an Editable parent Customer object. The Customer contains a collection of Contact objects. This collection is called CustomerContacts. I then have an Editable child Contact object.
Additional I have readonly CustomerList and CustomerInfo objects.

A skeleton of the model is shown below and shows the Business and Factory method headers that I have implemented.

public class Customer : BusinessBase<Customer>
{
        #region Business Methods

        private CustomerContacts _contacts =
            CustomerContacts.NewCustomerContacts();


        public int Id

        public string Name

        public override bool IsValid

        public override bool IsDirty

        protected override object GetIdValue()

        public Contact NewContact()

        public void AddContact(Contact contact)

        #endregion
       
       
        #region Factory Methods

        public static Customer NewCustomer()

        public static void DeleteCustomer(int id)

        public static Customer GetCustomer(int id)

        public override Customer Save()

        private Customer()

        #endregion
       
    ...
    ...
}

public class CustomerContacts : BusinessListBase<CustomerContacts, Contact>
{
        #region Business Methods

        public void Remove(int id)

        public Contact GetContactById(int id)

        protected override object AddNewCore()

        #endregion

   
        #region Factory Methods

        internal static CustomerContacts NewCustomerContacts()

        internal static CustomerContacts GetCustomerContacts()

        private CustomerContacts()

        #endregion

    ...
    ...
}

public class Contact : BusinessBase<Contact>
{   
        #region Business Methods

        public int Id

        public int CustomerId

        public string Name

        public string Phone

        public string Mobile

        public string Email

        public override bool IsValid

        public override bool IsDirty

        protected override object GetIdValue()

        #endregion

   
        #region Factory Methods

        internal static Contact NewContact()

        internal static Contact GetContact(Csla.Data.SafeDataReader dr)

        private Contact()

        #endregion

    ...
    ...
}

public class CustomerList : ReadOnlyListBase<CustomerList, CustomerInfo>
{
    ...
    ...
}

public class CustomerContactInfo : ReadOnlyBase<CustomerContactInfo>
{
    ...
    ...
}


My problem is this that I can't work out how to add a contact and save it as part of the customer record.
I can create a new customer record and save it and that works okay.
I can create a new contact record but I don't understand how I should be adding it to the customer object in order for the Customer.Save() method to save the Customer and all the Contacts.

I have a test stub showing what I want to achieve.

class Program
{
    static void Main(string[] args)
    {
        if (!PTPrincipal.Login("Gareth", "password"))
            return;

        // The following works okay.
   
        Customer obj = Customer.NewCustomer();
        obj.Name = "Gareth";
        obj.Save();
       
        // Now create a new contact.
       
        Contact con = obj.NewContact();
        con.Name = "Gareth";
        con.Phone = "01279 111 222";
        con.Mobile = "07973 222 333";
        con.Email = "gareth@somewhere.com";

        // How to add the contact to the customer?
   
        obj.AddContact(con);  // Is this correct?

        // Save the customer forcing the new contact
        // to be written to the database too.
   
        obj.Save();

    }
}

Can anyone advise me on if I am going about this the right way and how to add the contact object to the customer object so that it is saved correctly?

Many thanks,
Gareth.



burmajam replied on Wednesday, March 14, 2007

Wrong aproach. CreateChildObject factory method should be internal an called ONLY from within ChildCollection, that has public methods for get, add and delete. Within this "add" method you create your contact and place it in "this" i.e.:

public Address AddAddress(Guid ownerID)
  {
   Address newItem = Address.NewAddress(ownerID);
   this.Add(newItem);
   return newItem;
  }

dshafer replied on Wednesday, March 14, 2007

Gareth,

I'm not sure what your NewContact() and AddContact() methods on your Customer object are used for, but here's what I'd do:

1.  Make a public property on your Customer class that exposes your private field _contacts (We'll call it Contacts)

2. From your application code, you should be able to then do something like this to add a contact (using the same objects you created in your code above):

obj.Contacts.Add(con);

obj.Save();

ajj3085 replied on Wednesday, March 14, 2007

Hey Gareth,

Can contacts be created outside the scope of a customer?  Or can you only create a contact within the context of a customer?  What are the use cases for customers and contacts?

Depending on those answers, you may want to consider a CustomerContact class, which is created via a public factory method that accepts a ContactInfo object (if you are only adding contacts to a customer).  If contacts are only created within a customer, your current approach is close.  It seems you have a way to add a contact to the customer, or you may want to expose the CustomerContacts collection and just let the client use the Add method on the collection.

Gareth replied on Wednesday, March 14, 2007

Thanks for all your replies so far, I'll be re-working the code tonight.

In response in my model contacts only can belong to a customer, they cannot be created outside that scope.

Regards,
Gareth.

Gareth replied on Tuesday, April 10, 2007

Ok, I've now got back to this at long last. I've looked at the previous replies and also the templates and I now have the following classes:

Customer (Editable Root)
CustomerInfo (Read Only Child)
CustomerList (Read Only Collection of CustomerInfo objects)

CustomerContact (Editable Child)
CustomerContactList (Editable Child Collection of CustomerContact objects)

I've implemented everything as I think but I'm not 100% sure I've got the CustomerContact and CustomerContactList correct, specifically the Insert and Update routines (and I've not done the DeleteSelf yet either). The reason I believe it's not correct is because I'm not using the parent object reference in both of those routines and I can't find any reference to exactly what I should be doing with the reference.
Also neither of these two routines get called anyway because the obj.Contacts.Add(con) method called in the test stub never sets the dirty flag.

Could someone review the code for me and let me know what they think? Below are implementations of my Customer class, CustomerContact, CustomerContactList and also my test stub.

Thanks very much for all your help.
Gareth.

##########################################################################
# CUSTOMER CLASS
##########################################################################

using System;
using System.Data;
using System.Data.SqlClient;
using Csla;
using Csla.Data;
using Csla.Validation;

namespace NetShare.Library
{
    [Serializable()]
    public class Customer : BusinessBase<Customer>
    {
        #region Business Methods

        private int _id;
        private bool _isVip;
        private string _name = string.Empty;
        private byte[] _timestamp = new byteMusic [8];

        private CustomerContactList _contacts =
            CustomerContactList.NewCustomerContactList();

        [System.ComponentModel.DataObjectField(true, true)]
        public int Id
        {
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            get
            {
                CanReadProperty(true);
                return _id;
            }
        }

        public bool IsVip
        {
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            get
            {
                CanReadProperty(true);
                return _isVip;
            }
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            set
            {
                CanWriteProperty(true);
                if (_isVip != value)
                {
                    _isVip = value;
                    PropertyHasChanged();
                }
            }
        }

        public string Name
        {
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            get
            {
                CanReadProperty(true);
                return _name;
            }
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            set
            {
                CanWriteProperty(true);
                if (value == null) value = string.Empty;
                if (_name != value)
                {
                    _name = value;
                    PropertyHasChanged();
                }
            }
        }

        public CustomerContactList Contacts
        {
            get { return _contacts; }
        }

        public CustomerContact NewContact()
        {
            CustomerContact contact = CustomerContact.NewCustomerContact();
            contact.CustomerId = Id;
            return contact;
        }

        public override bool IsValid
        {
            get { return base.IsValid; }
        }
        public override bool IsDirty
        {
            get { return base.IsDirty; }
        }

        protected override object GetIdValue()
        {
            return _id;
        }

        #endregion

        #region Validation Rules

        protected override void AddBusinessRules()
        {
            ValidationRules.AddRule(new RuleHandler(CommonRules.StringRequired), "Name");
            ValidationRules.AddRule(new RuleHandler(CommonRules.StringMaxLength),
                new CommonRules.MaxLengthRuleArgs("Name", 50));
        }

        #endregion

        #region Authorization Rules

        protected override void AddAuthorizationRules()
        {
            // add AuthorizationRules here
            AuthorizationRules.AllowWrite("Name", Properties.Resources.RoleManager);
        }

        public static bool CanAddObject()
        {
            return Csla.ApplicationContext.User.IsInRole(Properties.Resources.RoleManager);
        }

        public static bool CanGetObject()
        {
            return true;
        }

        public static bool CanDeleteObject()
        {
            bool result = false;
            if (Csla.ApplicationContext.User.IsInRole(Properties.Resources.RoleManager))
                result = true;
            if (Csla.ApplicationContext.User.IsInRole(Properties.Resources.RoleAdmin))
                result = true;
            return result;
        }

        public static bool CanEditObject()
        {
            return Csla.ApplicationContext.User.IsInRole(Properties.Resources.RoleManager);
        }

        #endregion

        #region Factory Methods

        public static Customer NewCustomer()
        {
            if (!CanAddObject())
                throw new System.Security.SecurityException(
                  "User not authorised to add a customer");
            return DataPortal.Create<Customer>();
        }

        public static void DeleteCustomer(int id)
        {
            if (!CanDeleteObject())
                throw new System.Security.SecurityException(
                  "User not authorised to remove a customer");
            DataPortal.Delete(new Criteria(id));
        }

        public static Customer GetCustomer(int id)
        {
            if (!CanGetObject())
                throw new System.Security.SecurityException(
                  "User not authorised to view a customer");
            return DataPortal.Fetch<Customer>(new Criteria(id));
        }

        public override Customer Save()
        {
            if (IsDeleted && !CanDeleteObject())
                throw new System.Security.SecurityException(
                  "User not authorised to remove a customer");
            else if (IsNew && !CanAddObject())
                throw new System.Security.SecurityException(
                  "User not authorised to add a customer");
            else if (!CanEditObject())
                throw new System.Security.SecurityException(
                  "User not authorised to update a customer");
            return base.Save();
        }

        private Customer()
        { /* require use of factory methods */ }

        #endregion

        #region Data Access

        [Serializable()]
        private class Criteria
        {
            private int _id;
            public int Id
            {
                get { return _id; }
            }

            public Criteria(int id)
            { _id = id; }
        }

        [RunLocal()]
        protected override void DataPortal_Create()
        {
            // nothing to initialize
            ValidationRules.CheckRules();
        }

        private void DataPortal_Fetch(Criteria criteria)
        {
            using (SqlConnection cn = new SqlConnection(Database.NetShareConnection))
            {
                cn.Open();
                using (SqlCommand cm = cn.CreateCommand())
                {
                    cm.CommandType = CommandType.StoredProcedure;
                    cm.CommandText = "getCustomer";
                    cm.Parameters.AddWithValue("@Id", criteria.Id);

                    using (SafeDataReader dr = new SafeDataReader(cm.ExecuteReader()))
                    {
                        dr.Read();
                        _id = dr.GetInt32("Id");
                        _isVip = dr.GetBoolean("IsVip");
                        _name = dr.GetString("Name");
                        dr.GetBytes("LastChanged", 0, _timestamp, 0, 8);

                        // load child objects
                        dr.NextResult();
                        _contacts = CustomerContactList.GetCustomerContactList(dr);
                    }
                }
            }
        }

        [Transactional(TransactionalTypes.TransactionScope)]
        protected override void DataPortal_Insert()
        {
            using (SqlConnection cn = new SqlConnection(Database.NetShareConnection))
            {
                cn.Open();
                using (SqlCommand cm = cn.CreateCommand())
                {
                    cm.CommandText = "addCustomer";
                    DoInsertUpdate(cm);
                }
            }
            // update child objects
            _contacts.Update(this);
        }

        [Transactional(TransactionalTypes.TransactionScope)]
        protected override void DataPortal_Update()
        {
            if (base.IsDirty)
            {
                using (SqlConnection cn = new SqlConnection(Database.NetShareConnection))
                {
                    cn.Open();
                    using (SqlCommand cm = cn.CreateCommand())
                    {
                        cm.CommandText = "updateCustomer";
                        cm.Parameters.AddWithValue("@lastChanged", _timestamp);
                        DoInsertUpdate(cm);
                    }
                }
            }
            // update child objects
            _contacts.Update(this);
        }

        private void DoInsertUpdate(SqlCommand cm)
        {
            cm.CommandType = CommandType.StoredProcedure;
            cm.Parameters.AddWithValue("@IsVip", _isVip);
            cm.Parameters.AddWithValue("@Name", _name);
           
            SqlParameter param =  new SqlParameter("@NewId", SqlDbType.Int);
            param.Direction = ParameterDirection.Output;
            cm.Parameters.Add(param);
            param = new SqlParameter("@NewLastChanged", SqlDbType.Timestamp);
            param.Direction = ParameterDirection.Output;
            cm.Parameters.Add(param);

            cm.ExecuteNonQuery();

            _id = (int)cm.Parameters["@NewId"].Value;
            _timestamp = (byte[])cm.Parameters["@NewLastChanged"].Value;
        }

        [Transactional(TransactionalTypes.TransactionScope)]
        protected override void DataPortal_DeleteSelf()
        {
            DataPortal_Delete(new Criteria(_id));
        }

        [Transactional(TransactionalTypes.TransactionScope)]
        private void DataPortal_Delete(Criteria criteria)
        {
            using (SqlConnection cn = new SqlConnection(Database.NetShareConnection))
            {
                cn.Open();
                using (SqlCommand cm = cn.CreateCommand())
                {
                    cm.CommandType = CommandType.StoredProcedure;
                    cm.CommandText = "deleteCustomer";
                    cm.Parameters.AddWithValue("@Id", criteria.Id);
                    cm.ExecuteNonQuery();
                }
            }
        }

        #endregion

        #region Exists

        public static bool Exists(string id)
        {
            ExistsCommand result;
            result = DataPortal.Execute<ExistsCommand>(new ExistsCommand(id));
            return result.Exists;
        }

        [Serializable()]
        private class ExistsCommand : CommandBase
        {

            private string _id;
            private bool _exists;

            public bool Exists
            {
                get { return _exists; }
            }

            public ExistsCommand(string id)
            {
                _id = id;
            }

            protected override void DataPortal_Execute()
            {
                using (SqlConnection cn = new SqlConnection(Database.NetShareConnection))
                {
                    cn.Open();
                    using (SqlCommand cm = cn.CreateCommand())
                    {
                        cm.CommandType = CommandType.StoredProcedure;
                        cm.CommandText = "existsCustomer";
                        cm.Parameters.AddWithValue("@Id", _id);
                        int count = (int)cm.ExecuteScalar();
                        _exists = (count > 0);
                    }
                }
            }
        }

        #endregion
    }
}

##########################################################################
# CUSTOMERCONTACT CLASS
##########################################################################

using System;
using System.Data;
using System.Data.SqlClient;
using Csla;
using Csla.Data;
using Csla.Validation;

namespace NetShare.Library
{
    [Serializable()]
    public class CustomerContact
        : BusinessBase<CustomerContact>
    {
        #region Business Methods

        private int _id;
        private int _customerId;
        private string _name = string.Empty;
        private string _phone = string.Empty;
        private string _mobile = string.Empty;
        private string _email = string.Empty;
        private byte[] _timestamp = new byteMusic [8];

        [System.ComponentModel.DataObjectField(true, true)]
        public int Id
        {
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            get
            {
                CanReadProperty(true);
                return _id;
            }
        }

        public int CustomerId
        {
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            get
            {
                CanReadProperty(true);
                return _customerId;
            }
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            set
            {
                CanWriteProperty(true);
                if (_customerId != value)
                {
                    _customerId = value;
                    PropertyHasChanged();
                }
            }
        }

        public string Name
        {
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            get
            {
                CanReadProperty(true);
                return _name;
            }
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            set
            {
                CanWriteProperty(true);
                if (value == null) value = string.Empty;
                if (_name != value)
                {
                    _name = value;
                    PropertyHasChanged();
                }
            }
        }

        public string Phone
        {
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            get
            {
                CanReadProperty(true);
                return _phone;
            }
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            set
            {
                CanWriteProperty(true);
                if (value == null) value = string.Empty;
                if (_phone != value)
                {
                    _phone = value;
                    PropertyHasChanged();
                }
            }
        }

        public string Mobile
        {
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            get
            {
                CanReadProperty(true);
                return _mobile;
            }
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            set
            {
                CanWriteProperty();
                if (value == null) value = string.Empty;
                if (_mobile != value)
                {
                    _mobile = value;
                    PropertyHasChanged();
                }
            }
        }

        public string Email
        {
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            get
            {
                CanReadProperty(true);
                return _email;
            }
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            set
            {
                CanWriteProperty();
                if (value == null) value = string.Empty;
                if (_email != value)
                {
                    _email = value;
                    PropertyHasChanged();
                }
            }
        }

        protected override object GetIdValue()
        {
            return _id;
        }

        #endregion

        #region Validation Rules

        protected override void AddBusinessRules()
        {
            ValidationRules.AddRule(new RuleHandler(CommonRules.StringRequired), "Name");
            ValidationRules.AddRule(new RuleHandler(CommonRules.StringMaxLength),
                new CommonRules.MaxLengthRuleArgs("Name", 50));

            ValidationRules.AddRule(new RuleHandler(CommonRules.StringMaxLength),
                new CommonRules.MaxLengthRuleArgs("Phone", 50));
            ValidationRules.AddRule(new RuleHandler(CommonRules.StringMaxLength),
                new CommonRules.MaxLengthRuleArgs("Mobile", 50));
            ValidationRules.AddRule(new RuleHandler(CommonRules.StringMaxLength),
                new CommonRules.MaxLengthRuleArgs("Email", 50));
        }

        #endregion

        #region Authorization Rules

        protected override void AddAuthorizationRules()
        {
            // add AuthorizationRules here
            AuthorizationRules.AllowWrite("Name", Properties.Resources.RoleManager);
            AuthorizationRules.AllowWrite("Phone", Properties.Resources.RoleManager);
            AuthorizationRules.AllowWrite("Mobile", Properties.Resources.RoleManager);
            AuthorizationRules.AllowWrite("Email", Properties.Resources.RoleManager);
        }

        #endregion

        #region Factory Methods

        internal static CustomerContact NewCustomerContact()
        {
            return new CustomerContact();
        }

        internal static CustomerContact GetCustomerContact(SafeDataReader dr)
        {
            return new CustomerContact(dr);
        }

        private CustomerContact()
        {
            MarkAsChild();
        }

        private CustomerContact(SafeDataReader dr)
        {  
            MarkAsChild();
            Fetch(dr);
        }

        #endregion

        #region Data Access

        [RunLocal()]
        protected override void DataPortal_Create()
        {
            // nothing to initialise
            ValidationRules.CheckRules();
        }

        private void Fetch(SafeDataReader dr)
        {
            _id = dr.GetInt32("Id");
            _name = dr.GetString("Name");
            _phone = dr.GetString("Phone");
            _mobile = dr.GetString("Mobile");
            _email = dr.GetString("Email");
            dr.GetBytes("LastChanged", 0, _timestamp, 0, 8);
            MarkOld();
        }
  
        internal void Insert(Customer customer)
        {
            // if we're not dirty then don't update the database
            if (!this.IsDirty) return;

            using (SqlConnection cn = new SqlConnection(Database.NetShareConnection))
            {
                cn.Open();
                using (SqlCommand cm = cn.CreateCommand())
                {

                    cm.CommandText = "addContact";
                    cm.CommandType = CommandType.StoredProcedure;
                    cm.Parameters.AddWithValue("@CustomerId", _customerId);
                    cm.Parameters.AddWithValue("@Name", _name);
                    cm.Parameters.AddWithValue("@Phone", _phone);
                    cm.Parameters.AddWithValue("@Mobile", _mobile);
                    cm.Parameters.AddWithValue("@Email", _email);
                
                 
                    SqlParameter param = new SqlParameter("@NewId", SqlDbType.Int);
                    param.Direction = ParameterDirection.Output;
                    cm.Parameters.Add(param);
                    param = new SqlParameter("@NewLastChanged", SqlDbType.Timestamp);
                    param.Direction = ParameterDirection.Output;
                    cm.Parameters.Add(param);

                    cm.ExecuteNonQuery();

                    _id = (int)cm.Parameters["@NewId"].Value;
                    _timestamp = (byte[])cm.Parameters["@NewLastChanged"].Value;

                    MarkOld();
                }
            }
        }

        internal void Update(Customer customer)
        {
            // if we're not dirty then don't update the database
            if (!this.IsDirty) return;

            using (SqlConnection cn = new SqlConnection(Database.NetShareConnection))
            {
                cn.Open();
                using (SqlCommand cm = cn.CreateCommand())
                {
                    cm.CommandText = "updateContact";
                    cm.CommandType = CommandType.StoredProcedure;
                    cm.Parameters.AddWithValue("@id", _id);
                    cm.Parameters.AddWithValue("@customerId", _customerId);
                    cm.Parameters.AddWithValue("@name", _name);
                    cm.Parameters.AddWithValue("@phone", _phone);
                    cm.Parameters.AddWithValue("@mobile", _mobile);
                    cm.Parameters.AddWithValue("@email", _email);
                    cm.Parameters.AddWithValue("@lastChanged", _timestamp);

                    SqlParameter param = new SqlParameter("@NewLastChanged", SqlDbType.Timestamp);
                    param.Direction = ParameterDirection.Output;
                    cm.Parameters.Add(param);

                    cm.ExecuteNonQuery();

                    _timestamp = (byte[])cm.Parameters["@NewLastChanged"].Value;

                    MarkOld();
                }
            }
        }

        internal void DeleteSelf()
        {
            MarkNew();
        }

        //private void DataPortal_Delete(Criteria critera)
        //{
        //    using (SqlConnection cn = new SqlConnection(Database.NetShareConnection))
        //    {
        //        cn.Open();
        //        using (SqlCommand cm = cn.CreateCommand())
        //        {
        //            cm.CommandType = CommandType.StoredProcedure;
        //            cm.CommandText = "deleteContact";
        //            cm.Parameters.AddWithValue("@id", _id);
        //            cm.ExecuteNonQuery();
        //        }
        //    }
        //}

        #endregion
    }
}

##########################################################################
# CUSTOMERCONTACTLIST CLASS
##########################################################################

using System;
using System.Data;
using System.Data.SqlClient;
using Csla;
using Csla.Data;

namespace NetShare.Library
{
    [Serializable()]
    public class CustomerContactList
        : BusinessListBase<CustomerContactList, CustomerContact>
    {
        #region Business Methods

        public CustomerContact GetItem(int contactId)
        {
            foreach (CustomerContact obj in this)
                if (obj.Id == contactId)
                    return obj;
            return null;
        }

        public void Remove(int contactId)
        {
            foreach (CustomerContact obj in this)
                if (obj.Id == contactId)
                {
                    Remove(obj);
                    break;
                }
        }

        public bool Contains(int contactId)
        {
            foreach (CustomerContact obj in this)
                if (obj.Id == contactId)
                    return true;
            return false;
        }

        public bool ContainsDeleted(int contactId)
        {
            foreach (CustomerContact obj in this)
                if (obj.Id == contactId)
                    return true;
            return false;
        }

        #endregion

        #region Factory Methods

        internal static CustomerContactList NewCustomerContactList()
        {
            return new CustomerContactList();
        }

        internal static CustomerContactList GetCustomerContactList(SafeDataReader dr)
        {
            return new CustomerContactList(dr);
        }

        private CustomerContactList()
        {
            MarkAsChild();
        }

        private CustomerContactList(SafeDataReader dr)
        {
            MarkAsChild();
            Fetch(dr);
        }

        #endregion

        #region Data Access

        private void Fetch(SafeDataReader dr)
        {
            this.RaiseListChangedEvents = false;
            while (dr.Read())
                this.Add(CustomerContact.GetCustomerContact(dr));
            this.RaiseListChangedEvents = true;
        }

        internal void Update(Customer customer)
        {
            this.RaiseListChangedEvents = false;
            // update (thus deleting) any deleted child objects
            foreach (CustomerContact item in DeletedList)
                item.DeleteSelf();
            // now that they are deleted, remove them from memory too
            DeletedList.Clear();

            // add/update any current child objects
            foreach (CustomerContact item in this)
            {
                if (item.IsNew)
                    item.Insert(customer);
                else
                    item.Update(customer);
            }
            this.RaiseListChangedEvents = true;
        }

        #endregion
    }
}

##########################################################################
# TEST STUB
##########################################################################

using System;
using System.Collections.Generic;
using System.Text;
using NetShare.Library;
using NetShare.Library.Security;

namespace TestStub
{
    class Program
    {
        static void Main(string[] args)
        {
            if (!PTPrincipal.Login("Gareth", "password"))
                return;


            Customer obj = Customer.NewCustomer();
            obj.Name = "Gareth";
            obj.IsVip = false;
            obj.Save();

            CustomerContact con = obj.NewContact();
            con.Name = "Gareth";
            con.Phone = "01279 555 666";
            con.Mobile = "07973 222 333";
            con.Email = "gareth@12elm.com";

            obj.Contacts.Add(con);   // <-- doesn't seem to work...
            obj.Save();

            CustomerList cl = CustomerList.GetCustomerList();
            foreach (Customer ci in cl)
            {
                System.Console.WriteLine(string.Format("{0}: {1}", ci.Id, ci.Name));

            }

            Console.ReadLine();
        }
    }
}



Gareth replied on Wednesday, April 11, 2007

After a couple of beers last night, sitting on the train on my way home from work I managed to sort out most of my problems with the code!!
Firstly in the Insert and Update routines I had to specify the parent object Id for the foreign key in order to get it to work correctly. That was so obvious I can't even explain how I missed it. i.e.

        internal void Insert(Customer customer)
        {
          ...
                    cm.Parameters.AddWithValue("@customerId", customer.id);
          ...
        }

        internal void Update(Customer customer)
        {
          ...
                    cm.Parameters.AddWithValue("@customerId", customer.id);
          ...
        }

Secondly the reason those routines we never getting called was because I'd missed of the Customer.Contacts collection IsDirty and IsValid checks. Again, quite obvious...

    public class Customer : BusinessBase<Customer>
    {
        ...

        public override bool IsValid
        {
            get { return base.IsValid && _contacts.IsValid; }
        }
        public override bool IsDirty
        {
            get { return base.IsDirty && _contacts.IsDirty; }
        }

                     
Then everything seemed to start working okay, except that I was getting MSDTC errors. Realising that I'd copied some code directly from the ProjectTracker project I removed the method attributes...

[Transactional(TransactionalTypes.TransactionScope)]

from the code and that sorted that problem. Now everything is workign except for this one problem...

            Customer obj = Customer.NewCustomer();
            obj.Name = "Gareth";
            obj.IsVip = false;
            obj.Save();
               // <-- this doesn't work...

            CustomerContact con = obj.NewContact();
            con.Name = "Gareth";
            con.Phone = "01279 555 666";
            con.Mobile = "07973 222 333";
            con.Email = "gareth@12elm.com";

            obj.Contacts.Add(con);   // <-- this does work...
            obj.Save();

Does anyone have any ideas why the first save statement does not work, but the second one does? The IsDirty flag is being set correctly. I'll trace into it further tonight, maybe after a few more beers again!!

Thanks,
Gareth.




Copyright (c) Marimer LLC