How do I get back a strongly typed collection that queries multiple entities with Castle's ActiveRecord? - nhibernate

I'm trying to get a specific set of data while joining 4 different entities together to do so. What I've done is setup a DTO to try to get this working:
public class LatestThread
{
private readonly string comment;
private readonly DateTime posted;
private readonly string userName;
private readonly int reputation;
private readonly int threadId;
private readonly string topic;
private readonly int userId;
private readonly string avatar;
public LatestThread(string comment, DateTime posted, string userName, int reputation, int threadId, string topic, int userId, string avatar)
{
this.comment = comment;
this.avatar = avatar;
this.userId = userId;
this.topic = topic;
this.threadId = threadId;
this.reputation = reputation;
this.userName = userName;
this.posted = posted;
}
public string Comment
{
get { return comment; }
}
public DateTime Posted
{
get { return posted; }
}
public string UserName
{
get { return userName; }
}
public int Reputation
{
get { return reputation; }
}
public int ThreadId
{
get { return threadId; }
}
public string Topic
{
get { return topic; }
}
public int UserId
{
get { return userId; }
}
public string Avatar
{
get { return avatar; }
}
}
Now I thought I could use SimpleQuery like so:
string hql = string.Format("select new LatestThread(m.Comment, m.Posted, u.UserName, u.Reputation, t.Id, t.Topic, u.Id, u.Avatar) from Thread as t inner join Message as m on t.Id = m.ThreadId inner join User as u on u.Id = m.PostedById inner join Activity as a on a.Id = t.ActivityId where a.Lineage like '{0}%' order by t.LastPosted desc", activityLineage);
return repository.SimpleQuery(0, 10, hql);
My repository method looks like:
public virtual IList<T> SimpleQuery<T>(int firstResult, int maxResults, string hql, params object[] parameters)
{
var query = new SimpleQuery<T>(hql, parameters);
query.SetQueryRange(firstResult, maxResults);
return query.Execute();
}
Now it's asking for me to put [ActiveRecord] at the top of my LatestThread class. When I do that it wants a primary key, and that just seems to be the wrong route.
I've also read bits that refer to the Import attribute given to classes that aren't the DTO. In all the examples though it's just two entities being joined, not the 4 I have. Do I need to add Import to all 4? Or is there something to tell AR that it's a readonly DTO class? OR am I doing this all wrong and there's a really easy way to do what I'm trying to do.
TIA!

Add the Import attribute to your new Thread class
[Import(typeof(LatestThread), "LatestThread")]
[ActiveRecord("Thread")]
public class Thread : ActiveRecordBase<Thread> { /* blah blah */ }
And then, query magic happens :)
string hql = string.Format("select new LatestThread(m.Comment, m.Posted, u.UserName, u.Reputation, t.Id, t.Topic, u.Id, u.Avatar) from Thread as t inner join Message as m on t.Id = m.ThreadId inner join User as u on u.Id = m.PostedById inner join Activity as a on a.Id = t.ActivityId where a.Lineage like '{0}%' order by t.LastPosted desc", activityLineage);
SimpleQuery<LatestThread> query = new SimpleQuery<LatestThread>(typeof(Thread), hql );
LatestThread[] results = query.Execute()
Source : http://www.kenegozi.com/Blog/2006/10/08/projection-using-activerecords-importattribute-and-hqls-select-new-clause.aspx

You can't query a type that isn't mapped (which is what the [ActiveRecord] attribute does). AFAIK you can't get NHibernate to create a new arbitrary object instance like that via HQL (I stand to be corrected if someone knows otherwise).
Your best bet is to do a projection query and then have a method to map the tuples returned into instances of your type.
My answer here shows how to do a projection query and map it to an anonymous type; what you want to do is not much different. You could then put a method to do this in a type-specific repository or a strongly-typed extension method to the generic repository.

Related

Spring Data JPA native query result entity

My native join query produces a new result that is a combination of database tables, so I created a dto for that resulting object (will be a list of records on a screen).
I believe I need to make it an entity, so JPA can recognize it, would that be the best way to do it?
Also, the entity needs an id, and I was hoping to let jpa generate it auto, but I'm getting "Invalid parameter: Unknown column name id. ERRORCODE=-4460, SQLSTATE=null"
My result set contains 4 of the same records instead of 4 different, and I think it has to do with my id field not set properly
Any help would be appreciated on the subject, thanks.
`public interface ErrorCodeRepo extends JpaRepository<Errors, ErrorsPK> {
#Query("SELECT e.transDate, e.category FROM Errors e")
List<QueuedErrors> findQueuedErrors();
}`
DTO class:
`
public class QueuedErrors {
private String transDate;
private String category;
public QueuedErrors(String transDate, String category) {
this.transDate = transDate;
this.category = category;
}
public String getTransDate() {
return transDate;
}
public void setTransDate(String transDate) {
this.transDate = transDate;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
`
When you create navite query which contains results from multiple tables (after joins) you don't have to create new entities.
Better way to solve this problem is to projection with interface or class DTO.
For example, if you want to combine results from Person and Address Entities, simply create interface:
public interface PersonView {
String getFirstName();
String getLastName();
String getStreet();
}
You can see combined fileds from Person (firstName, lastName) and Address (street).
You have to use it as query response, like this:
#Query(...)
List<PersonView> getPersonWithStreet(String state);
You can read more about it here:
https://www.baeldung.com/spring-data-jpa-projections
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections

Execute SQL-LIKE with List attribute

I have this method on my Dao class:
public List<E> search(String key, Object value) {
EntityManager entityManager = getEntityManager();
entityManager.getTransaction().begin();
List result = entityManager.createQuery("SELECT a FROM "+clazz.getSimpleName()+" a WHERE a."+key+" LIKE '"+value+"%'").getResultList();
entityManager.getTransaction().commit();
entityManager.close();
return result;
}
the sql works fine when the attribute is #Column or a #OneToOne`, but when it's something like that:
#OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
#OrderColumn
private List<Titulo> nome;
where the class Titulo has this attributes:
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#Column
private String idioma;
#Column(length=32)
private String conteudo;
causes this error:
message: left and right hand sides of a binary logic operator were incompatible [java.util.List(org.loja.model.categoria.Categoria.nome) : string]; nested exception is org.hibernate.TypeMismatchException: left and right hand sides of a binary logic operator were incompatible [java.util.List(org.loja.model.categoria.Categoria.nome) : string]
How I can change the method to make work for both types of attributes?
I manage to solve this issue with the approach below, using java reflection to detect the type of the field trying to be queried, and using a proper sql command. Don't know how efficient this can be; if anyone have a better solution to this, feel free to add another answer with it.
public List<E> search(String key, Object value) throws NoSuchFieldException {
EntityManager entityManager = getEntityManager();
entityManager.getTransaction().begin();
List result;
Field field = clazz.getDeclaredField(key);
ParameterizedType listType = (ParameterizedType) field.getGenericType();
Class<?> classElement = (Class<?>) listType.getActualTypeArguments()[0];
String nome = classElement.getSimpleName();
Field field2[] = classElement.getDeclaredFields();
String attr = field2[field2.length - 1].getName();
if(field != null) {
if(field2 != null) {
result = entityManager.createQuery("SELECT a FROM "+clazz.getSimpleName()+" a, "+nome+" b WHERE b."+attr+" LIKE '"+value+"%'").getResultList();
} else {
result = entityManager.createQuery("SELECT a FROM "+clazz.getSimpleName()+" a, "+nome+" b WHERE b LIKE '"+value+"%'").getResultList();
}
} else {
result = entityManager.createQuery("SELECT a FROM "+clazz.getSimpleName()+" a WHERE a."+key+" LIKE '"+value+"%'").getResultList();
}
entityManager.getTransaction().commit();
entityManager.close();
return result;
}
UPDATE
I got one issue with the code above: in the first query (of the three in the if/else), it's always returned all the elements of the table, almost if the LIKE was being ignored.

Persistence Entity must not be null

with following code I am getting error Persistence entity must not be null. what could be the mistake.
public interface DistrictRepo extends PagingAndSortingRepository<District, Integer> {
#Query(
"select d.districtId, d.districtName from District d where d.districtId in (:districtIds) group by d.districtId"
)
#RestResource(path="byList")
List<Object[]> byList(#Param("districtIds") List<Integer> districtIds);
}
If you are going to make a "search" method, use this approach:
#Projection(name = "idAndName", types = {District.class})
public interface IdAndName {
Integer getId();
String getName();
}
#RestResource(path="byIds", rel="byIds")
#Query("select d from District d where d.districtId in (:districtIds)")
List<District> findByIds(#Param("ids") Integer... ids);
Then use this url:
http://localhost:8080/api/districts/search/byIds?ids=1,2,3&projection=idAndName
More info about projection
If you need to use complex queries with grouping and aggregation that return DTOs you can not use "search" methods. Instead you have to implement custom controller, for example:
#RepositoryRestController
#RequestMapping("/districts")
public class DistrictController {
#Autoware
private DistrictRepo repo;
#GetMapping("/report")
public ResponseEntity<?> report(#RequestParam(value = "ids") Integer... ids) {
List<Dto> dtos = repo.getDtosByIds(ids);
return ResponseEntity.ok(new Resources<>(dtos));
}
}
Where Dto is something like this:
#Data // This is Lombok annotation (https://projectlombok.org/)
#Relation(value = "district", collectionRelation = "districts")
public class Dto {
private final Integer id;
private final String name;
}
And something like this the repo method:
public interface DistrictRepo extends PagingAndSortingRepository<District, Integer> {
#Query("select new ...Dto(d.districtId, d.districtName) from District d where d.districtId in (:districtIds) group by d.districtId")
#RestResource(path="byList", rel="byList")
List<Dto> getDtosByIds(#Param("ids") Integer... ids);
}
More info.

Does Dynamic Linq support BindingList<T>?

Question
I'm trying to use the Dynamic Linq Sample from Microsoft with BindingList<T> objects. But it looks like the Dynamic Linq will only work with IQueryable. What's the deal here, why doesn't BindingList<T> implement IQueryable. And is there a way around this?
Background Detail: I have many data sets that I need to dynamically filter at run time. Here is an example:
BindingList<MyObject> list = new BindingList<MyObject>();
MyObject selectedObj = list.FirstOrDefault(o => o.Name == "Master P")
// then later ...
MyObject selectedObj = list.FirstOrDefault(o => o.City == "Boston")
I am trying to make these queries dynamic, so the user can choose from all properties of MyObject to use in the query.
There is an Extension method on BindingList; AsQueryable(). So you can use
list.AsQueryable();
But if you want to search on all criteria could you create a search that uses an instance of MyObject as the search criteria and then generated a result set based on the Criteria in the object using standard link.
For example:
public List<MyObject> Search(MyObject SearchCriteria)
{
BindingList<MyObject> list = new BindingList<MyObject>();
list.Add(new MyObject("Test", "Boston"));
list.Add(new MyObject("Test2", "Atlanta"));
IEnumerable<MyObject> results = list.AsEnumerable();
if (!String.IsNullOrEmpty(SearchCriteria.Name))
results = results.Where(l => l.Name.Contains(SearchCriteria.Name));
if (!String.IsNullOrEmpty(SearchCriteria.City))
results = results.Where(l => l.City.Contains(SearchCriteria.City));
return results.ToList();
}
So in the following, Results1 will have 2 results and Results 2 will have only 1.
List<MyObject> results1 = Search(new MyObject("Test", ""));
List<MyObject> results2 = Search(new MyObject("Test", "Boston"));
I used a simple structure for MyObject as an example in this:
public class MyObject
{
public MyObject(string name, string city)
{
this.Name = name;
this.City = city;
}
public string Name { get; set; }
public string City { get; set; }
}

linq to sql -> join

SQL query:
SELECT ArticleCategories.Title AS Category, Articles.Title, Articles.[Content], Articles.Date
FROM ArticleCategories INNER JOIN
Articles ON ArticleCategories.CategoryID = Articles.CategoryID
Object repository:
public class ArticleDisplay
{
public int CategoryID;
public string CategoryTitle;
public int ArticleID;
public string ArticleTitle;
//public string ArticleDate;
public string ArticleContent;
}
public class ArticleRepository
{
private DB db = new DB();
//
// Query Methods
public IQueryable<ArticleDisplay> FindAllArticles()
{
var result = from category in db.ArticleCategories
join article in db.Articles on category.CategoryID equals article.CategoryID
select new
{
CategoryID = category.CategoryID,
CategoryTitle = category.Title,
ArticleID = article.ArticleID,
ArticleTitle = article.Title,
//ArticleDate = article.Date,
ArticleContent = article.Content
};
return result;
}
....
}
And finally, I get this error:
Error 1 Cannot implicitly convert type
'System.Linq.IQueryable'
to
'System.Linq.IQueryable'.
An explicit conversion exists (are you
missing a cast?) C:\Documents and
Settings\ilija\My Documents\Visual
Studio
2008\Projects\CMS\CMS\Models\ArticleRepository.cs 29 20 CMS
Any idea what did I do wrong?
Thanks,
Ile
Your method has a return type of IQueryable<Article>, but your LINQ query is not returning articles. Instead it's trying to return an anonymous type made up of properties from both category and article. You could create a class to hold this combination of properties and change the method signature to use that class.
As Adam said, if you're not wanting to return the full article class then you'll need to return your own class.
public IQueryable<ArticleDisplay> FindAllArticles()
{
var result = from category in db.ArticleCategories
join article in db.Articles on category.CategoryID equals article.CategoryID
select new ArticleDisplay() //specify your new class
{
CategoryID = category.CategoryID,
CategoryTitle = category.Title,
....
};
return result;
}
Your updated code still returns an anonymous class....