Query one to many relationship Android Room - android-sqlite

I have a one to many relationship. One Parent can hold many Child entities.
The children have the property date, which is of type Instant.
I have made a class which combines these two entities:
data class Combined(
#Embedded val parent: Parent,
#Relation(
parentColumn = "elementId",
entityColumn = "Id"
)
val children: List<Child>
)
I know that I can retrieve all Combined elements like so:
#Transaction
#Query("SELECT * FROM Parent")
fun getCombined(): Flow<List<Combined>>
Is there any way to retrieve a List<Combined> where only the children within a certain date range is included?

Is there any way to retrieve a List where only the children within a certain date range is included?
Not that easily. #Relation works by getting ALL the children of the parent based upon a second query (hence why #Transaction is recommended). Filtering only applies to the selection of the parent.
The problem is that if you use something like :-
SELECT * FROM parent JOIN child ON child.Id = parent.elementId WHERE date BETWEEN fromDate and toDate;
What is returned is a list where each row contains the parent and the single child. To extract the parent and all the filtered children requires the list to then be processed to single out the parent's with it's children.
Based upon the available code, here's an example.
One exception is the Id column which would typically be the ID of the child, so a parentId columns has been added that hold the ID of the parent.
Another exception is that the date, for convenience/brevity, is just a String (TEXT) rather than Instant which requires an #TypeConverter.
A Third exception is that a companion object has been used (as will be shown in due course).
First a new POJO, Filter for the extraction of the filtered list (a row for each parent and child)
data class Filter(
#Embedded
val parent: Parent,
#Embedded
val child: Child
)
Next a suitable Dao getFiltered for the query (pretty much as above) :-
#Query("SELECT * FROM parent JOIN child ON child.parentId = parent.elementId WHERE date BETWEEN :fromDate AND :toDate")
abstract fun getFiltered(fromDate: String, toDate:String): List<Filter>
Note that this returns a List of Filters NOT a List of Combineds
Last the Child with the companion object to facilitate building a List of Combineds from the List of Filters:-
#Entity
data class Child(
#PrimaryKey
var id: Long? = null,
var parentId: Long,
#NotNull
var childName: String,
var date: String
) {
companion object {
fun buildParentsWithChildren(filter: List<Filter>): List<Combined> {
var rv: ArrayList<Combined> = arrayListOf()
if (filter.size < 1) return rv
for (f: Filter in filter) {
addChild(rv, getOrAddParent(rv, f), f)
}
return rv
}
private fun getOrAddParent(built: ArrayList<Combined>, f: Filter): Int {
for (i in 0..(built.size-1)) {
if (built.get(i).parent.parentName == f.parent.parentName) {
return i
}
}
var newCombined: Combined = Combined(f.parent, emptyList())
built.add(newCombined)
return built.size - 1
}
private fun addChild(built: ArrayList<Combined>, parentIx: Int, f: Filter) {
var currentChildren: ArrayList<Child> = arrayListOf<Child>()
currentChildren.addAll(built.get(parentIx).children)
currentChildren.add(f.child)
built[parentIx] = Combined(parent = built.get(parentIx).parent, currentChildren)
}
}
}
Example
Here's an example of using the above.
First it builds some data, 3 parents 5 children (3 to the first parent, 2 to the second parent, 0 to the third parent) :-
and
It then uses the query to extract some data and converts this to a List. It then traverses the list outputting to the Log.
Here's the code from an Activity :-
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
var p1 = dao.insert(Parent(parentName = "Parent1"))
var p2 = dao.insert(Parent(parentName = "Parent2"))
var p3 = dao.insert(Parent(parentName = "Parent3"))
dao.insert(Child(parentId = p1,childName = "Child1",date = "2000-01-01"))
dao.insert(Child(parentId = p1,childName = "Child2",date = "2003-01-01"))
dao.insert(Child(parentId = p1,childName = "Child3",date = "2005-01-01"))
dao.insert(Child(parentId = p2,childName = "Child4",date = "2006-01-01"))
dao.insert(Child(parentId = p2,childName = "Child5",date = "2007-01-01"))
for(cmbnd: Combined in Child.buildParentsWithChildren(dao.getFiltered("2004-12-31","2010-01-01"))) {
Log.d("DBINFO","Parent is ${cmbnd.parent.parentName}")
for(c: Child in cmbnd.children)
Log.d("DBINFO","Child is ${c.childName} date is ${c.date}")
}
and the result :-
2021-08-02 08:38:50.426 D/DBINFO: Parent is Parent1
2021-08-02 08:38:50.426 D/DBINFO: Child is Child3 date is 2005-01-01
2021-08-02 08:38:50.426 D/DBINFO: Parent is Parent2
2021-08-02 08:38:50.427 D/DBINFO: Child is Child4 date is 2006-01-01
2021-08-02 08:38:50.427 D/DBINFO: Child is Child5 date is 2007-01-01
i.e. Only 1 of the 3 children from the first parent as only 1 has a date between 2005 and 2009. However, both child of the second parent as they fit the date range. Nothing for the third parent.

Additional
regarding the comment:-
I would actually prefer to get Parent 3 with an empty list, instead of not returning it.
This is actually, database wise, is relatively hard/complicated as you are asking for a "non-relationship" (for want of a better term) to be considered.
However, using a different approach, that is to get All parents and then for each parent to get the filtered children (none if applicable).
As such amending the above to include:-
in the #Dao AllDao
Add a Query to get All Parents (you probably have this) as a List.
Add a Query to get the Filtered Child as a List
e.g. :-
#Query("SELECT * FROM parent")
abstract fun getAllParents(): List<Parent>
#Query("SELECT * FROM child WHERE parentId = :parentId AND date BETWEEN :fromDate AND :toDate")
abstract fun getFilteredChildrenForAParent(parentId: Long, fromDate: String, toDate: String): List<Child>
If the #Dao AllDao is an abstract class rather than an interface then.
3. Add a function that extracts all the parents, looping through them getting the filtered children e.g. :-
fun getAllParentsWithFilteredChildren(fromDate: String, toDate: String): List<Combined> {
var rv: ArrayList<Combined> = arrayListOf()
for(p: Parent in this.getAllParents()) {
rv.add(Combined(parent = p,this.getFilteredChildrenForAParent(p.elementId!!,fromDate, toDate)))
}
return rv
}
Otherwise (if you don't want the #Dao to be an abstract class and therefore an interface) include a function, elsewhere (in the Child data class) such as :-
fun getAllParentsWithFilteredChildren(dao: AllDao, fromDate: String, toDate: String): List<Combined> {
var rv: ArrayList<Combined> = arrayListOf()
for (p: Parent in dao.getAllParents()) {
rv.add(Combined(p,dao.getFilteredChildrenForAParent(p.elementId!!,fromDate,toDate)))
}
return rv
}
NOTE the subtle difference, the dao is passed to the function
Result
Amending the activity code in the first example to include :-
for(cmbnd: Combined in dao.getAllParentsWithFilteredChildren("2004-12-31","2010-01-01")) {
Log.d("DBINFOV2","Parent is ${cmbnd.parent.parentName}")
for(c: Child in cmbnd.children)
Log.d("DBINFOV2","Child is ${c.childName} date is ${c.date}")
}
for (cmbnd: Combined in Child.getAllParentsWithFilteredChildren(dao,"2004-12-31","2010-01-01")) {
Log.d("DBINFOV3","Parent is ${cmbnd.parent.parentName}")
for(c: Child in cmbnd.children)
Log.d("DBINFOV3","Child is ${c.childName} date is ${c.date}")
}
Then the result (ignoring the result from the first example) then the logcat includes :-
2021-08-03 08:33:30.812 D/DBINFOV2: Parent is Parent1
2021-08-03 08:33:30.812 D/DBINFOV2: Child is Child3 date is 2005-01-01
2021-08-03 08:33:30.812 D/DBINFOV2: Parent is Parent2
2021-08-03 08:33:30.812 D/DBINFOV2: Child is Child4 date is 2006-01-01
2021-08-03 08:33:30.812 D/DBINFOV2: Child is Child5 date is 2007-01-01
2021-08-03 08:33:30.812 D/DBINFOV2: Parent is Parent3
2021-08-03 08:33:30.817 D/DBINFOV3: Parent is Parent1
2021-08-03 08:33:30.817 D/DBINFOV3: Child is Child3 date is 2005-01-01
2021-08-03 08:33:30.817 D/DBINFOV3: Parent is Parent2
2021-08-03 08:33:30.817 D/DBINFOV3: Child is Child4 date is 2006-01-01
2021-08-03 08:33:30.817 D/DBINFOV3: Child is Child5 date is 2007-01-01
2021-08-03 08:33:30.817 D/DBINFOV3: Parent is Parent3
i.e. Parent3 with no children is included

Related

How to create factory column for self referential column in a model in pytest?

I have a model with a column which is a self referential foreign key.
class Foo(db.Model):
foo_ref_id = db.Column(
db.Integer,
db.ForeignKey("foo.id", ondelete="CASCADE"),
nullable=True,
index=True,
)
I am trying to create a factory model for the same model:
class FooFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = Foo
context_base_id = factory.SubFactory("tests.factories.BaseFactory", parent=None)
context_base_id = factory.LazyAttribute(lambda x: BaseFactory(parent=None))
context_base_id = factory.Trait(
parent_category=factory.SubFactory("tests.factories.BaseFactory")
)
I have tried the 3 ways of doing achieving this. All of them return an error of maximum depth of recursion exceeded.
What is the proper way of doing this?
You have to tell the factory where to stop.
The simplest option is to pass an extra parameter to the SubFactory call:
class FooFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = Foo
parent = factory.SubFactory(
'apps.foo.factories.FooFactory',
parent__parent__parent=None,
)
# Other fields here
With the code above:
The first FooFactory will set self.parent = FooFactory(parent__parent__parent=None)
The parent will set self.parent = FooFactory(parent__parent=None)
The grandparent will set self.parent = FooFactory(parent=None)
The great-grandparent will set self.parent = None, thus ending the recursion.

How to map ALL names directly by JPA?

Given a ZIP-code-like hierarchical code/name schema.
For example:
code = 101010
Code:
100000 level 1 code (10....)
101000 level 2 code (..10..)
101010 level 3 code (....10)
Name (short name)
100000 - A
101000 - a
101010 - i
Name (FullQualifiedName)
100000 - A
101000 - A->a
101010 - A-a->i
EDIT
I wanna following code (JPA pseudo code), but CANNOT.
#Entity
public class CodeName{
// ....
String code; // 100101 levels = {100000, 100100, 100101}
String name; //
#HowToMapDirectedToNameOfCode('100000') // #SecondTable ?
String name1;
#HowToMapDirectedToNameOfCode('100100')
String name2;
#HowToMapDirectedToNameOfCode('100101')
String name3;
String getFullQualifiedName(){
return String.format("%s->%s->%s", name1, name2, name3);
}
// getter and setter
}
But it's relatively easier in native SQL:
SELECT (select p1.name from codename p1 where p1.code= concat( substring(p.code,1,2), "0000") ) province,
(select p2.name from codename p2 where p2.code= concat( substring(p.code,1,4), "00") ) city,
(select p3.name from codename p3 where p3.code=p.code) area
FROM codename p WHERE p.code = '100101';
So, I implements it as following snippet.
#Entity
public class CodeName{
// ....
String code; // 100000, 101000, 100101
String name; // province, city , area
#Transient
String name1; // mapping directly?
#Transient
String name2; // mapping directly?
#Transient
String name3; // mapping directly?
String getFullQualifiedName(){
return String.format("%s->%s->%s", name1, name2, name3);
}
// getter and setter
}
public interface CodeNameRepository extends CrudRepository<CodeName, Long>, CodeNameRepositoryCustom {
#Query(" FROM CodeName p " +
" WHERE p.code = CONCAT(SUBSTRING(?1, 1, 2), '0000') " +
" OR p.code = CONCAT(SUBSTRING(?1, 1, 4), '00') " +
" OR p.code = ?1")
List<CodeName> findAllLevelsByCode(String code);
}
#Component
public class CodeNameRepositoryImpl implements CodeNameRepositoryCustom {
#Autowired
private CodeNameRepository codeNameRepository ;
#Override
public CodeName CodeNamefindFullQualifiedNameByCode(String code) {
List<CodeName> codeNames= codeNameRepository .findAllLevelsByCode(code);
CodeName codeName;
// extra name1, name2, name3 from list,
// fill code, name, name1, name2, name3 to codeName and
return codeName;
}
}
But it have SO MANY limitations.
Most likely, I need getFullQualifiedName(), to display it on UI, but every time I must have an extra call to populate all names.
For each entity has CodeName as its children, no matter how deep the codeName is at, I MUST expand to the codeName and reload it with FQN.
Can we mapping all #Transient names directly by JPA?
You could technically model your code repository entity as follows:
public class CodeName {
#Id
#GeneratedValue(GenerationStrategy.AUTO)
#Column
private Long id;
#ManyToOne
private CodeName parent;
#OneToMany(mappedBy = "parent")
private List<CodeName> children;
#Column
private String name;
#Transient
public String getFullyQualifiedName() {
List<String> names = new ArrayList<>();
names.add(name);
CodeName theParent = parent;
while(theParent != null) {
names.add(theParent.getName());
theParent = theParent.parent;
}
Collections.reverse(names);
return StringUtils.join(names, "->");
}
}
Because the parent relationships will be fetched EAGERLY because they mapped as #ManyToOne, you can basically start at any child CodeName entity and traverse up it's parent/child relationship to the root. This basically allows the getFullyQualifiedName method to build the name for you at runtime.
If performance becomes a problem doing this, you can always datamine the names ahead of time in your entity as you described by adding a #Column private String fullyQualifiedName and make sure that field is inserted when you create your codes. Then the transient method I added to my the entity can be dropped since you're caching the names at data insertion.
It is possible to write a JPQL, which is equivalent to your SQL query. The only tricky part is to rewrite nested selects into cross joins, because nested selects are not supported by JPA and you need to join unrelated entities. On the other hand, functions CONCAT and SUBSTRING are supported by JPQL in the same way as in SQL. See the following JPQL query, which should give you the results as the SQL query in the question:
SELECT p1.name // province
, p2.name // city
, p.name // area
FROM CodeName p, CodeName p1, CodeName p2
WHERE p.code = '100101'
AND p1.code = concat( substring(p.code,1,2), "0000")
AND p2.code= concat( substring(p.code,1,4), "00")
The above query will give you 3 values in one row, which cannot be mapped into a single entity. The result of the query will therefore be a list of Object[] arrays. You may also add the original entity into the select clause: SELECT p1.name, p2.name, p.name, p FROM .... This way, you may later process the list of results and assign first three values into the transient fields of the entity:
Object[] rows = query.getResultList();
for (Object row : rows) {
CodeName c = (CodeName)row[3];
c.setName1((String)row[0]);
c.setName2((String)row[1]);
c.setName3((String)row[2]);
}

Linq projection: Get reference of new projected entity

I need to map EF entities to respective DTO. In the example below I have EF entities Parent and Child, and Child entity contains reference to Parent object. I also have ParentDto and ChildDto (DTO), and ChildDto contains reference to ParentDto (not Parent). So, how can I assign ParentDto reference to ChildDto instance in below method:
public Task<List<ParentDto>> Method()
{
return (Context.Set<Parent>()
.Where(someCondition)
.Select(p => new ParentDto
{
// here we map all properties from Parent to ParentDto
... ,
Children = p.Children.Select(c => new ChildDto
{
// here we map all properties from Child to ChildDto
... ,
Parent = ? // reference to newly created ParentDto instance
})
}).ToListAsync();
}
You have to use a variable but you can't do it in a lambda expression. You have to do the mapping in memory after calling ToListAsync():
public Task<List<ParentDto>> Method()
{
var parents = await (Context.Set<Parent>()
.Where(someCondition)
.ToListAsync());
return parents.Select(p =>
{
var parent = new ParentDto();
//map parent properties
parent.Children = p.Children.Select(c => new ChildrenDto
{
//map child properties
});
return parent;
}).ToList();
}
In regular LINQ (not to entities) this isn't possible because of an important feature of object initializers: atomic assignment. As you can read here, an object initialization like...
var c = new Customer() { Name = "Bart", City = "Redmond", Age = 24 };
...is equivalent to...
Customer __t = new Customer();
__t.Name = "Bart";
__t.City = "Redmond";
__t.Age = 24;
Customer c = __t;
So the object is created and fully initialized first and then its reference is exposed. Therefore, if inside the object another object is initialized, the nested object will never be able to grab a reference to its parent during the initialization phase. You can only assign the parent afterwards.
Although in LINQ-to-entities the mechanism of creating objects is entirely different, the initialization logic can be considered identical, and the same restrictions apply.
As you know, in LINQ-to-Entities we can't call instance methods of entities while we're in the query expression. Else you could, for instance, have called some method in Parent that constructs its children (and assigns itself to it as their parent). As it is now, the only thing you can do is construct the Parents with their nested parent.Children first and after that, traverse the parent.Children collections and assign their Parent to them (as in Ufuk's answer).

How do I filter related Child Record

I am using RIA services. I need to select a parent entity (UnitOccupier) which has a number of related child entities(UnitOccupierDetails). I need to filter the child entities to return a single record. How do I do this?
var q = from uo in _unitOccupierContext.GetUnitOccupierQuery()
where uo.UnitOccupierDetails.????
---> I cant get to the child properties here
Thanks
You cannot include a filtered child collection of the selected parent. You can only include either the full collection or no child collection at all. But as a workaround you could use an intermediate anonymous type, like so:
var q = (from uo in _unitOccupierContext.GetUnitOccupierQuery()
select new {
Parent = uo,
Childs = uo.UnitOccupierDetails
.Where(uod => uod.MyDetailsProp == MyDetailsValue)
}).FirstOrDefault();
if (q != null)
{
UnitOccupier selectedUnitOccupier = q.Parent;
selectedUnitOccupier.UnitOccupierDetails = q.Childs.ToList();
// selectedUnitOccupier now only contains the filtered childs
}
Edit
If you want to query for the childs and include their parents (related to question in comment) you could use:
var q = _unitOccupierContext.GetUnitOccupierQuery()
.SelectMany(uo => uo.UnitOccupierDetails
.Where(uod => uod.MyDetailsProp == MyDetailsValue))
.Include(uod => uod.UnitOccupier)
.FirstOrDefault(); // or .ToList() if you expect more than one record
// q is now null or a single UnitOccupierDetails entity
// with a reference to its parent
I am assuming here that your UnitOccupierDetails class has a navigation property to the parent UnitOccupier.

How to include an inherited property in an "uneven" inheritance with Entity Framework?

I have the following model (simplified):
abstract class CartItem { EntityReference<Cart> Cart; }
class HotelCartItem : CartItem { EntityReference<Hotel> Hotel; }
class TransferCartItem : CartItem { }
class Hotel { }
As expressed "graphically":
CartItem
|<- HotelCartItem
| |-> Hotel
|
|<- TransferCartItem
Now I want to load all CartItems and include data from the Hotel class if the type of CartItem is a HotelCartItem.
This is how I'm trying to do it, but it fails with a "does not declare a navigation property with the name 'Hotel'."
var q = from cartitems in context.CartItems
.Include("Hotel")
where cartitems.CART_ID == CartID
select cartitems;
If I leave out the .Include("Hotel") the Hotel property of CartItems of type Hotel is null.
My question:
Is there a way to get around this?
Eager loading of navigation properties on sub classes is tricky. I haven't found any other way except loading them separately. The easy way to do that is registering custom ObjectMaterialized handler (only in EF 4.0) on ObjectContext:
context.ObjectMaterialized += RegisterEagerLoadingStrategies;
And the handler method looks like:
private static void RegisterEagerLoadingStrategies(object sender, ObjectMaterializedEventArgs e)
{
var context = (ObjectContext)sender;
var cartItem = e.Entity as HotelCartItem;
if (cartItem != null)
{
context.LoadProperty(cartItem, o => o.Hotel);
}
}
This solution has N + 1 problem. N + 1 means that if your main query returns N HotelCartItems you will execute N + 1 queries in database (each LoadProperty calls additional query). Also this method is called for every loaded entity (not only for HotelCartItem). So this solution is really bad for loading large number of entities.
Another approach of loading navigation properties from related entities is dividing your query into two queries. First query will load CartItems and second query will load Hotels for cart items loaded in first query (same conditions). References to hotels in cart items should be automatically set if your entities are still attached to context.
I ended up splitting the query into several parts:
Load the parent item, a "Cart".
For each of the different types I got (HotelCartItem and TransferCartItem) I queried the db for a set of only that type:
private IQueryable<T> GetCartItemQuery<T>(Guid CartID) where T : CartItem
{
if (typeof(T) == typeof(HotelCartItem))
{
var q = from ci in db.CartItems.OfType<T>()
.Include("Hotel")
where ci.CART_ID == CartID
select ci;
return q;
}
else
{
var q = from ci in db.CartItems.OfType<T>()
where ci.CART_ID == CartID
select ci;
return q;
}
}
Call it with:
var hotels = GetCartItemQuery<HotelCartItem>(CartID);
var transfers = GetCartItemQuery<TransferCartItem>(CartID);
3 . Add the CartItems to the collection of the Cart-object.