I've created a simplified prototype solution as a sort of proof of concept before starting a larger program.Here is the test data used to build the objects:create table blah.dbo.tb_sandpitCreateMultipleLinkedObjects(    CustomerName varchar(50),    ItemCode varchar(50));insert into blah.dbo.tb_sandpitCreateMultipleLinkedObjectsvalues     ('jq','bat'),    ('jq','blackRubber'),    ('jq','redRubber'),    ('mt','redRubberX'),    ('mt','redRubberY'),    ('mt','redRubberZ'),    ('mt','blackRubberA'),    ('mt','blackRubberB'),    ('mt','bladeFast'),    ('mt','bladeVeryFast'),    ('mt','bladeTooFast');It has two concrete classes representing customers and items that the customers have bought. There are a few customers but potentially many items.The only use case I have to address is:An administrator identifies a user, then the system will display the items bought by that user. The system does not need to send any information back to SQL server.Here are the two concrete classes:class: Customer using System;using System.Collections.Generic;namespace SandpitCreateMultipleLinkedObjects{  class Customer  {    //links    List<Item> items;    Item i;    //property    public String Name { get; private set; }    //constructor    public Customer(String name)    {      items = new List<Item>();      Name = name;    }    //method to add link to item bought by customer    public void addItem(Item i)    {      items.Add(i);    }  }}class: Item using System;namespace SandpitCreateMultipleLinkedObjects{  class Item  {    //name property    public String Name { get; private set; }    public Item(String name)    {      this.Name = name;    }  }}I have an orchestrating/coordinating class that ultimately will talk to the user interface:using System;using System.Data;using System.Data.SqlClient;using System.Configuration;using System.Collections.Generic;using System.Collections.ObjectModel;namespace SandpitCreateMultipleLinkedObjects{  class AppCoordinator  {    Dictionary<String,Customer> customers;    private string connString;    private const string targetTableName = @blah.dbo.tb_sandpitCreateMultipleLinkedObjects;    private DataTable dt;    public AppCoordinator()    {      //this will contain links to the customers      customers = new Dictionary<string,Customer>();      //fill the main datatable      this.dt = fillDataTable();      //get a distinct view of the customers      //instantiate the customer objects      DataView view = new DataView(dt);      DataTable distinctValues = view.ToTable(true,new String[] { CustomerName });      foreach(DataRow row in distinctValues.Rows)      {        String custName = row[CustomerName].ToString();        customers.Add(custName,new Customer(custName));      }      //then the instantiate the item objects      foreach(DataRow row in dt.Rows)      {        String custName = row[CustomerName].ToString();        Customer linkedCustomer;        customers.TryGetValue(custName,out linkedCustomer);        String itemName = row[ItemCode].ToString();        Item i = new Item(itemName);        linkedCustomer.addItem(i);      }    }    //gets data from the server    private DataTable fillDataTable()    {      this.connString = ConfigurationManager.ConnectionStrings[foo].ConnectionString;      SqlConnection conn = null;      SqlDataAdapter ad = null;      DataSet ds = null;      DataTable returnDt = null;      try      {        conn = new SqlConnection(this.connString);        ad = new SqlDataAdapter(SELECT * FROM  + targetTableName,conn);        ds = new DataSet();        ad.Fill(ds,AvailableReports);        returnDt = ds.Tables[AvailableReports];      }      catch(SqlException ex)      {        Console.WriteLine(ex.ToString());      }      finally      {        if(conn != null)        {          conn.Close();          conn.Dispose();        }      }      return returnDt;    }    //a read-only copy of the dictionary    //read-only for hiding purposes    public ReadOnlyDictionary<String,Customer> getCustomers()    {      return new ReadOnlyDictionary<String,Customer>(customers);    }  }}Here is Main which resides in Program:using System;using System.Collections.Generic;using System.Collections.ObjectModel;namespace SandpitCreateMultipleLinkedObjects{  class Program  {    private AppCoordinator appCoord;    static void Main(string[] args)    {      Program p = new Program();      p.appCoord = new AppCoordinator();      ReadOnlyDictionary<String,Customer> dics = p.appCoord.getCustomers();      Console.WriteLine({0},dics.Count);      foreach(String aName in dics.Keys)      {        Customer c;        dics.TryGetValue(aName,out c);        Console.WriteLine({0} has this many items: {1},c.Name, c.getItems().Count);      }      Console.WriteLine();      Console.WriteLine(press [enter] to exit);      Console.Read();    }  }}It works ok. I am new to OOP so any tips and pointers on this code would be appreciated.
Creating multiple objects from SQL server extract
c#;object oriented;sql server
Customer and Item classesWell, to start with, Customer.i isn't used and I don't see how it could ever be. So that should just go away.Your naming style is off for C#; you should be using PascalCase instead of camelCase for public methods (eg., AddItem).Customer.AddItem and Customer.GetItems don't add much value; they're OK as-is, and are actually a reasonable encapsulation - but they're kind of overkill here. (as an aside, your Customer class doesn't include GetItems, but I inferred it from your Main). This comment could really be directed to the whole structure, actually - since you really just need a Dictionary<string, List<string>> to solve the problem. Not to say the structure can't be useful, but I would consider it overkill unless there's some future plans being made here.Program classYour Program class sets a private AppCoordinator member, but never uses it outside of Main; it's odd to have a class instantiate itself like this. In this case, you can just use the AppCoordinator as a local.Console.WriteLine will take an object and call .ToString on it for you; no reason to use the format overload.  Console.WriteLine(dics.Count);You can foreach over the Dictionary directly, getting a KeyValuePair<string, Customer>. Or you can foreach over Dictionary.Values and just get the Customer. Either would be preferable to going over the Keys just to get to the Value (and you don't check the return of TryGetValue). I'd also change the name of your dics variable since the trailing s implies that it's multiple dictionaries (FWIW, I'd probably go with just d just so I didn't have to ever read a potentially offensive word over the phone; but that's just me!)  foreach(Customer customer in dic.Values)  {     Console.WriteLine({0} has this many items: {1}, customer.Name, customer.GetItems().Count);  }Console.Read will exit at any key; you want to change your message or change that to Console.ReadLine to require an Enter.AppCoordinatorYour AppCoordinator class is a major code smell; it's a (no offense) terrible name that really doesn't describe what it's doing, and is likely to become a god object. I would rename that to CustomerRepository or CustomerDataAccess or something.Since this is your largest class, the meat of my comments are here:prefer smallest scope possible for variables: targetTableName and dt should all be locals; technically so should connString, but we'd generally want the ability to set that one from a constructor or somethingdon't do work you don't have to: SqlDataAdapter.Fill will fill just a DataTable, don't pass it a DataSet just to pull out the DataTableprefer using over explicit Dispose: it reads better, and is much easier to get correctSome will quibble, but I don't like the assignment to a local just to return at the endDon't catch exceptions that you can't do anything about; if you do want to log, then throw at the end (otherwise, you just returned an empty datatable which someone is going to read and think there's been no sales)Pet peeve, but I prefer explicit cast to calling ToString; that makes you explicitly deal with DbNullYou generally don't want to be doing data access or other real work in the constructor; it opens you up to possible exceptions, makes inheritance harder, and is generally unexpected. Move all of that to the GetCustomers method.I'm not sure that it's a requirement, but your existing code does end up caching the database results; you can do that lazily instead.Maybe not now, but you'll definitely want to consider finding a mini (or full) ORM to cut out the repetitive ADO.NET code.Using DataTables isn't the end of the world, but not really standard at this point. I'd switch those to a DataReader instead since you don't really need the extra functiuonality of the DataTable.You do use it to build a distinct Customer list, but that can be done with a ContainsKey on your Dictionary instead.So, I'd suggest it'd end up looking something like (it's been awhile since I've written straight ADO.NET, so there may be some minor issues here): class CustomerDataAccess  {     private string readonly connectionString;     private ReadOnlyDictionary<string, Customer> readOnlyCustomers;     public CustomerDataAccess()         : this(ConfigurationManager.ConnectionStrings[foo].ConnectionString)     {     }     public CustomerDataAccess(string connectionString)     {         this.connectionString = connectionString;     }     public ReadOnlyDictionary<String,Customer> GetCustomers()     {         if (this.readOnlyCustomers == null)          {            Dictionary<String, Customer> customers = this.GetCustomersInternal();            this.readOnlyCustomers = new ReadOnlyDictionary(customers);         }         return this.readOnlyCustomers;     }     private Dictionary<String, Customer> GetCustomersInternal()     {         const string targetTableName = @blah.dbo.tb_sandpitCreateMultipleLinkedObjects;         try          {             using (SqlConnection conn = new SqlConnection(this.connectionString))             using (SqlCommand cmd = new SqlCommand(SELECT * FROM  + targetTableName, conn))             {                 conn.Open();                 Dictionary<string,Customer> customers = new Dictionary<string,Customer>();                 using (SqlDataReader dr = cmd.ExecuteReader())                 {                     string customerName = dr.GetString(CustomerName);                     Customer customer;                     if (!customers.TryGetValue(customerName, out customer))                     {                         customer = new Customer(customerName);                         customers.Add(customer.Name, customer);                     }                     string itemCode = dr.GetString(ItemCode);                     Item item = new Item(itemCode);                     customer.AddItem(item);                 }                 this.readOnlyCustomers = new ReadOnlyDictionary(customers);             }         }         catch (SqlException ex)         {            Console.WriteLine(ex);            throw;         }     } }