简体   繁体   中英

loading a List<T> from datatable with generic method

I load 10 different List< T > with datatable. The datatable is loaded from a Sqlite Database. To do this, I'm repeating 10 times the same method. Here is an example of my code:

//List to be load
public static List<AircraftModel> Aircraft = new List<AircraftModel>();
public static List<AirlineModel> Airline = new List<AirlineModel>();

//Method to load the list Aircraft with the datatable
public void LoadAircraft(DataTable data)
    {
       foreach (DataRow row in data.Rows)
        {
          Aircraft.Add(new AircraftModel
            {
                Id = Int32.Parse(row["id"].ToString()),
                Registration = row["registration"].ToString(),
                Capacity = Int32.Parse(row["capacity"].ToString()),
                Type = row["type"].ToString()
            });
        }
    }

//Method to load the List Airline with datatable
public void LoadAirline(DataTable data)
    {
        foreach (DataRow row in data.Rows)
        {
            Airline.Add(new AirlineModel
            {
                Code = row["code"].ToString(),
                AirlineName = row["name"].ToString()
            });
        }
    }

Would it be possible to optimize my code with a generic method like this:

//call method to load the List
 GetData<AircraftModel>(Aircraft, datatable);
 GetData<AirlineModel>(Airline, datatable);

//Unique generic method to load the 10 List < T >
public void GetData<T>(List< T > ListToBeLoad, DataTable table)
       where T : class, new()
    {

        foreach (DataRow row in table.Rows)
        {
            ListToBeLoad.Add( new T
            {
              ......
              HELP NEEDED PLEASE
              ......
            }
        }            
    }

thanks in advance for your reply and propositions

Cyrille

You have couple of methods, you can create a mapping method and pass it as an argument, or you can use AutoMapper.

Here is 2 examples of the first solution (Without AutoMapper)

Example of populating to existing list

PopulateList(list, dataTable, (row) =>
    new AircraftModel
    {
        Id = int.Parse(row["id"].ToString()),
        Registration = row["registration"].ToString(),
        Capacity = int.Parse(row["capacity"].ToString()),
        Type = row["type"].ToString()

    }
);

public static void PopulateList<T>(List<T> list, DataTable data, Func<DataRow, T> mapFunc)
    where T : new()
{
    foreach (DataRow row in data.Rows)
    {
        list.Add(mapFunc(row));
    }
}

Example of creating a new list and mapping the rows.

var list2 = Map(dataTable, (row) =>
    new AircraftModel
    {
        Id = int.Parse(row["id"].ToString()),
        Registration = row["registration"].ToString(),
        Capacity = int.Parse(row["capacity"].ToString()),
        Type = row["type"].ToString()

    }
);

public static IEnumerable<T> Map<T>(DataTable data, Func<DataRow, T> mapFunc)
    where T : new()
{
    foreach (DataRow row in data.Rows)
    {
        yield return mapFunc(row);
    }
}

As you may have noticed, the issue is that you need to know the columns in order to get the right data, and to know the properties in order to store it in the right place. As a result, your code needs to be aware of each of the individual classes - and that's fine: You're not going to get rid of this code.

Generally, when you want to construct something, you may want to think of a factory , that is, a method (or class) that is capable of constructing an instance of a specific type. A factory class , in your case, could look like the following:

public abstract class Factory<T>
{
    public T Create(DataRow row);
}

Now the next thing you would do is creating specific instances for each type you want to create, eg

public sealed class AirlineModelFactory : Factory<AirlineModel>
{
    public override AirlineModel Create(DataRow row)
    {
        return new AirlineModel
        {
            Code = row["code"].ToString(),
            AirlineName = row["name"].ToString()
        };
    }
}

Now the boilerplate code is this:

public void GetData<T>(List<T> list, DataTable table, Factory<T> factory)
where T : class, new()
{
    foreach (DataRow row in table.Rows)
    {
        list.Add(factory.Create(row));
    }            
}

and you may call it as

GetData(AirlineList, table, new AirlineModelFactory());
GetData(AirpoirtList, table, new AirportModelFactory());
// etc.

although, arguably, you may want to keep your factory instances around (or even inject them for inversion of control) to avoid the new . However, as you will notice, you still need to create N classes / methods for N types.

To automate things a bit more, you could introduce a "generic" base class to your factories, eg

public abstract class GenericFactory
{
    public abstract object CreateObject(DataRow row);
}

and then implement

public abstract class Factory<T> : GenericFactory
{
    public override object CreateObject(DataRow row) => Create(row);
    // ...
}

With this, you could think of a lookup dictionary Dictionary<Type, GenericFactory> . By picking the right type and then casting to the right T , you get your correct instance. This is, however, a service locator pattern - a code smell, because a missing registration in the dictionary leads to an error at runtime - but depending on your needs, it could still be of help.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM