This project is read-only.

FeatureTable

Nov 6, 2010 at 6:40 AM

Here is the spifyness.  I tried to inherit from DataTable before, but it is not something to be done lightly.  However, as of VS2010, literally every time you automatically hook up a table it genrates this wonderful auto-code in the designer giving you strong classed DataRow members, and an example of how to set up the table to work with them.  I don't think we want strong typed attributes here, but we can piggyback on that automatically generated design for our own model where a Geometry, Shape, FID, Extent and a few other things are thrown in on top of your standard table, giving you something that is super easy to plug & play with databases, but something that should be a heck of a lot sturdier from the standpoint of coordinating between index mode, vertex mode, and attributes which may or may not be loaded etc. 

Anyway these are just some ideas, so I'm looking for feedback.  The autogenerated code also had some serialization stuff that seemed dependent on the dataset, so I chose to nix it.  Maybe someone more educated in the ways of the wise could give a viewpoint on whether it makes sense to try to support "xml serialziable" on the table itself.  I'm not sure where or how that would apply, but it may only be related to schema, and only important for serializing your dataset.

Ted

// ********************************************************************************************************
// Product Name: DotSpatial.Data.dll
// Description:  The data access libraries for the DotSpatial project.
// ********************************************************************************************************
// The contents of this file are subject to the MIT License (MIT)
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://dotspatial.codeplex.com/license
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either expressed or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Initial Developer of this Original Code is Ted Dunsford. Created 11/5/2010 6:59:48 PM 
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
// |      Name            |    Date     |                                Comments
// |----------------------|-------------|-----------------------------------------------------------------
// ********************************************************************************************************
using System;
using System.ComponentModel;
using System.Data;
using DotSpatial.Topology;
namespace DotSpatial.Data
{
    /// <summary>
    ///Represents the strongly named DataTable class.
    ///</summary>
    public class FeatureTable : TypedTableBase<FeatureRow>
    {
        #region Events

        /// <summary>
        /// Occurs while the row is changing
        /// </summary>
        public event EventHandler<FeatureRowChangeEvent> FeatureRowChanging;

        /// <summary>
        /// Occurs after the row has been changed.
        /// </summary>
        public event EventHandler<FeatureRowChangeEvent> FeatureRowChanged;

        /// <summary>
        /// Occurs while the row is being deleted.
        /// </summary>
        public event EventHandler<FeatureRowChangeEvent> FeatureRowDeleting;

        /// <summary>
        /// Occurs after the row has been deleted.
        /// </summary>
        public event EventHandler<FeatureRowChangeEvent> FeatureRowDeleted;

        #endregion

        #region Private Variables

        private DataColumn _columnFID;

        private DataColumn _columnGeometry;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of a FeatureTable class.
        /// </summary>
        public FeatureTable():this("MyFeatureTable")
        {
        }

        /// <summary>
        /// Initializes a new instance of a FeatureTable class.
        /// </summary>
        public FeatureTable(string tableName)
        {
            // Blame microsoft for the virtual call in constructor alert here, not me.
            // ReSharper disable DoNotCallOverridableMethodsInConstructor
            TableName = tableName;
            BeginInit();
            InitClass();
            EndInit();
            // ReSharper restore DoNotCallOverridableMethodsInConstructor
        }

        #endregion

        #region Methods

        /// <summary>
        /// Adds the specified FeatureRow to this FeatureTable.
        /// </summary>
        /// <param name="row">the row to add.</param>
        public void AddFeatureRow(FeatureRow row)
        {
            Rows.Add(row);
        }

        /// <summary>
        /// Generates a new feature row using only the goemetry.
        /// </summary>
        /// <param name="wellKnownBinary">The byte form of the well known text to use in creating a new, otherwise empty row.</param>
        /// <returns>The newly created FeatureRow with the specified well known text.</returns>
        public FeatureRow AddFeatureRow(byte[] wellKnownBinary)
        {
            FeatureRow rowFeatureRow = ((FeatureRow)(NewRow()));
            object[] columnValuesArray = new object[] {
                        null,
                        wellKnownBinary};
            rowFeatureRow.ItemArray = columnValuesArray;
            Rows.Add(rowFeatureRow);
            return rowFeatureRow;
        }

        /// <summary>
        /// Retrieves a newly generated row from this table cast as a FeatureRow.
        /// </summary>
        /// <returns>The FeatureRow class.</returns>
        public FeatureRow NewFeatureRow()
        {
            return ((FeatureRow)(NewRow()));
        }

        /// <inheritdocs/>
        public override DataTable Clone()
        {
            FeatureTable cln = ((FeatureTable)(base.Clone()));
            cln.InitVars();
            return cln;
        }

        /// <summary>
        /// Finds a FeatureRow by the FID field.
        /// </summary>
        /// <param name="fid">The long fid to find</param>
        /// <returns>A FeatureRow</returns>
        public FeatureRow FindByFID(long fid)
        {
            return ((FeatureRow)(Rows.Find(new object[] { fid })));
        }

        /// <summary>
        /// Removes the specified row from the table.
        /// </summary>
        /// <param name="row">The FeatureRow to remove.</param>
        public void RemoveFeatureRow(FeatureRow row)
        {
            Rows.Remove(row);
        }

        #endregion

        #region Properties

        /// <summary>
        /// The integer number of rows in this table.
        /// </summary>
        [Browsable(false)]
        public int Count
        {
            get
            {
                return Rows.Count;
            }
        }

        /// <summary>
        /// The Column containing the FID value.
        /// </summary>
        public DataColumn FidColumn
        {
            get
            {
                return _columnFID;
            }
        }

        /// <summary>
        /// The Column containing the Geometry column.
        /// </summary>
        public DataColumn GeometryColumn
        {
            get
            {
                return _columnGeometry;
            }
        }

        /// <summary>
        /// Optionally gets or sets a geometry factory to use when instantiating geometries
        /// from WKB for the rows of this table.
        /// </summary>
        public IGeometryFactory GeometryFactory { get; set; }

        /// <summary>
        /// Accesses the FeatureRow of this table based on the specified index.
        /// </summary>
        /// <param name="index">The integer index of the feature row to access.</param>
        /// <returns>This FeatureRow</returns>
        public FeatureRow this[int index]
        {
            get
            {
                return ((FeatureRow)(Rows[index]));
            }
        }

        /// <summary>
        /// Generates a new feature row using only the goemetry.
        /// </summary>
        /// <param name="geometry">The byte form of the well known text to use in creating a new, otherwise empty row.</param>
        /// <returns>The newly created FeatureRow with the specified well known text.</returns>
        public FeatureRow AddFeatureRow(IGeometry geometry)
        {
           
            FeatureRow rowFeatureRow = ((FeatureRow)(NewRow()));
            object[] columnValuesArray = new object[] {
                        null, geometry.ToBinary()};
            rowFeatureRow.Geometry = geometry;
            rowFeatureRow.ItemArray = columnValuesArray;
            Rows.Add(rowFeatureRow);
            return rowFeatureRow;
        }

        #endregion

        #region protected or private Methods

        /// <inheritdocs/>
        protected override DataTable CreateInstance()
        {
            return new FeatureTable();
        }

        private void InitClass()
        {
            _columnFID = new DataColumn("FID", typeof(long), null, MappingType.Element);
            Columns.Add(_columnFID);
            _columnGeometry = new DataColumn("GEOMETRY", typeof(byte[]), null, MappingType.Element);
            Columns.Add(_columnGeometry);
            Constraints.Add(new UniqueConstraint("Constraint1", new[] {
                                _columnFID}, true));
            _columnFID.AutoIncrement = true;
            _columnFID.AutoIncrementSeed = -1;
            _columnFID.AutoIncrementStep = -1;
            _columnFID.AllowDBNull = false;
            _columnFID.Unique = true;
        }

        /// <inheritdocs/>
        protected override Type GetRowType()
        {
            return typeof(FeatureRow);
        }

        /// <inheritdocs/>
        protected override DataRow NewRowFromBuilder(DataRowBuilder builder)
        {
            return new FeatureRow(builder);
        }

        /// <inheritdocs/>
        protected override void OnRowChanged(DataRowChangeEventArgs e)
        {
            base.OnRowChanged(e);
            if ((FeatureRowChanged != null))
            {
                FeatureRowChanged(this, new FeatureRowChangeEvent(((FeatureRow)(e.Row)), e.Action));
            }
        }

        /// <inheritdocs/>
        protected override void OnRowChanging(DataRowChangeEventArgs e)
        {
            base.OnRowChanging(e);
            if ((FeatureRowChanging != null))
            {
                FeatureRowChanging(this, new FeatureRowChangeEvent(((FeatureRow)(e.Row)), e.Action));
            }
        }

        /// <inheritdocs/>
        protected override void OnRowDeleted(DataRowChangeEventArgs e)
        {
            base.OnRowDeleted(e);
            if ((FeatureRowDeleted != null))
            {
                FeatureRowDeleted(this, new FeatureRowChangeEvent(((FeatureRow)(e.Row)), e.Action));
            }
        }

        /// <inheritdocs/>
        protected override void OnRowDeleting(DataRowChangeEventArgs e)
        {
            base.OnRowDeleting(e);
            if ((FeatureRowDeleting != null))
            {
                FeatureRowDeleting(this, new FeatureRowChangeEvent(((FeatureRow)(e.Row)), e.Action));
            }
        }

        #endregion

        #region Internal

        /// <summary>
        /// This may or may not be required for proper functioning, but is not part of the public API.
        /// </summary>
        /// <param name="table">A DataTable</param>
        internal FeatureTable(DataTable table)
        {
            TableName = table.TableName;
            if ((table.CaseSensitive != table.DataSet.CaseSensitive))
            {
                CaseSensitive = table.CaseSensitive;
            }
            if ((table.Locale.ToString() != table.DataSet.Locale.ToString()))
            {
                Locale = table.Locale;
            }
            if ((table.Namespace != table.DataSet.Namespace))
            {
                Namespace = table.Namespace;
            }
            Prefix = table.Prefix;
            MinimumCapacity = table.MinimumCapacity;
        }

        /// <summary>
        /// This may not work since I stripped off the xml related auto text that seems to rely on an existing dataset
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        protected FeatureTable(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) :
            base(info, context)
        {
            InitVars();
        }

        internal void InitVars()
        {
            _columnFID = Columns["FID"];
            _columnGeometry = Columns["GEOMETRY"];
        }

        #endregion

    }
}

Nov 6, 2010 at 8:32 PM
use IDataReader, so that you don't have to load the whole thing in memory?

On Fri, Nov 5, 2010 at 10:40 PM, shade1974 <notifications@codeplex.com> wrote:
> From: shade1974
>
> Here is the spifyness.  I tried to inherit from DataTable before, but it is
> not something to be done lightly.  However, as of VS2010, literally every
> time you automatically hook up a table it genrates this wonderful auto-code
> in the designer giving you strong classed DataRow members, and an example of
> how to set up the table to work with them.  I don't think we want strong
> typed attributes here, but we can piggyback on that automatically generated
> design for our own model where a Geometry, Shape, FID, Extent and a few
> other things are thrown in on top of your standard table, giving you
> something that is super easy to plug & play with databases, but something
> that should be a heck of a lot sturdier from the standpoint of coordinating
> between index mode, vertex mode, and attributes which may or may not be
> loaded etc.
>
> Anyway these are just some ideas, so I'm looking for feedback.  The
> autogenerated code also had some serialization stuff that seemed dependent
> on the dataset, so I chose to nix it.  Maybe someone more educated in the
> ways of the wise could give a viewpoint on whether it makes sense to try to
> support "xml serialziable" on the table itself.  I'm not sure where or how
> that would apply, but it may only be related to schema, and only important
> for serializing your dataset.
>
> Ted
>
> //
> ********************************************************************************************************
> // Product Name: DotSpatial.Data.dll
> // Description: The data access libraries for the DotSpatial project.
> //
> ********************************************************************************************************
> // The contents of this file are subject to the MIT License (MIT)
> // you may not use this file except in compliance with the License. You may
> obtain a copy of the License at
> // http://dotspatial.codeplex.com/license
> //
> // Software distributed under the License is distributed on an "AS IS"
> basis, WITHOUT WARRANTY OF
> // ANY KIND, either expressed or implied. See the License for the
> specificlanguage governing rights and
> // limitations under the License.
> //
> // The Initial Developer of this Original Code is Ted Dunsford. Created
> 11/5/2010 6:59:48 PM
> //
> // Contributor(s): (Open source contributors should list themselves and
> their modifications here).
> // | Name | Date |
> Comments
> //
> |----------------------|-------------|-----------------------------------------------------------------
> //
> ********************************************************************************************************
> using System;
> using System.ComponentModel;
> using System.Data;
> using DotSpatial.Topology;
> namespace DotSpatial.Data
> {
> /// <summary>
> ///Represents the strongly named DataTable class.
> ///</summary>
> public class FeatureTable : TypedTableBase<FeatureRow>
> {
> #region Events
>
> /// <summary>
> /// Occurs while the row is changing
> /// </summary>
> public event EventHandler<FeatureRowChangeEvent> FeatureRowChanging;
>
> /// <summary>
> /// Occurs after the row has been changed.
> /// </summary>
> public event EventHandler<FeatureRowChangeEvent> FeatureRowChanged;
>
> /// <summary>
> /// Occurs while the row is being deleted.
> /// </summary>
> public event EventHandler<FeatureRowChangeEvent> FeatureRowDeleting;
>
> /// <summary>
> /// Occurs after the row has been deleted.
> /// </summary>
> public event EventHandler<FeatureRowChangeEvent> FeatureRowDeleted;
>
> #endregion
>
> #region Private Variables
>
> private DataColumn _columnFID;
>
> private DataColumn _columnGeometry;
>
> #endregion
>
> #region Constructors
>
> /// <summary>
> /// Initializes a new instance of a FeatureTable class.
> /// </summary>
> public FeatureTable():this("MyFeatureTable")
> {
> }
>
> /// <summary>
> /// Initializes a new instance of a FeatureTable class.
> /// </summary>
> public FeatureTable(string tableName)
> {
> // Blame microsoft for the virtual call in constructor alert
> here, not me.
> // ReSharper disable DoNotCallOverridableMethodsInConstructor
> TableName = tableName;
> BeginInit();
> InitClass();
> EndInit();
> // ReSharper restore DoNotCallOverridableMethodsInConstructor
> }
>
> #endregion
>
> #region Methods
>
> /// <summary>
> /// Adds the specified FeatureRow to this FeatureTable.
> /// </summary>
> /// <param name="row">the row to add.</param>
> public void AddFeatureRow(FeatureRow row)
> {
> Rows.Add(row);
> }
>
> /// <summary>
> /// Generates a new feature row using only the goemetry.
> /// </summary>
> /// <param name="wellKnownBinary">The byte form of the well known
> text to use in creating a new, otherwise empty row.</param>
> /// <returns>The newly created FeatureRow with the specified well
> known text.</returns>
> public FeatureRow AddFeatureRow(byte[] wellKnownBinary)
> {
> FeatureRow rowFeatureRow = ((FeatureRow)(NewRow()));
> object[] columnValuesArray = new object[] {
> null,
> wellKnownBinary};
> rowFeatureRow.ItemArray = columnValuesArray;
> Rows.Add(rowFeatureRow);
> return rowFeatureRow;
> }
>
> /// <summary>
> /// Retrieves a newly generated row from this table cast as a
> FeatureRow.
> /// </summary>
> /// <returns>The FeatureRow class.</returns>
> public FeatureRow NewFeatureRow()
> {
> return ((FeatureRow)(NewRow()));
> }
>
> /// <inheritdocs/>
> public override DataTable Clone()
> {
> FeatureTable cln = ((FeatureTable)(base.Clone()));
> cln.InitVars();
> return cln;
> }
>
> /// <summary>
> /// Finds a FeatureRow by the FID field.
> /// </summary>
> /// <param name="fid">The long fid to find</param>
> /// <returns>A FeatureRow</returns>
> public FeatureRow FindByFID(long fid)
> {
> return ((FeatureRow)(Rows.Find(new object[] { fid })));
> }
>
> /// <summary>
> /// Removes the specified row from the table.
> /// </summary>
> /// <param name="row">The FeatureRow to remove.</param>
> public void RemoveFeatureRow(FeatureRow row)
> {
> Rows.Remove(row);
> }
>
> #endregion
>
> #region Properties
>
> /// <summary>
> /// The integer number of rows in this table.
> /// </summary>
> [Browsable(false)]
> public int Count
> {
> get
> {
> return Rows.Count;
> }
> }
>
> /// <summary>
> /// The Column containing the FID value.
> /// </summary>
> public DataColumn FidColumn
> {
> get
> {
> return _columnFID;
> }
> }
>
> /// <summary>
> /// The Column containing the Geometry column.
> /// </summary>
> public DataColumn GeometryColumn
> {
> get
> {
> return _columnGeometry;
> }
> }
>
> /// <summary>
> /// Optionally gets or sets a geometry factory to use when
> instantiating geometries
> /// from WKB for the rows of this table.
> /// </summary>
> public IGeometryFactory GeometryFactory { get; set; }
>
> /// <summary>
> /// Accesses the FeatureRow of this table based on the specified
> index.
> /// </summary>
> /// <param name="index">The integer index of the feature row to
> access.</param>
> /// <returns>This FeatureRow</returns>
> public FeatureRow this[int index]
> {
> get
> {
> return ((FeatureRow)(Rows[index]));
> }
> }
>
> /// <summary>
> /// Generates a new feature row using only the goemetry.
> /// </summary>
> /// <param name="geometry">The byte form of the well known text to
> use in creating a new, otherwise empty row.</param>
> /// <returns>The newly created FeatureRow with the specified well
> known text.</returns>
> public FeatureRow AddFeatureRow(IGeometry geometry)
> {
>
> FeatureRow rowFeatureRow = ((FeatureRow)(NewRow()));
> object[] columnValuesArray = new object[] {
> null, geometry.ToBinary()};
> rowFeatureRow.Geometry = geometry;
> rowFeatureRow.ItemArray = columnValuesArray;
> Rows.Add(rowFeatureRow);
> return rowFeatureRow;
> }
>
> #endregion
>
> #region protected or private Methods
>
> /// <inheritdocs/>
> protected override DataTable CreateInstance()
> {
> return new FeatureTable();
> }
>
> private void InitClass()
> {
> _columnFID = new DataColumn("FID", typeof(long), null,
> MappingType.Element);
> Columns.Add(_columnFID);
> _columnGeometry = new DataColumn("GEOMETRY", typeof(byte[]),
> null, MappingType.Element);
> Columns.Add(_columnGeometry);
> Constraints.Add(new UniqueConstraint("Constraint1", new[] {
> _columnFID}, true));
> _columnFID.AutoIncrement = true;
> _columnFID.AutoIncrementSeed = -1;
> _columnFID.AutoIncrementStep = -1;
> _columnFID.AllowDBNull = false;
> _columnFID.Unique = true;
> }
>
> /// <inheritdocs/>
> protected override Type GetRowType()
> {
> return typeof(FeatureRow);
> }
>
> /// <inheritdocs/>
> protected override DataRow NewRowFromBuilder(DataRowBuilder builder)
> {
> return new FeatureRow(builder);
> }
>
> /// <inheritdocs/>
> protected override void OnRowChanged(DataRowChangeEventArgs e)
> {
> base.OnRowChanged(e);
> if ((FeatureRowChanged != null))
> {
> FeatureRowChanged(this, new
> FeatureRowChangeEvent(((FeatureRow)(e.Row)), e.Action));
> }
> }
>
> /// <inheritdocs/>
> protected override void OnRowChanging(DataRowChangeEventArgs e)
> {
> base.OnRowChanging(e);
> if ((FeatureRowChanging != null))
> {
> FeatureRowChanging(this, new
> FeatureRowChangeEvent(((FeatureRow)(e.Row)), e.Action));
> }
> }
>
> /// <inheritdocs/>
> protected override void OnRowDeleted(DataRowChangeEventArgs e)
> {
> base.OnRowDeleted(e);
> if ((FeatureRowDeleted != null))
> {
> FeatureRowDeleted(this, new
> FeatureRowChangeEvent(((FeatureRow)(e.Row)), e.Action));
> }
> }
>
> /// <inheritdocs/>
> protected override void OnRowDeleting(DataRowChangeEventArgs e)
> {
> base.OnRowDeleting(e);
> if ((FeatureRowDeleting != null))
> {
> FeatureRowDeleting(this, new
> FeatureRowChangeEvent(((FeatureRow)(e.Row)), e.Action));
> }
> }
>
> #endregion
>
> #region Internal
>
> /// <summary>
> /// This may or may not be required for proper functioning, but is
> not part of the public API.
> /// </summary>
> /// <param name="table">A DataTable</param>
> internal FeatureTable(DataTable table)
> {
> TableName = table.TableName;
> if ((table.CaseSensitive != table.DataSet.CaseSensitive))
> {
> CaseSensitive = table.CaseSensitive;
> }
> if ((table.Locale.ToString() !=
> table.DataSet.Locale.ToString()))
> {
> Locale = table.Locale;
> }
> if ((table.Namespace != table.DataSet.Namespace))
> {
> Namespace = table.Namespace;
> }
> Prefix = table.Prefix;
> MinimumCapacity = table.MinimumCapacity;
> }
>
> /// <summary>
> /// This may not work since I stripped off the xml related auto text
> that seems to rely on an existing dataset
> /// </summary>
> /// <param name="info"></param>
> /// <param name="context"></param>
> protected
> FeatureTable(System.Runtime.Serialization.SerializationInfo info,
> System.Runtime.Serialization.StreamingContext context) :
> base(info, context)
> {
> InitVars();
> }
>
> internal void InitVars()
> {
> _columnFID = Columns["FID"];
> _columnGeometry = Columns["GEOMETRY"];
> }
>
> #endregion
>
> }
> }
>
> Read the full discussion online.
>
> To add a post to this discussion, reply to this email
> ([email removed])
>
> To start a new discussion for this project, email
> [email removed]
>
> You are receiving this email because you subscribed to this discussion on
> CodePlex. You can unsubscribe or change your settings on codePlex.com.
>
> Please note: Images and attachments will be removed from emails. Any posts
> to this discussion will also be available online at codeplex.com
Nov 6, 2010 at 11:22 PM

You are onto where I am headed with this, though I am thinking now of IDataReader "as well" rather than "instead of".  It is easier to use, and we can put the file buffer management and query handling behind the scenes, while the user gets access to a very straight forward enumerator.  I'm hoping to use the FeatureTable as the pages returned for a particular query, and having some sort of paging data adapter to maintain responsibility for tracking what page you are in for your dataset, but which would have a very tiny memory profile in and of itself.  If the memory hog is in the hands of the user, they can decide what they want to do with a page of results.  If it is in a super secret buffer, they can't change the memory profile in any way.

Nov 7, 2010 at 12:55 AM

Hmm.  It also seems that if we can support IDataReader, the "Load" method on the DataTable should allow us to rather easily populate the table.  Not certain this is available on our generic base, but I assume it would be.  So if I basically institute some sort of page setting on my IDataReader, we should be able to load a single page of results to the table, do what you will with the table, rinse and repeat.  Not a bad tactic.  I am liking IQueriable<T> less every time I look at it though.  It seems like you need to implement about 10 classes that have little or nothing to do with your specific data type just to get the very fundamental nuts and bolts of binary expression tree parsing going, and it seems to be really designed where everything is strong typed.  In our case, except for the geometry itself and maybe the FID, we aren't really looking at a great strong typed scenario since the attributes can be any type.  I'm probably just not seeing the forest for the trees on that one, but I'm thinking we should start with the basics.  I think if we can get a basic IDataReader implementation going to load the FeatureTable from a shapefile, then that will be a strong next step.

Nov 8, 2010 at 5:10 PM

Thanks Ted for working on this. Looks good to me so far. 

Nov 8, 2010 at 9:19 PM

Ted,

While using the PolygonShapefileShapeSource (which I assume will be deprecated with our new approach), it seemed the Dictionary<int, Shape> returned from GetShapes was unnecessary... an IEnumerable of KeyValuePairs would have sufficed (in fact I ended up enumerating the dictionary which returned me KVPs).  So, I think a FeatureTable of FeatureRows, where we can get the FID and Shape from the FeatureRow (as you have designed it) will work nicely.

For my application, I'm thinking I'll have my own Map Layers derived off of the Dot Spatial Map Layers so I can support the thousands of symbologies that my application needs that probably is not in the mainstream of Dot Spatial use cases.  As long as the AttributeTable class hangs around, I think I can continue to do my own fast symbology key file cache which means we can make a low priority of loading only a single column of attribute data into a FeatureTable (which also appears to be somewhat outside the Dot Spatial mainstream).  So, my particular use case, which I think would fit well with the mainstream) would be to have FeatureRows that have NO attribute data... just the Shapes and FIDs (wouldn't need the extents either).  This is probably what you would want for the simple layer display case where you use layer symbology (not attribute-driven).

So, as long as the performance holds up, the approach you have looks good to me.

Thanks,

Kyle