1

I have a lot of model classes that I fill in with data from API.
Structure of API data is DataRow.
For each class there is an implicit conversion from DataRow to model class. I have to write a set of functions for each model class. One of the functions, get from database, is common for all model classes so I decided to write function with generic type for this.

Here is an example of model class:

public class Person
{
    public long Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public static implicit operator Person(DataRow dataRow)
    {
        if (dataRow != null)
        {
            return new Person
            {
                Id = long.Parse((string)dataRow["Id"]),
                FirstName = (string)dataRow["FirstName"],
                LastName = (string)dataRow["LastName"]
            };
        };
        return null;
    }
}

example of class with generic type for casting, looks like this:

public class DefaultRepository<T>
{        
    public T CreateInstanceOfModelFromDataRow(DataRow dataRow)
    {
        T modelInstance = (T)Activator.CreateInstance(typeof(T));

        modelInstance = (T)dataRow;
        
        return modelInstance;
    }     
}

and code for testing looks like this:

// for testing I will create DataTable and DataRow and populate it fake data            
DataTable fakeTable = new DataTable();

fakeTable.Columns.Add("Id");
fakeTable.Columns.Add("FirstName");
fakeTable.Columns.Add("LastName");

DataRow fakeRow = fakeTable.NewRow();
        
fakeRow["Id"] = "1";
fakeRow["FirstName"] = "John";
fakeRow["LastName"] = "Doe";

// Cast DataRow to Pesron without error
Person fakePerson01 = (Person)fakeRow;

// Cant cast on this way create error while compiling
DefaultRepository<Person> defRepo = new DefaultRepository<Person>();
Person fakePerson02 = defRepo.CreateInstanceOfModelFromDataRow(fakeRow);

I get error while compiling "Cannot convert type 'System.Data.DataRow' to 'T'" at line modelInstance = (T)dataRow;

I try with (T)Convert.ChangeType(dataRow, typeof(T)); also but no luck.

Is it posible to do this conversion?

Milos
  • 143
  • 1
  • 11
  • Does this answer your question? [How do write a generic function that takes a datarow and fill an object's properties?](https://stackoverflow.com/questions/6690501/how-do-write-a-generic-function-that-takes-a-datarow-and-fill-an-objects-proper) – Wim Ombelets Dec 14 '20 at 09:19
  • 4
    You're rebuilding EF Core or Dapper. Why? You gain nothing but have to handle DataTable's shortcomings. With Dapper, all this code would be replaced with `conn.Query("select * from Persons where LastName=@name.",new {name="Doe"});`. – Panagiotis Kanavos Dec 14 '20 at 09:20
  • 2
    On the other hand, if you used EF Core you wouldn't even need a repository, you'd get a Unit-of-Work out of the box *and* parameterized SQL generation. – Panagiotis Kanavos Dec 14 '20 at 09:24
  • Thanks for suggestion @PanagiotisKanavos, but I can't use any ORM because don't have direct access to database. – Milos Dec 14 '20 at 09:41
  • Thanks @WimOmbelets, but I can't use that solution because there are too many conflicts in field names from DataRow(s) and models Classes. If I will use this solution I need to create mapping for each model class, and the conversion has already been written. – Milos Dec 14 '20 at 09:50
  • I don't know if this is possible with generic classes (and I'm curious to know the answer myself, so +1 to your question), but I can suggest an alternative: Create an interface `ILoadableFromDataRow { void LoadFromDataRow(DataRow row); }`, have `Person` implement it and put your parsing logic there. `DefaultRepository` can then restrict `T` to `T : ILoadableFromDataRow` and directly call `LoadFromDataRow` after `CreateInstance`. – Heinzi Dec 14 '20 at 11:55

1 Answers1

1

Since you are using generics, compiler does not know what type T is, hence you are getting the compile time error.

You will need to add constraint to your generic implementation -

public class DefaultRepository<T> where T : Person
{
        public T CreateInstanceOfModelFromDataRow(DataRow dataRow) 
        {
            T modelInstance = (T)Activator.CreateInstance(typeof(T));

            modelInstance = (T)dataRow;

            return modelInstance;
        }
}

Also you need not create instance through reflection. You can just cast dataRow to your class -

    public class DefaultRepository<T> where T : Person
    {
        public T CreateInstanceOfModelFromDataRow(DataRow dataRow) 
        {
            var modelInstance = (T)dataRow;

            return modelInstance;
        }
    }
Nikhil Patil
  • 2,480
  • 1
  • 7
  • 20
  • This mean that the compiler limits me to only one class, and that I can't use multiple model classes like Person, Student... because with constraint can define only one class at time. Am I right? Thanks for explanation @NikhilPatil. – Milos Dec 14 '20 at 14:06
  • Yes, but you can have constraint on interface or abstract class and make your classes derive or implement from them. The problem is defining a implicit operator in this 'abstract' definitions. I tried to do it, but I didn't work. Refer - https://stackoverflow.com/questions/32519131/c-sharp-implicit-operator-with-generic , https://stackoverflow.com/questions/6946412/c-sharp-creating-an-implicit-conversion-for-generic-class if it helps. – Nikhil Patil Dec 15 '20 at 03:57
  • Thanks a lot for your help and effort Nikhil! :) – Milos Dec 15 '20 at 07:47