Navigator to subtyped relation table

Posts   
 
    
Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 04-Jul-2020 18:20:37   

v.5.5.3 Adapter, MSSQL, DB first

I have tables: Defect PartyRole and its subtype ProductionUnit

Then I have relationship table Defect2PartyRole This table is subtyped to Defect2ProductionUnit, Defect2Customer ...

I have two Devex grids on form. GridMaster datasource=Defects collection GridChild datasource=Defect collection, datamember=

I need to set GridChild datamember to Defect2PartyRole subtype: "Defect2ProductionUnit" but only "Defect2PartyRoles" is available as a choice in winforms designer.

In LLBL designer Defect entity has only navigator to supertype "Defect2PartyRoles"

I tried to add relationship in LLBL designer from DefectEntity to Defect2ProductionUnit, but designer shows warning DZ0040 that new relationship is shared with Defect<->Defect2PartyRoles relationship. And DZ0025 warning about missing foreign backing key constraint in DB...

As LLBL designer knows Defect2ProductionUnit is subtype of Defect2PartyRole, why it cannot automatically create also navigators to subtypes? This way DefectEntity will have subcollections "Defect2PartyRoles" and also "Defect2ProductionUnit" and easy to set GridChild datamember to Defect2ProductionUnit

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 05-Jul-2020 09:29:01   

A navigator of type T is able to provide types T and all subtypes of T, that's how inheritance works, as you know. The designer doesn't create navigators for subtypes as it's unnecessary: the navigator that's there is capable of returning the subtypes. Remember, the navigator is mapped onto a relationship between the supertype and another entity, so all subtypes of that supertype apply. Adding another navigator is nothing more than a readonly filter on the already existing one.

the winforms designer here shows the type of the navigator. If you want to show a specific grid for a specific type, you have to define multiple grids and view them based on the data, i.e. make all of them hidden and show the one that applies.

If I have Customer - Orders and Order is a supertype of 2 different order types, O1 and O2, even with 2 navigators it won't help: I would have to make the selection at runtime in a small piece of code to decide which grid is visible: the grid viewing the columns for O1 or the grid viewing the columns of O2. If I recall correctly, you can do that in the same grid control with different views. (but it requires a bit of code).

Frans Bouma | Lead developer LLBLGen Pro
Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 05-Jul-2020 11:35:01   

This is especially databinding issue. Based on navigators, datamember is generated (related collection)

Say I have Customer - Orders and Order is a supertype of 2 different order types, O1=PurchaseOrder and O2=SalesOrder

I load Customer collection with prefetch to Orders even with typefilter to subtype CustomerPurchaseOrder

I will add two Devex grids to form: 1/ GridCustomer datasource=Customer collection

2/ GridPurchaseOrders (O1); datasource=Customer collection datamember=CustomerOrders <- I cannot select DataGrid DataMember property=CustomerPurchaseOrder, as navigator is only to supertype CustomerOrders, means datamember can be set only to CustomerOrders.

This way I will no see specific fields for subtype CustomerPurchaseOrders If I add new row in grid, the type of row is automatically CustomerOrderEntity Thus when Customer entity is saved with recurse=true, it will fail as it will write only to CustomerOrder table, not also to CustomerPurchaseOrders inherited table.

The only workaround now for this scenario is not use prefetch, load Customer collection without related entities, then load CustomerPurchaseOrders as collection. And then write some databinding hacks, when Customer current is changed then set filter to CustomerPurchaseOrder collection... Also saving has to be done separately, cannot be saved as graph by SaveEntity.

PS: Use more views in the same Devex grid does not solve this, as datamember datatype will be CustomerOrder ...

https://supportcenter.devexpress.com/ticket/details/e883/how-to-display-master-detail-tables-in-separate-grid-controls

Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 05-Jul-2020 18:24:48   

There will be in CustomerEntity code like:


<TypeContainedAttribute(GetType(CustomerOrderEntity))> _
Public Overridable ReadOnly Property [CustomerOrder]() As EntityCollection(Of CustomerOrderEntity)
    Get 
        Return GetOrCreateEntityCollection(Of CustomerOrderEntity, CustomerOrderEntityFactory)("Customer", True, False, _customerOrder)
    End Get
End Property

Seems in order to see/set datamember=CustomerPurchaseOrder I have to add into CustomeEntity something like:


<TypeContainedAttribute(GetType(CustomerPurchaseOrderEntity))> _
Public Overridable ReadOnly Property [CustomerPurchaseOrder]() As EntityCollection(Of CustomerPurchaseOrderEntity)
    Get
        ???? what should be there <<<<<-
    End Get
End Property

Seems the "only" remaining problem is how to bind that to DataGrid, so when adding new rows in grid then databinding will add rows into Customer.PurchaseOrders collection

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 06-Jul-2020 10:44:39   

the problem is that a 'collection' property is a set of a given type. If that type has subtypes, it can contain instances of that subtype but it can't be expressed in the type of the property. That's the case with all inheritance. You want to have exactly that: 1 type which can reflect exactly what types are in the collection, but that's not possible.

You can utilize contravariance, altho I'm not sure if VB.NET supports that. You have to cast to IEnumerable<CustomerOrderEntity> first:

public class Customer
{
    public List<Order> Orders {get;set;}
    
    public IEnumerable<POrder> PurchaseOrders
    {
        get { return (IEnumerable<POrder>)(IEnumerable<Order>)this.Orders; }
    }
}

public class Order
{
}

public class POrder
{
}

public class TOrder
{
}

See: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/

Downside is that an IEnumerable is readonly. So to add new rows, I think it's best to bind a collection of the actual type, which you fetch separately. This makes databinding easy, and you can use databinding to set the parent / FK field with the selected customer's pk for new entities. To persist both you can just use a Unit of work.

Frans Bouma | Lead developer LLBLGen Pro
Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 06-Jul-2020 14:58:29   

Thanks Otis!

Finally I solved it simply. I added manually relationship "CustomerPurchaseOrders", check ModelOnly checkbox. And changed prefetch to this relationship and everything works.

The only obvious "problem" is this relationship duplicates inherited relationship so I can see warning DZ0040 ...

The Foreign Key field '... (FK)' in the relationship '....' is shared with the relationship ..., and it therefore has multiple sources of truth. You should re-model this so FK fields aren't shared among relationships.

But I can live with that sunglasses

Have to say again: perfect product & perfect support.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 07-Jul-2020 09:11:13   

smile Glad you found a solution for this problem simple_smile

Frans Bouma | Lead developer LLBLGen Pro