Lost in generic classes and inheritance - any anyone lead me out?

245 Views Asked by At

I hope this is not to complicated. I do have it working, but I wonder if that is the best it can be:

I want to use the SQL Bulk Copy class, and more specific the overload that takes an IDataReader. I have my data in several List<> so I need a "translator". I found one that does it by using extension methods - very elegant, I think! It is listed here: http://www.differentpla.net/content/2011/01/converting-ienumerablet-idatareader I think this is an excellent base to build on?!

The code shown there is not complete, some unnecessary overloads that are never called are missing. I added them as NotImplementedException. The completed version can be found at the end.

You use that class by calling the AsDataReader method on your IEnumerable<> or List<>, and pass it the number of columns in your data, and a function with which to step over the columns, like this:

List<NodeDB> myList = new List<NodeDB>();
myList.Add(myNode1);
myBulk.WriteToServer(myList.AsDataReader(3, NodeDB.GetValue));

The class NodeDB has some fields, and a static(!?) method GetValue where it gets the currently iterated position (!?) and column, and returns the appropriate item:

static object GetValue(NodeDB nodeDB, int i) {
    object obj = null;
    switch (i) {
         case 0:
            obj = nodeDB.ID;
            break;
        case 1:
            obj = nodeDB.Latitude;
             break;
        case 2:
             obj = nodeDB.Longitude;
             break;
    }
    return obj;
}

Having a static method in the class that takes an instance of itself sounds strange, I wonder if there is a better way. But even more important: I do have several classes like NodeDB, all of them with their specific GetValues, organized into List<>, one List<> for each type.

I would like to pass down all of them to ONE generic bulk write method.

Currently, I do that by passing the different Lists as a IEnumberable, and then do a cast back to what it was after comparing them one by one:

if (list is List<NodeDB>) {
} else if ( ....

Within each of these different cases I need to address the correct function too, that is repetive and duplicate and tedious - is there a way to derive the correct Func argument from the List passed?

How could I change my classes (to derive from a common class which has an abstract GetValues)? All List<> could be given as List then, I think, and all calls to GetValues would reach it's targets. I tried to do that, but the static method cannot be abstract in a base class.

Or I can I add another parameter, a TYPE probably to then call the Func against that? Or change the IDataReader Extension?

As I said, I am lost... Thanks for your help! Ralf

Here the complete IDataReader Extension to IEnumerable with all interface implementations:

using System;
using System.Collections.Generic;
using System.Data;

static class DataReaderExtensions {
    public static IDataReader AsDataReader<TSource>(this IEnumerable<TSource> source, int fieldCount, Func<TSource, int, object> getValue) {
        return new EnumerableDataReader<TSource>(source.GetEnumerator(), fieldCount, getValue);
        //return EnumerableDataReader.Create(source, fieldCount, getValue);
    }
}

//internal static class EnumerableDataReader {
//    public static IDataReader Create<TSource>(IEnumerable<TSource> source, int fieldCount, Func<TSource, int, object> getValue) {
//        return new EnumerableDataReader<TSource>(source.GetEnumerator(), fieldCount, getValue);
//    }
//}

internal class EnumerableDataReader<TSource> : IDataReader {
    private readonly IEnumerator<TSource> _source;
    private readonly int _fieldCount;
    private readonly Func<TSource, int, object> _getValue;

    internal EnumerableDataReader(IEnumerator<TSource> source, int fieldCount, Func<TSource, int, object> getValue) {
        _source = source;
        _getValue = getValue;
        _fieldCount = fieldCount;
    }

    public void Dispose() {
        // Nothing.
    }

    public string GetName(int i) {
        throw new NotImplementedException();
    }

    public string GetDataTypeName(int i) {
        throw new NotImplementedException();
    }

    public Type GetFieldType(int i) {
        throw new NotImplementedException();
    }

    public object GetValue(int i) {
        return _getValue(_source.Current, i);
    }

    public int GetValues(object[] values) {
        throw new NotImplementedException();
    }

    public int GetOrdinal(string name) {
        throw new NotImplementedException();
    }

    public int FieldCount {
        get { return _fieldCount; }
    }

    object IDataRecord.this[int i] {
        get { throw new NotImplementedException(); }
    }

    object IDataRecord.this[string name] {
        get { throw new NotImplementedException(); }
    }

    public void Close() {
        throw new NotImplementedException();
    }

    public DataTable GetSchemaTable() {
        throw new NotImplementedException();
    }

    public bool NextResult() {
        throw new NotImplementedException();
    }

    public bool Read() {
        return _source.MoveNext();
    }

    public int Depth { get; private set; }
    public bool IsClosed { get; private set; }
    public int RecordsAffected { get; private set; }

    public bool IsDBNull(int i) {
        throw new NotImplementedException();
    }

    public IDataReader GetData(int i) {
        throw new NotImplementedException();
    }

    public DateTime GetDateTime(int i) {
        throw new NotImplementedException();
    }

    public Decimal GetDecimal(int i) {
        throw new NotImplementedException();
    }

    public String GetString(int i) {
        throw new NotImplementedException();
    }

    public Double GetDouble(int i) {
        throw new NotImplementedException();
    }

    public float GetFloat(int i) {
        throw new NotImplementedException();
    }

    public Int64 GetInt64(int i) {
        throw new NotImplementedException();
    }

    public Int32 GetInt32(int i) {
        throw new NotImplementedException();
    }

    public Int16 GetInt16(int i) {
        throw new NotImplementedException();
    }

    public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) {
        throw new NotImplementedException();
    }

    public Guid GetGuid(int i) {
        throw new NotImplementedException();
    }

    public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) {
        throw new NotImplementedException();
    }

    public Char GetChar(int i) {
        throw new NotImplementedException();
    }

    public Byte GetByte(int i) {
        throw new NotImplementedException();
    }

    public Boolean GetBoolean(int i) {
        throw new NotImplementedException();
    }

}
1

There are 1 best solutions below

1
On

One possible solution is to make GetValue non-static and pass it like this:

List<NodeDB> myList = new List<NodeDB>();
myList.Add(myNode1);
myBulk.WriteToServer(myList.AsDataReader(3, (n,i)=>n.GetValue(i)));

The GetValue method will look like this:

public object GetValue(int i) 
{
    object obj = null;
    switch (i) 
    {
        case 0:
            obj = this.ID;
            break;
        case 1:
            obj = this.Latitude;
            break;
        case 2:
            obj = this.Longitude;
            break;
    }
    return obj;
}