I have following #Query, that is perfectly working fine. But now I have a scenario where there screen needs a filter, that will add some where clauses to the query.
#Query
("
SELECT
ef, ed, ea
FROM EntityA ea
JOIN EntityB eb
JOIN EntityC ec
JOIN EntityD ed
JOIN EntityE ee
JOIN EntityF ef
WHERE
TRUNC(ee.date) = TRUNC(:date)
-- conditions based on screen filter parameters
AND ef.amount = :amount
AND LOWER(ec.name) LIKE LOWER('%' || :name || '%')
AND ec.projectId = :projectId
AND ed.divisionId = :divisionId
")
Found that there is a good Specifications support to dynamically create the queries as per requirement.
But not sure how do I select multiple objects ef, ed & ea in one go using Specifications, otherwise I have to write 4 more queries to return result based on filter criteria.
N.B. Not using eager loading for performance reasons as entities are used by multiple services.
Specifications are only used for dynamically creating a where clause.
If you also need to control the select clause I recommend using the JPA Criteria API inside a custom method of your repository.
I was able to achieve this by implementing Custom Repositories, auto-wiring the EntityManager in that implementation class and then building the final JPQL based on parameters passed. A good example is in Eugen's blog.
Previously I had following structure
public interface EntityARepository extends JpaRepository<EntityA, Long> {
#Query(...)
List<EntityA> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId);
}
public interface EntityAService {
List<Object[]> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId);
}
#Service
public class EntityAServiceImpl implements EntityAService {
#Autowired
EntityARepository entityARepository;
#Override
public List<Object[]> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId) {
...
...
...
}
}
And by using Custom Repositories all becomes like
public interface EntityACustomRepository {
List<Object[]> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId);
}
public interface EntityARepository extends JpaRepository<EntityA, Long> {
//#Query(...)
//List<EntityA> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId);
}
#Repository
public class EntityACustomRepositoryImpl implements EntityACustomRepository {
// autowiring entityManager helped to create and execute dynamic jpql
#Autowired
EntityManager entityManager;
public List<Object[]> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId) {
String jpql = "SELECT " +
" ef, ed, ea " +
" FROM EntityA ea " +
" JOIN EntityB eb " +
" JOIN EntityC ec " +
" JOIN EntityD ed " +
" JOIN EntityE ee " +
" JOIN EntityF ef " +
" WHERE " +
" TRUNC(ee.date) = TRUNC(:date) "
;
//conditions based on screen filter parameters
if(amount!=null && amount>0L) {
jpql += " AND ef.amount = :amount ";
}
if(name!=null && name.trim().length()>0) {
jpql += " AND LOWER(ec.name) LIKE LOWER('%' || :name || '%') ";
}
if(projectId!=null && projectId>0L) {
jpql += " AND ec.projectId = :projectId ";
}
if(divisionId!=null && divisionId>0L) {
jpql += " AND ed.divisionId = :divisionId ";
}
Query query = entityManager.createQuery(jpql);
query.setParameter("date", filterDate);
if(amount!=null && amount>0L) {
query.setParameter("amount", amount);
}
if(name!=null && name.trim().length()>0) {
query.setParameter("name", name);
}
if(projectId!=null && projectId>0L) {
query.setParameter("projectId", projectId);
}
if(divisionId!=null && divisionId>0L) {
query.setParameter("divisionId", divisionId);
}
return query.getResultList();
}
}
public interface EntityAService {
List<Object[]> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId);
}
#Service
public class EntityAServiceImpl implements EntityAService {
#Autowired
EntityARepository entityARepository;
#Override
public List<Object[]> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId) {
return entityARepository.findAllBy(filterDate, amount, name, projectId, divisionId);
}
}
It is possible to query on child entities using the Specification API if you map them with JPA, e.g. with #OneToMany.
#Entity
#Table(...)
public class EntityA {
// Omitting fields
#OneToMany(...)
private List<EntityB> bList = new ArrayList<>();
}
public class EntityASpecification implements Specification<EntityA> {
private SearchCriteriaValueClass criteria;
public EntityASpecification(SearchCriteriaValueClass criteria) {
this.criteria = criteria;
}
#Override
public Predicate toPredicate(Root<EntityA> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
ListJoin<EntityA, EntityB> pathToEntityB = root.join(EntityA_.bList, JoinType.INNER);
Predicate amountInBIsEqual = builder.equal(pathToEntityB.get(EntityB_.amount), criteria.getAmount);
Path<SomeEntityAField> pathToAField = root.get(EntityA_.someAField);
Predicate someValueInA = pathToAField.in(criteria.getCollectionForAFieldToBeIn());
query.distinct(true);
return builder.and(amountInBIsEqual, someValueInA);
}
}
Obviously a Specification is defined for one Entity and can only return instances of that. I don't see any (safe and effective) way of returning instances of F, D and A in one method call other than them being connected in a has-a relationship.
If I have this as my TPT model:
public class Foo
{
public Int32 Id { get; set; }
public string Text { get; set; }
}
[Table("Bars")]
public class Bar : Foo
{
public string MoreText { get; set; }
}
and a derived DbContext like this:
public class MyContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
}
Then when I execute a query on Foo the final SQL will include an outer join to Bar.
For example:
uisng(var context = new MyContext())
{
Console.WriteLine(context.Foos.ToString());
}
will result in this as the final SQL Statement
SELECT
CASE WHEN ( NOT (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL))) THEN
'0X' ELSE '0X0X' END AS [C1],
[Extent1].[Id] AS [Id],
[Extent1].[Text] AS [Text],
CASE WHEN ( NOT (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL))) THEN
CAST(NULL AS varchar(1)) ELSE [Project1].[MoreText] END AS [C2]
FROM [dbo].[Foos] AS [Extent1]
LEFT OUTER JOIN (SELECT
[Extent2].[Id] AS [Id],
[Extent2].[MoreText] AS [MoreText],
cast(1 as bit) AS [C1]
FROM [dbo].[Bars] AS [Extent2] ) AS [Project1] ON [Extent1].[Id] = [Proj
ect1].[Id]
I do understand why --- doing so allows me to do something like:
foreach(var x in context.Foos)
{
if(x is Bar) Console.WriteLine("Impressive");
else Console.WriteLine("Not so much");
}
However as you you can imagine this kind of query over base classes can quickly result in nightmarish queries for SQL Server to process. Hence the question.
Is it possible to inform Linq to EF 5.0 that it should bring back only the base type and not the derived type. therefore making the final SQL much simpler?
Not with Linq to Entities. Linq to entities offers OfType but it uses .NET rules for type inheritance so if you use OfType<Foo> you will still get both Foo and Bar instances because Bar is of type Foo.
Entity SQL (which is not available in code first and DbContext API - you must access it through ObjectContext API) is more powerful because it is not dependent on limited functionality offered by Linq. It offers construct OFTYPE ONLY which can return only instances of Foo but I believe it will not make the query simpler because to find which types are only Foo and not Bar it still have to make a join. If you expect to get instance of Foo even if the object is Bar you will not achieve that with EF - entity type is immutable. You cannot change the type in query.
The simplest workaround to use OfType and Linq is to define Foo as abstract and add additional derived type. You will always query a real type with OfType and it should ensure that you don't have joins with unnecessary tables.
I have two tables as follows:
Departments:
DeptID (PK)
DeptName
...
Employees:
EmpID (PK)
DeptID (FK)
EmpName
...
and I have a query using LINQ as follows:
public List<Employee> GetEmployee(int deptID)
{
var query = from e in mdc.Employees
join d in mdc.Departments on e.DeptID equals d.DeptID
where e.DeptID == deptID
select new { e.EmpID, e.EmpName, d.DeptName };
return query.ToList();
}
Now my question is this. I would like to select the fields EmpID, EmpName, and DeptName.
But what will my return type be? This one returns an error because this query returns a GenericList as opposed to my List<Employee>.
You need to create another class with required properties like this,
public class NewType{
public EmpID{get;set;}
//other fields here
}
and then select ,
public List<NewType> GetEmployee(int deptID)
{
var query = from e in mdc.Employees
join d in mdc.Departments on e.DeptID equals d.DeptID
where e.DeptID == deptID
select new NewType{ e.EmpID, e.EmpName, d.DeptName };
return query.ToList();
}
I'm getting started with JPA and I want to know the best way to achieve something like this:
I need to implement a service that returns a list of scripts, and each script has a list of parameters. I simplified the query, but it is something like this:
(SELECT
p.DESC
FROM
INPUT_PARAMETERS p
INNER JOIN SCRIPT_PARAMS sp ON p.PARAM_ID = sc.PARAM_I
INNER JOIN SCRIPT s ON s.SCRIPT_ID = sc.SCRIPT_ID
WHERE
s.NAME = 'name')
UNION
(SELECT
p.DESC
FROM
OUPUT_PARAMETERS p
INNER JOIN SCRIPT_PARAMS sp ON p.PARAM_ID = sc.PARAM_I
INNER JOIN SCRIPT s ON s.SCRIPT_ID = sc.SCRIPT_ID
WHERE
s.NAME = 'name')
And I want to return a list of POJO objects that are something like:
public class Script {
private String name;
private List<String> params;
public Script(){}
public String getName()
{
return name;
}
public void setName(String pName)
{
name = pName;
}
public List<String> getParams()
{
return params;
}
public void setParams(List<String> pParams)
{
params = pParams;
}
}
I want to know what is the best way to load the POJO object from a query. Is it best to build a JPQL query, or can I use a Native Named Query, and do I need to get a object[] and construct my POJOs manually, or can I use JPA to load the objects from the query?
You can only construct instances of Script manually. When querying with JPQL, reason is that constructor expression cannot take List as argument. Additionally JPQL does not have unions.
Also with SQL query you cannot construct Script directly, because result can be only scalars or entity.
I have 3 tables in my testdatabase, Customers <-> CustomersUsergroups <-> Usergroups
For customers I have a method that returns all Usergroups like this:
public IQueryable<Usergroup> GetUsergroups()
{
return from ug in _entities.UsergroupSet.Include("Customer")
select ug;
}
And to that I have a "filter-class" for class Usergroup
public static IQueryable<Usergroup> ByUsergroupID(this IQueryable<Usergroup> qry, int usergroupID)
{
return from ug in qry
where ug.UsergroupID == usergroupID
select ug;
}
so when I type:
return _repository.GetUsergroups().ByUsergroupID(usergroupID);
it works great, but the problem now is that I would like to extend so I can filter by CustomerID aswell, sort of like:
public static IQueryable<Customer> ByCustomerID(this IQueryable<Customer> qry, int customerID)
{
return from c in qry
where c.CustomerID == customerID
select c;
}
so I can use it like:
return _repository.GetUsergroups().ByCustomerID(customerID);
is that possible to do with just using "Include"? or isnt it an easy way to fix that since they are different classes?
Thanks in advance
/M
I'm not sure if I understand problem the way you described, but it is normal that you can't use method ByCustomerID because you defined extension on IQueryable<Customer> not on IQueryable<UserGroup>.
If you want to define it on IQueryable<UserGroup> and if you intended to get UserGroups assigned to one Customer that can be done like this:
public static IQueryable<UserGroup> ByCustomerID(this IQueryable<UserGroup> qry, int customerID)
{
return from ug in qry
from c in ug.Customer
where c.CustomerID == customerID
select ug;
}