views:

114

answers:

2

Hi all

I have a generic CRUD class to perform add, delete, select, create to my entity objects.

one of them - Message has two derived classes - order_message , and report_message.

My problem is that in my generic class, I need an objectset to perform crud ops, but objectset does not accept a derived class type, it only accept base class type.

This is the error I received:

There are no EntitySets defined for the specified entity type 'CustomerWebPortal_Entities.Order_Message'. If 'CustomerWebPortal_Entities.Order_Message' is a derived type, use the base type instead.

I tried use typeof(T).BaseType to replace T, and of cause was not working.

How should I correct this?

This is the overview of the generic class:

 public abstract class baseCrudDao<T> : ICrudDao<T> where T : class
{
    private System.Data.Objects.ObjectContext _context;

    private System.Data.Objects.ObjectSet<T> _entity; 
    public baseCrudDao()
    {

        _context = new CustomerWebPortalEntities();
        _entity = _context.CreateObjectSet<T>(); <-- error at here, only accept base type

    }
A: 

One work-around will be to use two generic type parameters (on base class) such as

public abstract class baseCrudDao<T, U> : ICrudDao<T> where U : class, T: U
{
    ...
    public baseCrudDao()
    {
        _context = new CustomerWebPortalEntities();
        _entity = _context.CreateObjectSet<U>(); <-- error at here, only accept base type
    }
    ...
}

Use Message in place of U. If you have many types and not all have inheritance relationships then constraint T:U won't work and you need to perhaps use T:class constraint. I will also suggest that you create another skeleton base class to be used for other types such as

public abstract class base2CrudDao<T> : baseCrudDao<T, T>

so that you don't have to specify U parameter for classes that don't have inheritance relationship.

VinayC
A big downside to this approach is that you now rely on consumers of the crud class to be aware of which base class to use and when. The fact that EF requires this decision is a provider-specific behavior that the author of the crud class probably wants to hide in the first place.
Michael Petito
I agree. I had think of alternate solution for that but it's bit ugly - first define the class with two generic parameters as shown above. Then create a wrapper class with one generic parameter - this class would create the instance of first generic class using reflection and by passing base type of T as U type parameter . All methods on this calls will be forwarded to instance that we have created.
VinayC
Is it possible, in the baseCrudDao, convert the T to T's base class type, if T is detected it is not a base class? does such code exist in C#?
Bryan Fok
@Bryan, what I was saying is that write a wrapper class A<T> that will create instance of class B<T, U> using reflection (see http://msdn.microsoft.com/en-us/library/system.type.makegenerictype.aspx) i.e. Activator.CreateInstance(typeof(A).MakeGenericType(typeof(T), typeof(T).BaseType)).Now methods of B will fwd calls to above instance. Unfortunately, it won't work as you have abstract classes here.
VinayC
Another approach would be to define a class that implements the interface for `IObjectSet<T>` for derived classes that wraps an instance returned by `CreateObjectSet`. Then you could use reflection to determine whether or not you can use the instance returned by `CreateObjectSet` or if you need to wrap it using the new class. This new class would be responsible for calling the methods of the inner object set (which is of the base type) and casting down to the derived class where appropriate.
Michael Petito
@Michael, +1 - IMO, this should work. Why don't you put it as answer with a template solution.
VinayC
Hi Michael, a template solution would be very nice.
Bryan Fok
+3  A: 

Well I finally had a chance to write a prototype as suggested. I think that something like this would work but I haven't tested it. Now all of your crud methods can be defined against the IObjectSet<> member.

public class Crud<EntityType> where EntityType : class
{
    private readonly ObjectContext Context;
    private readonly IObjectSet<EntityType> Entities;

    public Crud(ObjectContext context)
    {
        Context = context;

        Type BaseType = GetBaseEntityType();

        if (BaseType == typeof(EntityType))
        {
            Entities = Context.CreateObjectSet<EntityType>();
        }
        else
        {
            Entities = (IObjectSet<EntityType>)Activator.CreateInstance(typeof(ObjectSetProxy<,>).MakeGenericType(typeof(EntityType), BaseType), Context);
        }
    }

    private static Type GetBaseEntityType()
    {
        //naive implementation that assumes the first class in the hierarchy derived from object is the "base" type used by EF
        Type t = typeof(EntityType);

        while (t.BaseType != typeof(Object))
        {
            t = t.BaseType;
        }

        return t;
    }
}

internal class ObjectSetProxy<EntityType, BaseEntityType> : IObjectSet<EntityType>
    where EntityType : BaseEntityType
    where BaseEntityType : class
{
    private readonly IObjectSet<BaseEntityType> Entities;

    public ObjectSetProxy(ObjectContext context)
    {
        Entities = context.CreateObjectSet<BaseEntityType>();
    }

    public void AddObject(EntityType entity)
    {
        Entities.AddObject(entity);
    }

    //TODO: implement remaining proxy methods

    public IEnumerator<EntityType> GetEnumerator()
    {
        return Entities.OfType<EntityType>().GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public Type ElementType
    {
        get { return typeof(EntityType); }
    }

    public Expression Expression
    {
        get { return Entities.OfType<EntityType>().Expression; }
    }

    public IQueryProvider Provider
    {
        get { return Entities.Provider; }
    }
}
Michael Petito
+1 Seems like the best solution. Although my version creates the ObjectSet instance only. Otherwise you'd have to Invoke the CreateObjectContext method with reflection.
Casey Burns