General method where T implements the <T> interface
I am trying to create a general data retrieval process. That I am currently working, but there is a part of it that does not seem to be correct, and I hope that there is a better way to accomplish it.
So the idea is that I have classes for each table in the database, here is an example of a class:
public class CMCGRGRGROUP : IFacetsObject<CMCGRGRGROUP> { public int GRGR_CK { get; set; } public string GRGR_NAME { get; set; } public string GRGR_ADDR1 { get; set; } public IEnumerable<CMCGRGRGROUP> ToObject(DataTable table) { return table.AsEnumerable().Select(row => { return new CMCGRGRGROUP { GRGR_CK = Convert.ToInt32(row["GRGR_CK"]), GRGR_NAME = row["GRGR_NAME"].ToString(), GRGR_ADDR1 = row["GRGR_ADDR1"].ToString() }; }); } } You will notice that the class implements an interface of its type. The interface simply defines a method called ToObject , which is used to convert the data into a class of this particular type:
public interface IFacetsObject<T> { IEnumerable<T> ToObject(DataTable obj); } Now, here is the method I use to execute the request:
public IEnumerable<T> ExecuteQuery<T>(string sql, IFacetsObject<T> obj) where T : new() { using (var conn = new AseConnection(_conn)) { conn.Open(); var cmd = new AseCommand(sql, conn); var dt = new DataTable(); var da = new AseDataAdapter(sql, conn); da.Fill(dt); return obj.ToObject(dt); //this is the interface method } } So, the main question: How does the general method know that T must implement IFacetsObject<T> ? This way, I do not need to pass IFacetsObject<T> as a parameter. Ideally, I can change the return line to be something like this:
return T.ToObject(dt); And name it as follows:
var result = ExecuteQuery<CMCGRGRGROUP>(sql).Take(5); Instead of this:
var result = ExecuteQuery<CMCGRGRGROUP>(sql, new CMCGRGRGROUP()).Take(5); I admit that I'm not very familiar with generics, but maybe something in the implementation is wrong.
You can add a constraint to your ExecuteQuery method. You already have one: T requirement to be new. You would declare it as:
public IEnumerable<T> ExecuteQuery<T>(string sql, IFacetsObject<T> obj) where T : IFacetsObject<T>, new() { using (var conn = new AseConnection(_conn)) { conn.Open(); var cmd = new AseCommand(sql, conn); var dt = new DataTable(); var da = new AseDataAdapter(sql, conn); da.Fill(dt); return obj.ToObject(dt); //this is the interface method } } So now itβs known that T is an IFacetsObject<T> . Now you can:
public IEnumerable<T> ExecuteQuery<T>(string sql) where T : IFacetsObject<T>, new() { using (var conn = new AseConnection(_conn)) { conn.Open(); var cmd = new AseCommand(sql, conn); var dt = new DataTable(); var da = new AseDataAdapter(sql, conn); da.Fill(dt); return new T().ToObject(dt); //this is the interface method } } Which IMO is still pretty ugly.
EDIT Answer:
Note that you cannot call T.ToObject - the interface cannot define a static method. A workaround is to use the new one to create a new instance of T and call the instance method.
Your interface requires a general restriction. Declare it as follows:
public interface IFacetsObject<T> where T : IFacetsObject<T> { IEnumerable<T> ToObject(DataTable obj); } For this to work, you also need to modify your ad as follows:
public IEnumerable<T> ExecuteQuery<T>(string sql, IFacetsObject<T> obj) where T : IFacetsObject<T>, new()