• Microsoft LightSwitch – Maintaining a Primary Child Entity

    Posted Dec 27th, 2011 By in LightSwitch With| 3 Comments | Microsoft LightSwitch – Maintaining a Primary Child Entity
    Share on TwitterSubmit to StumbleUponSave on DeliciousDigg ThisSubmit to reddit

    Capturing and storing data is one thing. Turning that data into useful information is something else. The true value of your LightSwitch application will come from how useful the outputs are to your users.

    In this little gem of an article, I demonstrate how to define and maintain a “primary” entity for one to many entity relationships.

    The Scenario

    I have a very intuitive solution that enables me do a lot of customer relationship management (CRM) features, as well as bunch of other stakeholder management stuff (see my Simple Stakeholder Management article as a good reference) .

    Dealing with all these stakeholders can be a chore, especially when maintaining the contact information about each of these stakeholders. Keeping track of all those addresses, phone numbers, and email addresses can be difficult. Even more of a task is to determine what contact information is the most appropriate, or targeted, to use.

    To help me deal with all this information, I created a very effective solution that helps me better mitigate all that contact information. In a nutshell, I created a process that lets me define what piece of contact information is the “primary” information to use.  This way I can quickly see what I should use, instead of sifting around the data to see which is the appropriate or correct information to use.

    Still confused? Let me scale this scenario down a bit. Let’s say I have a customer and that customer is a big customer that has a large number of addresses, phone numbers, email addresses. And that customer has a large number of contacts (people), who also have one or more addresses, phone numbers, and so on. If I need to pick up  the phone, or send a letter, what contact information do I use first?

    Here is what I did…

    Who’s on First, What’s on Second…

    This article demonstrates how I implemented this “primary” scenario for customer addresses. The same scenario is easily applied to any kind of one to many relationships that you may have in your system.

    My Address entity looks like this…

    As you can see above, there is a relationship between the Address and a Company. A Company can have one or more Addresses. Also note the IsPrimary property, a Boolean type. This is what is used to define the address as the primary address for the related entity. Also,  there are other relationships here, such as for Company and Contact entities – which is important to remember what is explained later in this article.

    In the application I have a list-detail screen that lists all my customers.  Selecting a customer from the list will display details about the customer, including the contact information such as the as addresses, phone numbers, emails, and people contacts. This is what that screen looks like when it is running…

    Customer Contact Addresses

    The Address tab in  the screen (shown above) displays a list of addresses for the selected customer. One of those addresses, the Home address, is defined as the primary address for the customer.

    To maintain the address entity, and to mitigate the setting of the IsPrimary flag, I created a screen that is used exclusively for adding and editing addresses. The screen was created using the New Data screen template and then crafted so that it could be used to both add a new address entity as well as edit an existing address entity.

    Here is what the address editor screen looks like when it is running…

    Nothing spectacular really, but there is a lot happening under the hood that is important to understand. Before I move and show you some cool code, keep the following in mind about the models and their relationships (and constraints) used for a few of the stakeholders in the application:

    • A Customer can have one or more Addresses
    • A Customer must have one, and only one, Address defined as Primary.
    • A Company can have one or more Addresses
    • A Company must have one, and only one, Address defined as Primary.
    • A Contact can have one or more Addresses (and like the customer above, must have only one primary address).

    I don’t talk about the Company nor Contact entities here, but it follows the same principles, and you’ll see how I craft the Address screen so that it can be used to edit the addresses for all those entities.

    So, to achieve all those above listed constraints, I did this…

    First I created local properties for the screen. Each of these properties are used to define; first, whether or not the address being edited is a new address or an existing address, and second, which entity (a customer, company, or contact) the address is being edited for.

    An additional string property named SourceScreenName was also added. This property is used for mitigating a refresh on the screen that opened the address editor screen (example shown later in the code).

    For what it’s worth, here some of the noteworthy attributes of these screen properties…

    • SourceScreenName = String,  Is Parameter = False, Is Required = True
    • AddressId = Integer, Is Parameter = True, Is Required = False
    • CompanyId = Integer, Is Parameter = True, Is Required = False
    • CustomerId =  Integer, Is Parameter = True, Is Required = False
    • ContactId =  Integer, Is Parameter = True, Is Required = False
    • AddressEntityParentName =  String, Is Parameter = False, Is Required = False

    Cool Coding Stuff…

    First thing to do on this screen is to determine why it is being opened. This is all controlled via the values of the properties being passed as parameters to the screen.

    For example, the following code is used when clicking the Edit button on the Addresses grid toolbar in the Customer list-detail screen shown earlier…

     partial void AddressesEditSelected_Execute()
            {
                EditAddress();
            }
    
            private void EditAddress()
            {
                int addressId = this.Addresses.SelectedItem.Id;
                Application.ShowCreateOrEditAddress(this.Name, addressId, null, null, null);
            }

    The above EditAddress method is used  to open the address editor screen, using values for the screen parameters.

    Here is how the address editor screen controls this using those same parameters…

     partial void CreateOrEditAddress_InitializeDataWorkspace(List<IDataService> saveChangesTo)
            {
                if (AddressId.HasValue)
                {
                    Address address = DataWorkspace.ApplicationData.Addresses.Where(a => (a.Id == AddressId)).FirstOrDefault();
                    this.AddressProperty = address;
                }
                else
                {
                    this.AddressProperty = new Address();
                }
    
                if (CompanyId.HasValue)
                {
                    Company company = DataWorkspace.ApplicationData.Companies.Where(c => (c.Id == CompanyId)).FirstOrDefault();
                    this.AddressProperty.Company = company;
                    if (company != null)
                        AddressEntityParentName = company.CompanyName;
                }
                if (CustomerId.HasValue)
                {
                    Customer customer = DataWorkspace.ApplicationData.Customers.Where(c => (c.Id == CustomerId)).FirstOrDefault();
                    this.AddressProperty.Customer = customer;
                    if (customer != null)
                        AddressEntityParentName = customer.CustomerName;
                }
                if (ContactId.HasValue)
                {
                    Contact contact = DataWorkspace.ApplicationData.Contacts.Where(c => (c.Id == ContactId)).FirstOrDefault();
                    this.AddressProperty.Contact = contact;
                    if (contact != null)
                        AddressEntityParentName = contact.FirstName + " " + contact.LastName;
                }
            }

    Pretty crafty eh? Depending on which property I pass to the screen (as a parameter), the screen will add or edit the appropriate address for the appropriate entity.

    Saving the Primary Address

    With the screen properties, I can then control which values get updated, as well as do some magic to define whether or not the address is the “primary” address for the entity.

    First, I hook into the Saving event of the entity…

    …and the code for that event…

    partial void CreateOrEditAddress_Saving(ref bool handled)
            {
                if (this.AddressProperty.IsPrimary == false)
                {
                    if (HasExistingPrimaryAddress() == false)
                    {
                        string message = "There must be a primary address defined for this entity.";
                        message += Environment.NewLine + Environment.NewLine;
                        message +=  "This address will be the primary address, until another address is added or selected as primary.";
                        this.ShowMessageBox(message);
                        this.AddressProperty.IsPrimary = true;
                    }
                }
                else
                {
                    List<Address> addresses = new List<Address>();
    
                    if (this.AddressProperty.Company != null)
                    {
                        addresses = this.AddressProperty.Company.Addresses.ToList();
                    }
                    if (this.AddressProperty.Customer != null)
                    {
                        addresses = this.AddressProperty.Customer.Addresses.ToList();
                    }
                    if (this.AddressProperty.Contact != null)
                    {
                        addresses = this.AddressProperty.Contact.Addresses.ToList();
                    }
                    ResetAllPrimaryAddress(addresses);
                    this.AddressProperty.IsPrimary = true;
                }
            }

    The above code evaluates if the address being edited is set with the IsPrimary flag as false. If false, then a method is called to check to see if there are any other addresses for the entity. If no other addresses are found, then the address must have its IsPrimary flag set to true. If true, the logic sets all other addresses (if any) IsPrimary flag to false, and then saves the entity.

    Here are the HasExistingPrimaryAddresses and ResetAllPrimaryAddress methods…

     public bool HasExistingPrimaryAddress()
            {
                bool primaryWasFound = false;
    
                List<Address> addresses = new List<Address>();
    
                if (CompanyId.HasValue)
                {
                    Company company = DataWorkspace.ApplicationData.Companies.Where(c => (c.Id == CompanyId)).FirstOrDefault();
                    if (company != null)
                        addresses = company.Addresses.ToList();
                }
                if (CustomerId.HasValue)
                {
                    Customer customer = DataWorkspace.ApplicationData.Customers.Where(c => (c.Id == CustomerId)).FirstOrDefault();
                    if (customer != null)
                        addresses = customer.Addresses.ToList();
                }
                if (ContactId.HasValue)
                {
                    Contact contact = DataWorkspace.ApplicationData.Contacts.Where(c => (c.Id == ContactId)).FirstOrDefault();
                    if (contact != null)
                        addresses = contact.Addresses.ToList();
                }
    
                if (addresses.Count > 1)
                {
                    foreach (Address address in addresses)
                    {
                        if (address.IsPrimary)
                            primaryWasFound = true;
                    }
                }
                return primaryWasFound;
            }
    
            private void ResetAllPrimaryAddress(List<Address> addresses)
            {
                if (addresses.Count > 1)
                {
                    foreach (Address address in addresses)
                    {
                        if (address.IsPrimary)
                          address.IsPrimary = false;
                    }
                }
            }

    Finally, I simply hooked into the Saved event to close the update the addresses collection on the screen the originally opened the address editor, and then close the editor…

            partial void CreateOrEditAddress_Saved()
            {
                IActiveScreen sourceScreen = null;
                switch (SourceScreenName)
                {
                    case "CustomerListDetail":
                        if (this.AddressProperty.Customer != null)
                        sourceScreen = Application.ActiveScreens.Where(a => a.Screen is CustomersListDetail).FirstOrDefault();
                        break;
                    case "EditCompanyDetail":
                        sourceScreen = Application.ActiveScreens.Where(a => a.Screen is EditCompanyDetail).FirstOrDefault();
                        break;
                }
                // IActiveScreen sourceScreen = Application.ActiveScreens.Where(a => a.Screen.Name == SourceScreenName).FirstOrDefault();
    
                if (sourceScreen != null)
                {
                    sourceScreen.Screen.Details.Dispatcher.BeginInvoke(() =>
                    {
                        switch (SourceScreenName)
                        {
                            case "CustomerListDetail":
                                if (this.AddressProperty.Customer != null)
                                    ((CustomersListDetail)sourceScreen.Screen).Addresses.Refresh();
    
                                if (this.AddressProperty.Contact != null)
                                    ((CustomersListDetail)sourceScreen.Screen).ContactAddresses.Refresh();
    
                                break;
    
                            case "EditCompanyDetail":
                                ((EditCompanyDetail)sourceScreen.Screen).Addresses.Refresh();
    
                                break;
                        }
    
                    });
                }
                this.Close(false);
            }

    Conclusion

    I love the use of local screen properties. There is so much that can be done with relative ease using local screen properties.

    Using the technique above provides for an intuitive method of getting more relative context from your data. I applied this same technique to addresses, phones, emails, other contacts, and etc. Now when I want only the necessary information about a stakeholder, that information is immediately available, instead of searching for it. I’ve also extended this process to include other primary types, such as which address is the main “billing” address.

    Anywho, let me know if you get any use out of this method.

    Cheers!


    • delicious
    • digg
    • reddit

    Paul
    My name is Paul Patterson and I am a software developer who has a keen interest in technology, including; open source, .Net, and anything Interweb. When not crafting some code, I can be found learning something new about photography. As well, I occasionally escape to the "music room" with my guitars to practice a few scales and then jam with my favourite FM radio stations.

Leave a Reply


Comments (3)

Reply
Bhuven » 10. Jan, 2012

Hi Paul. Thank you very much. This explains it brilliantly!!

Reply
PaulSPatterson » 11. Jan, 2012

Thanks for the feedbac Bhuven. Much appreciated.

Paul

Reply
Microsoft Lightswitch Sharing a Unique Key « Tejana » 18. Feb, 2012

[...] model also shows the influence of Paul Patterson’s Maintaining a Primary Child Entity and Simple Stakeholder Management  Take a look at the Party [...]

© Copyright Paul S Patterson - Please, no touchie. :)