Reputation: 5579
I've coded myself into a corner and would like your help to dig me out again. In the right direction.
So, I've implemented a minor SQLite wrapper where I wanted the solution to be generic (don't we all). Afterwards, I now realize that the usage of these classes and interfaces aren't very intuitive nor generic.
Let's start at the bottom and work upwards. I've created a class called DataRow
that works as a base class for my table rows. The class DataRow
itself only has a property Id
(since all rows need one). This results in the following definition: class DataRow { public int Id { get; set; } }
Using this DataRow
class, is each table. And for database tables I've created one generic interface, and one generic base class. The definitions looks like this:
internal interface ITable<T>
where T : DataRow, new()
{
T Select(int id);
List<T> Select(List<int> ids);
int Insert(T t);
void Update(T t);
bool Delete(int id);
}
public class Table<T> : ITable<T>
where T : DataRow, new()
{
// Commented out to protect you from a minor case of serious brain damage.
}
This setup allows me to create concise definitions. In fact, they tend to be quite epic, really. Proud to say.
public class Car : DataRow
{
public decimal ParkingTicketDebt { get; set; }
public DateTime WhenWifeWillAllowReplacement { get; set; }
public bool CanTransformIntoSomethingAwesome { get; set; }
}
public class Cars : Table<Car> {
// Yep, that's all. You can go home now, folks. There's nothing here. Nothing at all. Especially not a great treasure of gold. Whops... I mean... there's really not. Not that I'm aware of, anyway... I mean, there could be. Not that I wouldn't say if I had any information on this great trasure of gold that might exist. But I know nothing of such an item. I really don't, so you can stop thinking about this great treasure of gold. Since I don't know anything about it, the chance that it even exist is extremely low. Miniscule. I mean, you would probably not find anything, not even if you digged for, like, a really long time. Seven years or something. Oookay. Slowly fading away...
}
As you may or may not have noticed, I'm using the class type name of Cars
to determine the name of the table in the database. Likewise, I'm performing reflection on Car
and use its public property names and types to get/set values in the database. And yes, I'm aware that I'm in the process of coding a stripped down version of Entity Framework. Which sounds both really stupid and quite time consuming.
Anyway, here is a usage example of the class Cars
, which I must remind you that I'm proud of:
new Cars().Delete(3497); // Note that I have a great number of (expensive) cars.
Nice, eh? One slight problem. This means that I have to write strongly typed code, specific to the number of tables that exist in the database. And I don't like specific code. I like generic code.
You might start arguing here that I'm overdoing it. Then let me tell you this. You're damn right I'm overkilling! I'm intentionally flamethrowing the dead guy that was ran over by a tank. Seven times.
So I started experimenting a bit and came up with this delicate solution:
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
[WebMethod(EnableSession = true)]
public int CreateEmptyRow(string tableName)
{
var tableType = Type.GetType(tableName);
if (tableType == null)
throw new TypeLoadException("Dumbass. That table doesn't exist");
var instance = Activator.CreateInstance(tableType) as ITable<dynamic>;
if (instance == null)
throw new TypeLoadException("Idiot. That type isn't a table");
return instance.Insert(new DataRow());
}
Note that I can really understand if you have no idea why someone would want to create an empty row.
So what's wrong with this? Well, it doesn't compile, for one. Here's the error: There is no implicit reference conversion from 'dynamic' to 'DataRow'
. A search on Google gave few results.
The problem is obviously Activator.CreateInstance(tableType) as ITable<dynamic>
. I've tried things like Activator.CreateInstance(tableType) as ITable<Table<DataRow>>
, an attempt which gave me this error: The type 'DataRow' must be convertible to 'DataRow'
.
Upvotes: 1
Views: 2536
Reputation: 2123
So, as ive written in the comment, Im adding an extra non-generic interface:
interface ITable
{
DataRow Select(int id);
IEnumerable<DataRow> Select(List<int> ids);
int Insert(DataRow t);
void Update(DataRow t);
}
interface ITable<T> where T : DataRow, new()
{
T Select(int id);
List<T> Select(List<int> ids);
int Insert(T t);
void Update(T t);
bool Delete(int id);
}
class Table<T> : ITable<T>, ITable where T : DataRow, new()
{
public T Select(int id)
{
return new T();
}
public List<T> Select(List<int> ids)
{
return new List<T>();
}
public int Insert(T t)
{
return 1;
}
public void Update(T t)
{
}
public bool Delete(int id)
{
return true;
}
DataRow ITable.Select(int id)
{
return this.Select(id);
}
IEnumerable<DataRow> ITable.Select(List<int> ids)
{
return this.Select(ids);
}
public int Insert(DataRow t)
{
return this.Insert(t);
}
public void Update(DataRow t)
{
this.Update(t);
}
}
and this is how im implementing the CreateEmptyRow
\ Select
methods:
public static int CreateEmptyRow(string tableName)
{
var tableType = Type.GetType(tableName);
if (tableType == null)
throw new TypeLoadException("Dumbass. That table doesn't exist");
var instance = Activator.CreateInstance(tableType) as ITable;
if (instance == null)
throw new TypeLoadException("Idiot. That type isn't a table");
return instance.Insert(new DataRow());
}
public static List<DataRow> Select(List<int> ids, string tableName)
{
var tableType = Type.GetType(tableName);
if (tableType == null)
throw new TypeLoadException("Dumbass. That table doesn't exist");
var instance = Activator.CreateInstance(tableType) as ITable;
if (instance == null)
throw new TypeLoadException("Idiot. That type isn't a table");
return instance.Select(ids).ToList();
}
notice that if you want such a generic solution, the select method (for example) can only return an IEnumerable
\ List
of DataRow
, which can be solved by using the provided Cast
extension method:
var myList = Select(null, "Cars").Cast<Car>();
Note: as you probably know, to instantiate the Cars
class by name, you also need to provide the namespace, which i skipped here, and probably the Table<T>
class should be abstract as well.
Upvotes: 2
Reputation: 144206
One problem is you're trying to insert a DataRow
into a table which takes some subclass of DataRow
, so even if you could compile it, you would still get an exception at runtime.
You need to find the generic row type to insert and insert a new instance of that type:
object instance = Activator.CreateInstance(tableType);
var tableInterface = tableType.GetInterfaces().FirstOrDefault(it => it.IsGenericType && it.GetGenericTypeDefinition() == typeof(ITable<>));
if(tableInterface == null) throw new ArgumentException("Type is not a table type");
var rowType = tableInterface.GetGenericArguments()[0];
var newRow = Activator.CreateInstance(rowType);
MethodInfo insertMethod = tableInterface.GetMethod("Insert");
return (int)insertMethod.Invoke(instance, new object[] { newRow });
However it seems you could make your CreateEmptyRow
method generic in the table and row type and avoid reflection altogether:
public int CreateEmptyRow<TTable, TRow>()
where TRow : DataRow, new()
where TTable : ITable<TRow>, new()
{
var table = new TTable();
return table.Insert(new TRow());
}
Upvotes: 1