views:

175

answers:

3

I use: EntityFramework + POCO

Here is the thing:

public interface IBaseType
{
    int Id { get; set; }
}

public class BaseType : IBaseType
{
    public virtual int Id { get; set; }
}

public class DerivedType : BaseType
{
}

The problem:

public class EntityFetcher<T> where T : BaseType
{
    public object GetById(int id)
    {
        ObjectSet<T> objectSet = (ObjectSet<T>)GetTheObjectSet(typeof(T)); 

        return objectSet.SingleOrDefault((o) => o.Id == id);
    }
}

If T is BaseType this all works perfectly, but: The problem is that in EntityFramework when one class inherits another they share the ObjectSet and, therefore, if T is DerivedType then the GetTheObjectSet returns ObjectSet<BaseType>, which cannot be cast to ObjectSet<DerivedType>.

Is there a way to actually cast this this thing or somehow else execute the SingleOrDefault? Can those things be cast using the IObjectSet<> and IBaseType?

A: 

I checked one of my test projects which is currently far away from buildable state but this worked for me before:

public interface IEntity
{
    int Id { get; }
    byte[] Timestamp { get; set; }
}

public abstract class Entity : IEntity
{
    public int Id { get; set; }
    public byte[] Timestamp { get; set; }
}

public class PocoData : Entity
{
   ...
}

public class Repository<T> : IRepository<T> where T : class, IEntity 
{
    protected ObjectContext Context;
    protected ObjectSet<T> ObjectSet;

    public Repository(ObjectContext context)
    {
        Context = context;
        ObjectSet = context.CreateObjectSet<T>();
    }

    public virtual T GetById(int id)
    {
        return ObjectSet.SingleOrDefault(o => o.Id == id);
    }

    ...
}

The main point is that Entity class is not modeled in EDMX file. All entities modeled in EDMX file has its own Id and Timestamp but my POCO classes use shared base class. I used Repository<PocoData> without any problem.

Ladislav Mrnka
Maybe I forgot to mention - both BaseType and DerivedType are entities that use EntityFramework inheritance. The fact that DerivedType is inherited means that we cannot do CreateObjectSet<DerivedType>, we can only do CreateObjectSet<BaseType> and select DerivedType entities using OfType<DerivedType>. And thus we cannot use ObjectSet<T> within the repository class (as it may be ObjectSet<BaseOf(T)>).
Jefim
+2  A: 

I think you're looking for this:

public T GetById(int id)
{
    ObjectSet<T> objectSet = (ObjectSet<T>)GetTheObjectSet(typeof(T)); 

    return objectSet.OfType<T>().SingleOrDefault((o) => o.Id == id);
}

The OfType method of an ObjectQuery (which ObjectSet derives from) will return objects of the derived type.

Bennor McCarthy
Again, in case of inheritance - GetTheObjectSet will return ObjectSet<BaseOf(T)>. So that GetTheObjectSet(typeof(DerivedType)) == ObjectSet<BaseType>. Thus we cannot cast it to ObjectSet<T> anymore due to the variance issue. The 3rd line of code in your comment will throw an exception in case T is inherited.
Jefim
Have you actually tried it? The OfType<DerivedType> of ObjectQuery is supposed to return derived types from an ObjectQuery<BaseType>. If you are explicitly retrieving only items of the BaseType in your GetTheObjectSet method in a way that stops the Entity Framework from working, that's probably where you're going wrong.
Bennor McCarthy
Yes, I know the OfType<T> method. And I understand the point that you are trying to make. The problem I have is essentially not in EF, but in casting - in my Repository<T> - T is DerivedType, therefore ObjectSet<T> is ObjectSet<DerivedType>. GetTheObjectSet though is a smart method - it understands that DerivedType is inherited from BaseType and returns ObjectSet<BaseType>, which cannot be cast to ObjectSet<DerivedType> (again - line 3). This WILL throw InvalidCast at runtime. Not for the T = BaseType, but for T = DerivedType. Yet your note on ObjectSet : ObjectQuery gave me the solution. Thx!
Jefim
Glad I could help.
Bennor McCarthy
A: 

The answer to this casting problem was as follows:

public T GetById(int id)
{
    // The line that throws InvalidCast with T = DerivedType
    //ObjectSet<T> objectSet = (ObjectSet<T>)GetTheObjectSet(typeof(T));

    // This is a valid cast since ObjectSet<T> is derived from ObjectQuery
    ObjectQuery objectQuery = (ObjectQuery)GetTheObjectSet(typeof(T));
    return objectQuery.OfType<T>().SingleOrDefault((e) => e.Id == id);
}

The solution was to cast ObjectSet to ObjectQuery and do the query there. The most important part here is that ObjectQuery is not generic, so the cast goes through fine.

I must give some credits to Bennor McCarthy as he was the one to point me to OfType + casting to ObjectQuery (the fact that ObjectSet : ObjectQuery). Thanks!

Jefim