0

I am trying to create reusable search queries on my base class so i don't have to have the same code repeated for each dervived class but i cannot get entity framework to play nicely.

I have 3 classes: CMEntityBase CMSite CMSiteServer

Site & SiteServer are both derived from CMEntityBase which has common properties (ID, name, etc)

I want to define some generic searches: e.g. GetByName

using (var db = new LNOSCMDataModel())
            {
                db.Configuration.LazyLoadingEnabled = false;
                var servers = db.CMSiteServers.
                    AsNoTracking().
                    GetByName(id,Active).
                    ConvertToAPIVM().ToList();
}

i have tried defining GetByName several ways:

base class:

    public static IQueryable<CMEntityBase> GetByName(this IQueryable<CMEntityBase> Entities, string Name, bool Active = true)
    {
        return Entities.Where(ss => ss.Name == Name && ss.Active == Active || Active == false);//.Cast<IEntity>();
    }

generics:

public static IQueryable<T> GetByName<T>(this IQueryable<CMEntityBase> Entities, string Name, bool Active = true) where T : CMEntityBase
        {
            return Entities.Where(ss => ss.Name == Name && ss.Active == Active || Active == false).Cast<T>();
        }

i've tried defining the base class as an interface and in the generic using T : class, IEntity (interface) --> this approach came from: LINQ to Entities only supports casting EDM primitive or enumeration types with IEntity interface

ultimately they all return the error:

LINQ to Entities only supports casting EDM primitive or enumeration types with IEntity interface.

ultimately i want to define a query on the base class properties but output the child class. right now it appears i need to copy/paste my methods per derived class.

Justin
  • 1,303
  • 15
  • 30

2 Answers2

0

Rather than accepting an IQueryable of a different type than what you want, and trying to cast it (which, as the error indicates, is not supported) you simply need to accept an IQueryable of the actual type that your query already is, so that there is no need to cast it. In this case it's as simple as using the generic type in the original query, rather than the base type:

public static IQueryable<T> GetByName<T>(this IQueryable<T> Entities, string Name, bool Active = true)
    where T : CMEntityBase //or the interface that specifies the needed members
{
    return Entities.Where(ss => ss.Name == Name && ss.Active == Active || Active == false);
}
Servy
  • 202,030
  • 26
  • 332
  • 449
-1

After a lot of experimenting, the solution was to create the base class as abstract

public abstract class CMEntityBase
{

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public abstract decimal ID { get; set; }


    [StringLength(50)]
    public abstract string Name { get; set; }

    ....
}

define my extension in a static extension class, the key here is to use .Select( e=> e as T) to convert it back to the child class.

public static partial class CMEntityBaseExtensions
{
    public static IQueryable<T> GetByName<T>(this IQueryable<T> Entities, string Name, bool Active = true) where T : CMEntityBase
    {
        return Entities.Where(ss => ss.Name == Name && ss.Active == Active || Active == false).
                 Select(e => e as T); // cast back to child!
    }
}

then i can use it in my controller:

 var servers1 = db.CMSiteServers
                .AsNoTracking().
                GetByName(id, Active);

and event use my 'casting' function to convert to a view model

            var servers = servers1.
                ConvertToAPIVM().ToList();

which looks like this:

public static partial class CMSiteServerExtensions
{
    public static IQueryable<CMSiteServerAPIVM> ConvertToAPIVM(this IQueryable<CMSiteServer> Servers)
    {
        return Servers.Select(ss => new CMSiteServerAPIVM()
        {
            SiteServerID = ss.ID,
            Name = ss.Name,
            Description = ss.Description,
            ...
        }
    }
 }
Justin
  • 1,303
  • 15
  • 30
  • I haven’t downvoted you, but I doubt that your bold text is true as the query is already of type T, so there is no need for the cast as all it does is casting T to T. – ckuri Dec 11 '18 at 02:08
  • i'll need to review again, i remember there was some problem at the time but it was long enough ago i don't remember Why it was necessary as it shouldn't have been. – Justin Jan 07 '19 at 20:18