Reputation: 3
I'm working on a very simple framework, where I want to automate CRUD functions. For the purpose of this question, I've created the code below, simplified just to illustrate my issue(s).
All items derive from the (abstract) base class "DbItem" (which contains the CRUD functions), whereby the child classes provide additional functionality, and also define the table names where the DbItems are stored. For instance, "Equipment" and "Entity" both derive from DbItem, and define table names ("equipment" and "entities" respectively). However, "Entity" class is abstract and further derived by "Human" and "Animal" class. (All Humans and Animals are stored in a shared "entity" table, but equipment is stored in a separate table.)
This part of code works. The Save() method defined in DbItem properly resolves the DbTable property.
However, then I also have a DbCollection class, which extends the generic C# Collection class. I wish for the DbCollection to be able to automatically determine the correct database table name with reflection.
Hence, if I want to create a list of all equipment, I create a new DbCollection, and the code composes the appropriate SELECT statement (SELECT ... FROM equipment ...). This works.
However, if I want a list of all entities (animals and humans), I have an issue. "Entity" is marked as abstract, and I should not be able to instantiate it. However, I also do not know how to get the value of Entity.DbTable property.
A simple "fix" is to remove the "abstract" qualified from the Entity class definition. This however does not sound right to me.
Can you please let me know how can I get the value of Entity.DbTable property?
class Program
{
abstract class DbItem
{
public int Id { get; set; }
public abstract string DbTable { get; }
public void Save()
{
Console.WriteLine($"INSERT INTO {DbTable} (Id) VALUES ({this.Id})...");
}
}
abstract class Entity : DbItem
{
public sealed override string DbTable { get => "entities"; }
}
class Human : Entity { }
class Equipment : DbItem
{
public override string DbTable => "equipment";
}
class DbCollection<T> : System.Collections.ObjectModel.Collection<T>
{
public virtual string DbTable { get
{
Type t = typeof(T);
//System.Reflection.PropertyInfo p = t.GetProperty("DbName");
if(t.IsAbstract)
{
// What do we do here to get the value of Entity.DbTable?
var prop = t.GetProperty("DbTable");
// Herein lies the problem: One cannot instantiate an abstract class to provide to the GetValue() method
return prop.GetValue(null).ToString(); // System.Reflection.TargetException: 'Non-static method requires a target.'
}
else
{
var obj = Activator.CreateInstance(t);
//return (obj as DbItem).DbTable; // this also works
var prop = t.GetProperty("DbTable");
return prop.GetValue(obj).ToString();
}
}
}
public DbCollection()
{
Console.WriteLine($"SELECT Id FROM {DbTable} WHERE ...");
}
}
static void Main(string[] args)
{
var h = new Human();
h.Save(); // 1. Correctly outputs "entities";
var e = new Equipment();
e.Save(); // 2. Correctly outputs "equipment";
var ec = new DbCollection<Equipment>(); // 3. Correctly outputs "equipment"
var hc = new DbCollection<Human>(); // 4. Correctly outputs "entities"
var entityCollection = new DbCollection<Entity>(); // 5. Error.
Console.ReadLine();
}
}
Upvotes: 0
Views: 3213
Reputation: 52210
Don't use a property, use an attribute. That's what you want, right? To associate a class with a table name that is fixed at compile time?
First, create a custom attribute class that stores a table name:
[AttributeUsage(AttributeTargets.Class | Inherited = true)]
public class DbTableAttribute: System.Attribute
{
private readonly string _name;
public string Name { get { return _name; } }
public DbTableAttribute(string name)
{
_name = name;
}
}
Add it to your human/animal/entity/DbItem classes:
[DbTable("entities")]
abstract class Entity : DbItem
{
//Don't need this any more
//public sealed override string DbTable { get => "entities"; }
}
And retrieve it like this:
public string GetDbTable<T>() where T : DbItem
{
var attr = typeof(T).GetCustomAttributes(
typeof(DbTableAttribute), true
).FirstOrDefault() as DbTableAttribute;
return attr?.Name;
}
Upvotes: 1
Reputation: 2113
What do you expect to happen in your fifth case?
There is absolutly no way to create an instance of an abstract
class. remove the abstract
keyword from your Entity class. If you want to prevent external creation of the Entity
class you could use an internal
constructor.
Since this is an collection you might also use the first entry of the collection to get the DbTable
result - might be dangerous since the second item could be of another type.
Upvotes: 0