EF1: Filtering derived types of entity class using .OfType<> by passing a string value - entity-framework

I have a situation where I'm trying to filter a LINQ select using a derived sub class.
ctx.BaseEntity.OfType<SubClass>() - this works fine.
However I'd like to do this using a string value instead. I've come across a performance barrier when I have lots (>20) Sub Classes and selecting an Entity without using OfType just isn't an option. I have a generic UI that renders from the base class, so I don't know what Class Type will be returned at compile time.
So what I'd like to do is this:
Perform a projected Select where I
return just the SubClassType from
the database
Perform a second select
using this value as the OfType to
only select the relevant related
entity from the database (No mass
unions generated)
int id = 1;
var classType = (from c in ctx.BaseClass.Include("ClassType")
where c.id == id
select new
{
c.ClassType.TypeName
}).First();
BaseClass caseQuery = ctx.BaseClass.OfType<classType.TypeName>()
.Include("ClassType")
.Include("ChildEntity1")
.Include("ChildEntity2")
.Where(x => x.id== id);
But obviously this won't work because OfType requires a Type and not a string.
Any ideas on how I can achieve this?
Update:
As a side note to the original question, it turns out that the moment you project a query that uses a Navigation Property - it builds the monster SQL too, so I've ended up using a stored procedure to populate my ClassType entity from the BaseClass Id.

So I've just got it to work using eSQL, which I'd never used before. I've posted the code here just in case it helps someone. Has anyone else got a more strongly typed solution they can think of?
BaseClass caseQuery = ctx.BaseClass.CreateQuery<BaseClass>("SELECT VALUE c FROM OFTYPE(Entities.[BaseClass],namespace.[" + classType.TypeName + "]) as c")
.Include("ClassType")
.Include("ChildEntity1")
.Include("ChildEntity2")
.Where(x => x.id== id).FirstOrDefault();

To answer the headline question about calling OfType with a string / runtime type, you can do the following:
// Get the type, assuming the derived type is defined in the same assembly
// as the base class and you have the type name as a string
var typeToFilter = typeof(BaseClass)
.Assembly
.GetType("Namespace." + derivedTypeName);
// The use reflection to get the OfType method and call it directly
MethodInfo ofType = typeof(Queryable).GetMethod("OfType");
MethodInfo ofTypeGeneric = method.MakeGenericMethod(new Type[] { typeToFilter });
var result = (IQueryable<Equipment>)generic.Invoke(null, new object[] { equipment });
Combine this with your stored procedure to get the class name and you (should?) avoid the massive join - I don't have table-per-type implementation to play with so I can't test.

Related

Exclude derived entities from requests to the base class

I have this DbContext:
public class DataContext : DbContext
{
public DbSet<Base> Bases {get;set}
public DbSet<Sub> Subs {get;set}
}
Sub is a subclass of Base.
When I'm querying the list of Base entities like so:
Context.Bases.ToListAsync()
It returns me every entities, either Base or Sub.
How can I configure my model context to get only the entities that are of Base type and not the ones that derives from it.
The best (or least worst) solution I found is to directly use the shadow property:
Context.Bases.Where(b => EF.Property<string>(b, "Discriminator") == "Base")).ToListAsync();
It works but needs to be repeted now and then, each time I need to query Bases. I'd have prefered a solution in the OnModelCreating method.
I'll accept this answer unless someone else find a better solution.
You'd have to use OfType<T>:
var basesOnly = await _context.Bases.OfType<Base>().ToListAsync();
UPDATE
Sorry, then. I could have sworn the above works, but it doesn't. The next best method I can think of is to simply filter out the types you don't want. It's not ideal, because it requires specifying all subtypes in your query, which then means you need to remember to update it if you add more subtypes.
var basesOnly = await _context.Bases.Where(x => !(x is Sub)).ToListAsync();
How can I configure my model context to get only the entities that are
of Base type and not the ones that derives from it.
You cannot. Every Sub is a Base. So querying all Bases includes all Subs. Eg code like the following must succeed:
Base b = db.Bases.Where(i => i.Id == 1).Single();
if (b is Sub)
begin
Sub s = (Sub)b;
. . .
end
else //other Sub
begin
Sub2 s = (Sub2)b;
. . .
end
You can fetch an anonymous type with the just the base class properties.
And asking this question suggests that inheritance might is not the right modeling technique for your scenario.
If what you want it to fetch the Entities of type Base, but not the subtype Sub, then you can do that with a query like:
var q = from b in db.Bases
where !(b is Sub)
select b;
Which translates to :
SELECT [b].[Id], [b].[Discriminator], [b].[Name], [b].[Size]
FROM [Bases] AS [b]
WHERE [b].[Discriminator] IN (N'Sub', N'Base')
AND NOT ([b].[Discriminator] = N'Sub')
But you can't (currently) exclude all subtypes without enumerating them. Eg this query:
var q2 = from b in db.Bases
where b.GetType() == typeof(Base)
select b;
Will not be completely translated to SQL, and will filter out the subtypes on the client.

Passing an aggregate select expression to Dynamic Linq's GroupBy

I have simplified the following example from my code and hoping there's no obvious compilation errors because of it. Lets say I have the following entities (not what i actually have, please assume I have no EF or schema issues, this is just for example):
public class Company
{
public string GroupProperty {get;set;}
public virtual ICollection<PricingForm> PricingForms {get;set;}
}
public class PricingForm
{
public decimal Cost {get;set;}
}
And I want to query like so:
IQueryable DynamicGrouping<T>(IQueryable<T> query)
{
Expression<Func<Company, decimal?>> exp = c => c.PricingForms.Sum(fr => fr.Cost);
string selector = "new (it.Key as Key, #0(it) as Value)";
IQueryable grouping = query.GroupBy("it.GroupProperty", "it").Select(selector, exp);
return grouping;
}
I get the following error when calling the groupby/select line:
System.Linq.Dynamic.ParseException: 'Argument list incompatible with lambda expression'
What type is "it" when grouped? I have tried using other expressions that assume it is an IGrouping<string, Company>, or a IQueryable<Company>, same error. I've tried just selecting "Cost" and moving the Sum() aggregate into the selector string (i.e. Sum(#0(it)) as Value) and always seem to get the same error.
I eventually tried something along the lines of:
Expression<Func<IEnumerable<Company>, decimal?>> exp = l => l.SelectMany(c => c.PricingForms).Sum(fr => fr.Cost);
However this one, I get farther but when attempting to iterate through the results I got a different error.
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
So, with this dynamic grouping and injecting my own select expression, what should I assume the datatype of 'it' is? Will this even work?
The type of it is IGrouping<TKey, TElement>, where TKey is dynamic based on the keySelector result type, and TElement is the element type of the input IQueryable. Luckily IGrouping<TKey, TElement> inherits (is a) IEnumerable<TElement>, so as soon as you know the input element type, you can safely base selector on IEnumerable<TElement>.
In other words, the last attempt based on Expression<Func<IEnumerable<Company>, decimal?>> is correct.
The new error you are getting is because #0(it) generates Expression.Invoke call which is not supported by EF. The easiest way to fix that is to use LINQKit Expand method:
Expression<Func<Company, decimal?>> exp = c => c.PricingForms.Sum(fr => fr.Cost);
string selector = "new (it.Key as Key, #0(it) as Value)";
IQueryable grouping = query.GroupBy("it.GroupProperty", "it").Select(selector, exp);
// This would fix the EF invocation expression error
grouping = grouping.Provider.CreateQuery(grouping.Expression.Expand());
return grouping;

How to refactor parts of an EF select

Let's say I have the following EF code:
context.Employees.Select(e => e
{
FullName = e.FirstName + " " + e.LastName,
StartDate = e.StartDate,
... // Grab other data
};
Now maybe I notice that I construct the full name in multiple places, but would like in centralized. Is it possible to refactor this?
If I make it a method or a Func, I get EF errors, because it can't translate it into SQL.
NOTE: This is a simple example, assume it can get much more complicated with "Select"s, "Where"s, whatever in the assignment, so adding a ToList and then running additional code would be suboptimal and does not fit the definition of refactoring since I would have to change functionality and not just make it more maintainable.
One solution is to use the AsExpandable method from LinqKit:
Expression<Func<Employee,string>> fullName = e => e.FirstName + " " + e.LastName;
context.Employees.AsExpandable().Select(e => e
{
FullName = fullName.Compile().Invoke(e),
StartDate = e.StartDate,
... // Grab other data
};
From the linked article:
Compile is an inbuilt method in the Expression class. It converts the
Expression into a plain Func which
satisfies the compiler. Of course, if this method actually ran, we'd
end up with compiled IL code instead of an expression tree, and LINQ
to SQL or Entity Framework would throw an exception. But here's the
clever part: Compile never actually runs; nor does LINQ to SQL or
Entity Framework ever get to see it. The call to Compile gets stripped
out entirely by a special wrapper that was created by calling
AsExpandable, and substituted for a correct expression tree.
Alternatively, you could look into creating an Model Defined Function with Entity Framework. There's also the Microsoft.Linq.Translations library if you want to define a FullName property on the Employee class itself.
I think the better centralized way to do it in the entity class itself. You can add ReadOnly property to your entity class which should be NotMapped to the database to return required formatted data.
Public class Employee
{
//...
public string fullName{get { return FirstName + " " + LastName;}}
}

returning an ObjectQuery<T> from the objectQuery using reflection

I have one method which is returning the object of ObjectQuery in this ObjectQuery object the type of object is ObjectQuery, now i want to include table in this object
using reflection, i called the method Include using reflection for this but i m getting the error can someone please tell me the error. here is the sample code.
ObjectQuery objTest = LoadEntitiy(entites,entityClassType);
public ObjectQuery LoadEntitiy(ClientEntities entities, Type entityClasstype)
{
PropertyInfo pi = entities.GetType().GetProperties().First(item => item.Name == entityClasstype.Name.ToString());
Object obj = pi.GetValue(entities, null);
Type objContext = obj.GetType();
return (ObjectQuery)obj;
}
now i m calling the method for including it using the reflection that is here
Type lstType = typeof(ObjectQuery<>);
Type constructedType = lstType.MakeGenericType(typeof(ObjectQuery<>));
MethodInfo addListItemMethod = constructedType.GetMethod("Include");
addListItemMethod.Invoke(objTest, new object[] {"tablename" });
It seems that you want to define a convention to always "Include" a certain set of data.
This convention is normally known as eager loading and there are alternatives like lazy loading.
EF 4.1 or greater already includes functionality to do this for you, see http://msdn.microsoft.com/en-us/library/gg715120(v=vs.103).aspx
Type lstType = typeof(ObjectQuery<>);
Type constructedType = lstType.MakeGenericType(typeof(T(which u want to send as parameter)));
MethodInfo addListItemMethod = constructedType.GetMethod("Include");
object objtest = addListItemMethod.Invoke(objTest, new object[] {"tblname" });
Now the objtest contains the all the table names which you want to include.

Entity Framework Foreign Key Queries

I have two tables in my entity framework, objects, and parameters which have a foreign key pointing to the object to which they belong. I want to populate a tree with all the attributes of a certain object. So in order to find those I want to do this:
String parentObject = "ParentObjectName";
var getAttributes = (from o in myDB.ATTRIBUTE
where o.PARENT_OBJECT == parentObject
select o);
However when I try to do this I get an error saying it cannot convert from type OBJECT to string, even though in the database this value is stored as a string. I have a workaround where I get an instance of the parentObject, then go through every attribute and check whether it's parent_object == parentObjectInstance, but this is much less efficient than just doing 1 query. Any help would be greatly appreciate, thanks!
Well, PARENT_OBJECT.ToString() can't be called (implicitly or explicitly) in L2E, but if it just returns a property, you can look at that directly:
String parentObject = "ParentObjectName";
var getAttributes = (from o in myDB.ATTRIBUTE
where o.PARENT_OBJECT.NAME == parentObject
select o);
...note the .NAME
Try this:
String parentObject = "ParentObjectName";
var getAttributes = (from o in myDB.ATTRIBUTE
where o.PARENT_OBJECT.ToString() == parentObject
select o);