Monday, November 9, 2009

Using a Base Class with LINQ Generated Classes

I am using a custom TreeView control in a form that utilizes the generic syntax to provide greater type-safety and increased functionality .  This control utilizes an abstract class of TreeViewBase.  In order to use the control you must declare you TreeView class like so:

  //a tree control that handles InvestBase objects
  public class ProductTree : TreeViewBase<InvestBase>
  {
    //the sample uses the category's name as the identifier
    public override string GetItemKey(InvestBase item)
    {
      return item.ItemKey;
    }

    //returns subcategories that should be available 
    //through the tree
    public override ICollection<InvestBase>   
    GetChildItems(InvestBase parent)
    {
      return parent.Children;
    }

    //get the parent category, or null if it's a root category
    public override InvestBase GetParentItem(InvestBase item)
    {
      return item.Parent;
    }

    public TreeViewItem SelectedNode
    {
      get { return this.TryFindNode(this.SelectedItem); }
    }
  }

I did this in my MainWindow.xaml.cs file.  Notice the InvestBase class that I have used for the generic .  The next step was to declare the base class.  Below is the declaration of the InvestBase base class.  Notice that I wanted to expose several of the LINQ defined properties and even an EntitySet collection using virtual properties, as well as defining some basic functionality that could be overriden:


  public abstract class InvestBase
  {
    #region Basic Properties
    public virtual System.Guid KeyID { get; set; }
    public virtual string Name { get; set; }
    public virtual EntitySet<CGChart> CGCharts { get; set;}
    public virtual string Notes { get; set; }
    public virtual string Description { get; set; }
    public virtual bool Active { get; set; }
    public virtual string ObjectType
    {
      get { return this.GetType().ToString(); }
    }
    #endregion

    #region Charting
    /// List the available Charts and keep track of the 
    /// last one selected;
    protected CGChart _SelectedChart;

    public virtual List<CGChart> ChartList
    {
      get { return CGCharts.ToList();}
    }

    public virtual CGChart SelectedChart
    {
      get 
      {
        if(_SelectedChart==null && CGCharts.Count>0)
          _SelectedChart = CGCharts[0];
        return _SelectedChart;
      }
      set { _SelectedChart = value;}
    }
    #endregion

    #region TreeViewBase Members
    public virtual IEnumerable<InvestBase> TreeRoot
    {
      get
      {
        List<InvestBase> treeList = new List<InvestBase>();
        treeList.Add(this);
        return treeList;
      }
    }

    public virtual string ItemKey
    {
      get { return KeyID.ToString(); }
    }

    public virtual string NodeLabel
    {
      get { return Name; }
    }

    public abstract string ImagePath {get;}
    public abstract ICollection<InvestBase> Children { get; }
    public virtual InvestBase Parent { get; set; }
    #endregion
  }

Next I placed this base class in a partial class declaration for the three classes that would be in my tree control.  Below I show CGManager and just the header for the CGProduct and CGStrategy classes because these are similar:

  public partial class CGManager: InvestBase
  {
    ...

    #region TreeViewBase Members
    public override string ImagePath
    {
      get { return "/Images/manager.png"; }
    }

    public override ICollection<InvestBase> Children
    {
      get
      {
        List<InvestBase> strats = new List<InvestBase>();
        foreach (var s in _CGStrategies)
        {
          strats.Add(s);
        }
        List<InvestBase> prods = new List<InvestBase>();
        foreach (var p in CGProducts)
        {
          if (p.CGStrategy == null) prods.Add(p);
        }
        strats.AddRange(prods);
        return strats;
      }
    }

    #endregion
  }

  public partial class CGStrategy : InvestBase
  {
    ...
  }
 
  public partial class CGProduct : InvestBase
  {
    ...
  }

This all compiles just fine since the automatically generated LINQ classes (CGManager, CGStrategy and CGProduct) are also generated as partial class definitions.  The problems start with my virtual properties since C# does not associate these with the base class’ default functionality. For example, the ItemKey virtual method tries to pass back the KeyID property, but since the “override” keyword is never found in the LINQ KeyID property, the compiler assumes we want the InvestBase.KeyID which is null!  


Now you can’t add or change any of the code in the designer generated file where the LINQ classes are declared.  If you try, it will just get over-written.  What I found is that you can set an “Inheritance Modifier” in the *.dbml file for properties and even for associations.  For properties:




And for associations:



NOTE: Make sure to “Clean” your project before you re-build it.  Sometimes this may cause the *.designer.cs file to completely disappear from your project!  Don’t panic.  Just rename the associated *.dbml file to something else (I just tack a “_OLD” onto the end) and the designer file will magically re-appear.  Rename the stupid *.dmbl file back and all will be right with the world and you should be able to build your project.


Once you set the “override” inheritance modifiers on all your virtual properties, the compiler will now find the LINQ property instead of the base class’ one.

No comments:

Post a Comment