tags:

views:

51

answers:

5

Hey all,

I'm currently starting a new project and I've run into a bit of a readblock. I'm hoping someone can help me out and I'll do my best to describe the problem.

I have a base abstract class called "EntityBase". From this class there are around 100 or so inherited classes. EntityBase has a number of methods such as Load() and Save() that are common to all my inherited classes. It also has a couple of constructors that accept either an integer or an IDataReader which are used to load the object from the database.

That's all working quite well.

Enter my new base class, named EntityCollectionBase which extends List<EntityBase>. I'm trying to write a Load function for it but I'm not sure how to proceed. Hopefully this bit of code can better illustrate my goal:

 public bool Load()
 {
     bool result = true;

     using (IDataReader reader = _dbManager.ExectureReaderSProc(this.LoadProcedure, new SqlParameter[] { new SqlParameter("@parentId", _parentID) }))
     {
         this.Add(new EntityBase(reader)); // WON'T WORK, EntityBase IS ABSTRACT
     }

     return result;
 }

As you can see, I need the Load function to work in a generic manner to handle anything extending EntityBase, but because EntityBase is abstract, I cannot instanciate it.

Any ideas?

Thanks,
Sonny

A: 

I would perhaps go for a generic collection class contrained to EntityBase, and then you could instantiate and add objects in the following manner. However, this code assumes you have a constructor which accepts an integer parameter. If it does not, this code will properly blow up.

class EntityCollectionBase<T> : List<T> where T : EntityBase
{
    public void Load()
    {
        // example
        int someId = 14;
        T t = (T)Activator.CreateInstance(typeof(T), someId);
        this.Add(t);
    }
}

A safer approach would be to further constrain your T to have a parameterless constructor, and have methods* in your base class that would load from an integer or DataReader (*or abstract in the base, implemented in the derived).

class EntityCollectionBase<T> : List<T> where T : EntityBase, new()
{
    public void Load()
    {
        // example
        int someId = 14;
        T t = new T();
        t.Load(someId);
        this.Add(t);
    }
}
Anthony Pegram
+1  A: 

You'll need to use reflection to access a constructor taking an IDataReader. Your current example also only loads one item, which probably isn't what you want when loading a collection:

public class EntityCollectionBase<T> where T : EntityBase
{
    public void Load()
    {
        var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IDataReader) });
        using(IDataReader reader = ...)
        {
            while(reader.Read())
            {
                T entity = (T)constructorInfo.Invoke(new object[] { reader });
                this.Add(entity);
            }
        }
    }
}

I'd consider making this static since the collection could be left in an invalid state if the IDataReader throws an exception while being loaded:

public static EntityCollectionBase<T> Load() { ... }
Lee
Good catch. I meant to have the while loop in my original example.
Sonny Boy
This worked once I changed the class definition to class EntityCollectionBase<T> : List<T> where T : EntityBase
Sonny Boy
A: 

Give your EntityBase a void SetReader(IDataReader) method, and make sure all your entities have a default constructor.

public bool Load<T>() where T : EntityBase, new()
{
    ...
    using (IDataReader reader = _dbManager.ExectureReaderSProc(
        this.LoadProcedure, new SqlParameter[] {new SqlParameter("@parentId", _parentID) })) {
        EntityBase entity = new T();
        entity.SetReader(reader);
        this.Add(entity);
    }
    ...
}
Mark H
A: 

Maybe this works. But I prefer factory class instead

public class EntityCollectionBase<T> : List<T> where T : EntityBase, new()
{
    public EntityBase Load()
    {
        return new T().Create(10);
    }
}

public abstract class EntityBase
{
    public abstract EntityBase Create(int a);
}
Fujiy
+1  A: 

Whenever you create an instance of one of several possible classes based on runtime data, a factory is the answer:

public interface IEntityFactory
{
    EntityBase CreateEntity(IDataReader reader);
}

You would modify the loading class to accept one:

private readonly IEntityFactory _entityFactory;

public Loader(IEntityFactory entityFactory)
{
    _entityFactory = entityFactory;
}

Then you would use the factory in the Load method:

public void Load()
{
    using(var reader = _dbManager.ExectureReaderSProc(this.LoadProcedure, new SqlParameter[] { new SqlParameter("@parentId", _parentID) }))
    {
        Add(_entityFactory.CreateEntity(reader));
    }
}
Bryan Watts
Thanks. Much cleaner than using the reflection and generics methods (though both worked).
Sonny Boy