TypedEntityViews

Posts   
 
    
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 31-Jul-2012 16:57:42   

I have an entity called Trust which is derived from LegalBody. I have an entity called Policy which is derived from Asset. I have a link table (what is the correct name for an intermediate table??) called AssetToOwner which has the FKs for Asset and LegalBody (plus a Percentage field). LegalBody has a m:n relationship called OwnedAssets (via AssetToOwner).

What I want to do is create some kind of 'View' to filter Asset by Type. This can be done with an EntityView2 on OwnedAssets I think but that collection is read-only after a fetch and I need the 'View' to be live for in-memory changes.

AssetOwnerhips is the collection property on LegalBody which holds AssetToOwnerEntities. A view on here will give me AssetToOwnerEntities and not the AssetEntities I need.

Q1: I understand why an M:N collection is readonly for external use. But if I understand correctly,

What I need is a TypedEntityView on TrustEntity which filters the Assets it owns and shows only those which are PolicyEntity.

This half worked: Code: new TypedEntityView<PolicyEntity, AssetEntity>(OwnedAssets);

but because OwnedAssets is m:n, it is read-only and only includes entities at fetch time. ie it won't work for Assets which become owned by a Trust in memory.

Is there any way of using TypedEntityViews on the link table?

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 31-Jul-2012 19:16:46   

What I want to do is create some kind of 'View' to filter Asset by Type. This can be done with an EntityView2 on OwnedAssets I think but that collection is read-only after a fetch and I need the 'View' to be live for in-memory changes.

Just copy the entities into a new collection, then do the filtering using an EntityView. Now if you do changes you will need to save this collection separately, so don't Merge it back to the Original read-only collection.

simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 01-Aug-2012 07:54:32   

The m:n collection is there but a bit pointless for non-readonly-use.

The thing is we have the join table present also. (Yes, we use a context so the entities are not duplicated). Its frustrating that one is a readonly snap-shot but can be filtered/viewed easily where the other is writeable but not easily filtered/viewed.

(An additional collection won't work for our scenario - the forms that allow modifications get given an entity or collection to display - they have no say in the save process)

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 01-Aug-2012 21:13:59   

where the other is writeable but not easily filtered/viewed.

Why is that? Any collection should be easily filtered.

simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 02-Aug-2012 10:06:07   

Well in my case, if I bind a grid to a join table entity (AssetToOwner in the above example), the grid would only show AssetID, LegalBodyID, Percentage as values What I want to be able to do is show the Asset entity itself.

If I bind the grid to OwnedAssets (the m:n that I prefetched) then it will correctly show the assets but I can't edit it because the collection is readonly. Even if I had a popup form that allowed new assets to be added to AssetToOwner, the grid would not update because it is static after the fetch.

I came up with 3 possible ways to get around this: 1) Add properties to the join table entity via a partial class that duplicate and pass through those on the entities it is joining. 2) Create a class that listens to changes on the join table and then updates the m:n collection (taking off the readonly, making the change, then putting it back) 3) Writing a special 'join-table view' that wraps the join-table collection but only shows the Asset entity externally.

I have a further question: The m:n collection 'knows' one of the two other collections involved (it says so in the designer - "...(via AssetToOwner") and it is on the same entity as itself. Similar to option 2 above, why can't the m:n collection, under certain circumstances**, do the monitoring of that 1:m collection itself and update itself. It would still be readonly externally but that doesn't mean it can't be changed internally and it gets around the issue which I am presuming is the reason for being readonly anyway - namely, it can't assume the join table is only FKs.

**The circumstances being: 1) The 1:m collection private field is not null (ie it was prefetched) 2) The entities in that collection have real entities on their properties, not just their FKs. 3) (Possibly) the m:n collection was not itself prefetched (prefetching it would indicate a static view is required as is currently the case)

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39910
Joined: 17-Aug-2003
# Posted on: 03-Aug-2012 10:32:31   

An m:n relationship is based on an intermediate entity. This data might not be available in memory, or just partly be available in memory. This means that to make this work, for a m:n relationship, all (!) intermediate data should be fetched and the m:n relationship should not contain any data directly but be a view on the intermediate data.

As the code doesn't know whether the intermediate data is all in memory (frankly, it can never know, because another user can insert data into the intermediate table at the same time), we see the m:n relationships as readonly and a 'result of a query'. Modifying intermediate entities at runtime therefore don't result in the m:n relationships being updated.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 07-Aug-2012 10:16:10   

Otis wrote:

An m:n relationship is based on an intermediate entity. This data might not be available in memory, or just partly be available in memory. This means that to make this work, for a m:n relationship, all (!) intermediate data should be fetched and the m:n relationship should not contain any data directly but be a view on the intermediate data.

As the code doesn't know whether the intermediate data is all in memory (frankly, it can never know, because another user can insert data into the intermediate table at the same time), we see the m:n relationships as readonly and a 'result of a query'. Modifying intermediate entities at runtime therefore don't result in the m:n relationships being updated.

I understand your second sentence and that is why I put in the 'certain circumstances', which I believe, would get around this. (Your comment about other users inserting data into the intermediate table is moot however - that is always the case - as soon as you fetch, your data may be stale and I think you have mentioned specifically that in the docs or these forums)

However, I still have the problem about binding a grid. I need a live view of the other side of the m:n join (ie those in memory - not bothered about others changing the database). So I'll have to write one of the possible solutions I described.

If I go for option (2), can I clarify with you that the contents of that collection are considered static by LLBLGen. ie if I choose to turn off the readonly and add stuff in there then LLBLGen isn't doing anything special with it - ie it is just a plain ReadOnly collection?

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 08-Aug-2012 00:38:14   

AFAIK, it is plain readOnly.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39910
Joined: 17-Aug-2003
# Posted on: 08-Aug-2012 10:53:07   

readonly as in: it won't look at it for persistence.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 08-Aug-2012 14:52:31   

Thanks both for that confirmation.

I've started having a go at this and had to jump through hoops trying to get the schema in a generic way.

Heres my (test) m:m relationship Asset <-> AssetToOwner <-> LegalBody

Taking one side of this, Asset, I have two collections I'm interested in: .AssetOwnerships (which is the 1:m with AssetToOwner) .Owners (which is the m:m and is a collection of LegalBody)

Ultimately I want to just provide the m:m collection to my new class.

new ManyToManyUpdater(asset.Owners);

(but for now I'm also passing in asset.Ownerships 'cos I can't work out if/how they are related)

The theory is simple: In the ctor: 1) Get the entity that hold the m:n collection. 2) Get the collection to the intermediate entities (.AssetOwnerships in this case) 3) Read all the intermediate entities in .AssetOwnerships and find the LegalBodyEntity (ie the 'other' side' - copy this into .Owners (remembering to set IsReadOnly = false temporarily.

At this point, .Owners can be bound to a Grid and will show the list of LegalBody entities. The DefaultView will be fine or I could use the TypedEntityView I mentioned in another thread if I wish to filter by entities derived form LegalBody.

4) I then listen to changes on .AssetOwnerships and make the same changes in .Owners: If an AssetToOwner entity gets added then I add AssetToOwner.LegalBody to Owners. If an AssetToOwner entity gets removed then I remove the corresponding entity from owners (the EntityRemoved event has the InvolvedEntity - nice one!). I don't need to worry about changes to the LegalBody entity itself - those changes will be pick up by property bindings.

I also don't need to worry about persistence because the collection I am modifying is ignored - hurrah! So that it is, hopefully.

What you could help me with is the best way of accessing the metadata:

For Step 1: I have partial class for EntityCollection<TEntity> and I added a GetContainingEntity() method which just calls the protected IEntity2 ContainingEntity property. This works but why is this protected though??

For Step 2: This is the biggest problem and I cheat for now by just passing it in. Does the metadata reflect that for a m:m collection, the 1:m collection it is indirectly 'related' with?

For Step 3: Hmm. My method works but there must be a better way. Here is what I came up with (don't laugh) :-

        IEntity2 GetOtherEntityFromIntermediateEntity(IEntity2 intermediateEntity)
        {
            var data = intermediateEntity.GetRelatedData();

            return (IEntity2) data.Values.Single(o => o != owningEntity);
        }

I thought that for a given intermediate entity, it would store the two entities on either side. Well I just want the one that isn't the one I started with. (it is assumed and a requirement that the entities are present and not just their IDs for this to work).

Is there a better way than this?

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 08-Aug-2012 21:05:59   

I think there is a better approach.

Fetch the entire graph, i.e. Asset <-> AssetToOwner <-> LegalBody Better to use a Context to make sure you have unique instances of each entity.

Create a Disctionary<AssetId, List<LegalBody>> this would serve you the owners of each Asset, and hence can be used to bind list of Owners of each Asset to the required control.

Needless to say the List<LegalBody> is actually holding references to the sae entities in the graph.

When Saving, save the entire graph recursively.

That's it.

simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 09-Aug-2012 08:16:10   

Walaa

The fetch you mentioned is what we do. Saving also isn't a problem.

The problem is that AssetToOwner is being edited before the save - adds, deletes, modifies (there is a Percentage field there as well as the 2 FKs). We cannot bind a grid to Asset.Owners because it is either empty (if not prefetched) or will only reflect the Owners at fetch time (static). We cannot bind a grid to AssetToOwner (which is what is being editing) since that only has two FKs - not what we want in the grid.

I think I see what you are saying but a List<LegalBody> doesn't have the advantage of being viewed through an EntityView like EntityCollection does. For example Asset is in a hierarchy, we have two grids - one that shows Policy derivatives and one that shows anything else - views lets us do this very easily.

Plus Asset.Owners already exists and its name makes sense - its just useless in a non-readonly scenario.

What I am proposing makes Asset.Owners 'live' with the changes made to AssetToOwner - fire and forget.

Not quite as straightforward as I thought.

new AssetToOwnerEntity
                                    {
                                        Asset = asset,
                                        LegalBody = firstClient,
                                        Percentage = 20
                                    };

and

new AssetToOwnerEntity
                                    {
                                        LegalBody = secondClient,
                                        Asset = asset,
                                        Percentage = 80
                                    };

are slightly different in that the first has a null LegalBody at the point is added to Asset.Owners.

What I've done is to listen to PropertyChanged for any added AssetToOwner so LegalBody is picked up either at add-to-collection time or set-property time.

Seems to work fine but I still wonder if there is an easier way of doing some of the metadata stuff.

If I tidy up the code (need a few more tests first) and post it here, would you guys have a look to see what could be done better?

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 09-Aug-2012 19:28:11   
If I tidy up the code (need a few more tests first) and post it here, would you guys have a look to see what could be done better?

Sure. And it would turn useful for other developers.

simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 10-Aug-2012 08:03:22   

Walaa wrote:

If I tidy up the code (need a few more tests first) and post it here, would you guys have a look to see what could be done better?

Sure. And it would turn useful for other developers.

Will do but could you help me out with the getting the info out of the schema as mentioned above.

This is what I have to determine the property names for any intermediate entity:

        void EnsureIntermediateEntityPropertyInfo(IEntityCore intermediateEntity)
        {
            if (owningEntityPropertyInfo != null) return;

            try
            {
                var relatedData = intermediateEntity.GetRelatedData();
                if (relatedData.Count != 2) throw new InvalidOperationException("Expected only two related entities on intermediate entity.");

                foreach(var pair in relatedData)
                {
                    if (pair.Value == owningEntity)
                    {
                        owningEntityPropertyInfo = intermediateEntity.GetType().GetProperty(pair.Key, BindingFlags.Instance | BindingFlags.Public);
                    }
                    else
                    {
                        otherEntityPropertyInfo = intermediateEntity.GetType().GetProperty(pair.Key, BindingFlags.Instance | BindingFlags.Public);
                    }
                }

                Debug.Assert(owningEntityPropertyInfo != null && otherEntityPropertyInfo != null);
            }
            catch
            {
                throw new InvalidOperationException("Could not determine structure of intermediate entity.");
            }
        }

Plus I would really like to find, given just a m:n collection on an entity, the 1:n collection to the intermediate entities on the same entity.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39910
Joined: 17-Aug-2003
# Posted on: 13-Aug-2012 11:24:18   

Plus I would really like to find, given just a m:n collection on an entity, the 1:n collection to the intermediate entities on the same entity.

This is doable, but a little tricky.

There's a method called 'GetRelationsForField'. The field is the field mapped onto the relation. So in the case of northwind, you have Customer 1:n Order m:1 Employee and in Customer you have a m:n relation with Employee.

If you pass in 'Employees' to the method in Customer, you'll get 2 relations back: the Customer 1:n Order one and then from the intermediate entity to the m:n related entity (order m:1 employee in this case). Each 1:1/1:n/m:1 relation has the field mapped onto it as property ('MappedFieldName') which you can use further.

Frans Bouma | Lead developer LLBLGen Pro