Sonny Boy
Sonny Boy

Reputation: 8016

Creating a collection for a base type with methods that will work on all inherited objects?

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

Upvotes: 2

Views: 227

Answers (5)

Bryan Watts
Bryan Watts

Reputation: 45465

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));
    }
}

Upvotes: 1

Felipe Pessoto
Felipe Pessoto

Reputation: 6969

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);
}

Upvotes: 0

Anthony Pegram
Anthony Pegram

Reputation: 126932

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);
    }
}

Upvotes: 0

Mark H
Mark H

Reputation: 13907

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);
    }
    ...
}

Upvotes: 0

Lee
Lee

Reputation: 144176

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() { ... }

Upvotes: 1

Related Questions