Conditionally add query operator on properties defined in non-EDM base type, if inheriting - entity-framework

(C# code at end of question)
I have the following inheritance chain:
PreRecord <- Record <- (multiple entity types)
Record declares a property ID As Integer.
PreRecord and Record are not EDM types, and do not correspond to tables in the database.
I have a method that takes a generic parameter constrained to PreRecord and builds an EF query with the generic parameter as the element type. At runtime, in the event that T inherits not just from PreRecord but from Record, I would like add an OrderBy operator on ID:
'Sample 1
Function GetQuery(Of T As PreRecord)(row As T) As IQueryable(Of T)
Dim dcx = New MyDbContext
Dim qry = dcx.Set(Of T).AsQueryable
If TypeOf row Is RecordBase Then
'modify/rewrite the query here
End If
Return qry
End Function
If the parameter constraint were to Record I would have no problem applying query operators that use the ID property. How can I make use of a different (narrowing) generic constraint mid-method and still return an IQueryable(Of T) / IQueryable<T>, where T is still constrained to PreRecord?
I tried this:
'Sample 2
qry = dcx.Set(Of T).Cast(Of Record).OrderBy(Function(x) x.ID).Cast(Of PreRecord)()
which doesn't work:
LINQ to Entities only supports casting EDM primitive or enumeration types.
C# equivalent:
//Sample 1
public IQueryable<T> GetQuery<T>(T row) where T : PreRecord {
var dcx = new MyDbContext();
var qry = dcx.Set<T>.AsQueryable();
if (row is RecordBase) {
//modify/rewrite the query here
}
return qry;
}
and this doesn't work:
//Sample 2
qry = dcx.Set<T>.Cast<Record>.OrderBy(x => x.ID).Cast<PreRecord>()

The problem here is the fact that compiler checks queries already at compile time and PreRecord class does not have ID property. We cannot use simply Cast, because when it is used in definition of the query parser tries to convert it to sql - but there is no such thing that exists in sql. Sql supports only conversion of one column type to another - so on the .NET side it is supported only for primitive and enum types. To overcome compiler query checking we may use Expression class to build dynamic queries:
ParameterExpression e = Expression.Parameter(typeof(Record));
Expression body = Expression.Property(e, "ID");
Expression<Func<PreRecord, int>> orderByExpression = Expression.Lambda<Func<PreRecord, int>>(body, e);
And use your expression in the query:
qry = dcx.Set<T>.OrderBy(orderByExpression);
This way your linq query will not be validated during compile time but execution time. Here I assumed ID is of type int, if the type is different change it accordingly.

Related

JPA/Hibernate custom query with list of Postgres enums

I have an entity Company with type represented by enum CompanyType.
Database is Postgres and it is represented as enum type there.
I use JPA/Hibernate and its repositories.
Note that I am new to JPA, Hibernate and Kotlin.
I am trying to create custom #Query where I need to select companies where type (the enum) is in list of possible types. I am, however, encountering various error regarding type casting in SQL and/or syntax of the #Query.
Main part of the data class Company in Kotlin (did not copy any other attributes including id):
#Entity(name = "Company")
#Table(name = "company")
data class Company(
#Enumerated(EnumType.STRING)
#NotNull
#Column(name = "type", nullable = false, columnDefinition = "company_type")
val type: CompanyType = CompanyType.OTHER
) : Serializable
Enum CompanyType in Kotlin:
enum class CompanyType(value: Int) {
BUSINESS(1),
TOWN(2),
NONPROFIT(3),
RESEARCH(4),
OTHER(5)
}
Enum company_type in Postgres:
CREATE TYPE public.company_type AS ENUM (
'BUSINESS',
'TOWN',
'NONPROFIT',
'RESEARCH',
'OTHER'
);
CREATE CAST (character varying AS public.company_type) WITH INOUT AS ASSIGNMENT;
JPA Repository with my initial attempt:
#Repository
interface CompanyDAO : PagingAndSortingRepository<Company> {
#Query("SELECT c FROM #{#entityName} c " +
"WHERE c.type IN ?1"
)
fun findAllByTypeIn(types: List<CompanyType>, pageable: Pageable): Page<Company>
}
which compiles but once executed following error occurs:
ERROR: operator does not exist: company_type = character varying
So I tried to cast it, but do not know how exactly...
#Query("SELECT c FROM #{#entityName} c " +
"WHERE c.type IN cast(?1 AS company_type[])"
)
results in error in compile time:
antlr.MismatchedTokenException: expecting EOF, found ')'
and trying
#Query("SELECT c FROM #{#entityName} c " +
"WHERE c.type IN ?1\\:\\:company_type[]"
)
results in
org.hibernate.QueryException: unexpected char: '\'
How to create such query that takes list of enums and returns such entities that have the value equal to any of items in the list?
This error is coming from Postgres:
ERROR: operator does not exist: company_type = character varying
To fix this, you can create a custom operator for company_type in Postgres, and not change anything in your code. Like this:
CREATE FUNCTION ctype_compare(company_type, text)
RETURNS boolean
AS '
select cast($1 as text) = $2;
'
LANGUAGE sql IMMUTABLE;
CREATE OPERATOR = (
leftarg = company_type,
rightarg = text,
procedure = company_type_compare
);
With that, you can in fact remove the #Query and Hibernate will do the right thing. If you can't create custom operator, maybe because you don't have the right permissions, then you have to change your query to this:
#Query("SELECT c FROM #{#entityName} c " +
"WHERE cast (c.type as text) IN ?1")
And then you have to fix your argument type to String.
Page<Company> findAllByTypeIn(List<String> types, Pageable pageable);
And to call your DAO method, you pass the correct type:
List<String> types = new ArrayList();
types.add(CompanyType.OTHER.toString());
types.add(CompanyType.BUSINESS.toString());
Page<Company> companies = dao.findAllByTypeIn(types, Pageable.unpaged());
I did this in Java, not Kotlin. But it should work for you.
Would it help to create the cast as IMPLICIT instead of as ASSIGNMENT so that it is used in comparisons as well?
CREATE CAST (character varying as days) WITH INOUT AS IMPLICIT;

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;

LINQ to Entities dynamic DbSet

Is it possible to specify the DbSet of a LINQ to Entities query at run time?
For example if I have a number of different DbSets that have a common property such as "IsExpired" could I pass the DbSet into the query?
So,
Dim query = From o In db.Products Where o.IsExpired = True
Would look something like,
Dim myDynamicName As String
myDynamicName = "Products"
Dim query = From o In db("myDynamicName") Where o.IsExpired = True
The reason why this is not possible becomes more clear when using fluent syntax:
Dim query = db("myDynamicName").Where(Function(o) o.IsExpired)
The function is a
Function Func(Of In T, Out bool)
and the type of T is infered from the IQueryable Of T that precedes it. (This is possible because Where is an extension method, so the IQueryable is its first input parameter). Thus, the compiler knows that o.IsExpired is a valid expression.
That means that db("myDynamicName") must either be specifically typed, which it isn't, or you must supply the type to the Where method. But that's exactly the thing you're trying to circumvent.

Teaching entity framework generating SQL for custom NodeTypes

Is it possible to customize generation of SQL by using custom Expression inheritors with NodeType Extension and CanReduce = false?
Consider the following gist where Name is a property in the ViewModel and Attribute1 is an attribute in SQL-typed column in MS SQL.
/* may not compile, focus is on SQL generation. */
Expression<Func<ViewModel, bool>> expr = (vm) => vm.Name == "abc" && vm.Attribute1 == 123;
var translator = new CustomExpressionVisitor();
LambdaExpression translated = (LambdaExpression)translator.Visit(expr);
/*
translated has a custom Expression type for vm.Attribute1 == 123 part, it is of type XmlPredicateExpression and this expression has all necessary information to generate an SQL query on this column.
*/
How can we teach EntityFramework to generate SQL for this expression? Is it possible?

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

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.