Polymorphic Problem

Posts   
 
    
ScottCate
User
Posts: 48
Joined: 11-May-2005
# Posted on: 02-Feb-2007 01:32:06   

.NET 2.0 LLBLGen Version 2.0.0.0 (Dec 6, 2006) Research: Search for InheritanceInfoProviderBase and _entityToHierarchyLeafs Close Findings : http://llblgen.com/TinyForum/Messages.aspx?ThreadID=8311&HighLight=1

**Facts. ** I have two tables, BaseItem and Item. BaseItem has 20 fields, and Item extends baseItem with 5 nText fields. It's perfect to have them as extentions, since most of the time I don't need them, and the large fields really slow things down.

Production Code To Produce Problem.

private EntityCollection<T> GetItemColllection<T>(FetchSettings fetchSettings) where T : BaseItemEntity
EntityCollection<T> items = new EntityCollection<T>();

adapter.FetchEntityCollection( items,fetchSettings.Filter,fetchSettings.MaxItemsToReturn,fetchSettings.Sort,fetchSettings.Prefetch ,fetchSettings.CurrentPage,fetchSettings.ItemsPerPage );

This works great, since I can pass in BaseItemEntity or ItemEntity and get back the proper collection.

Problem. When I ask for baseItemEntity, The select is still being created with the ItemEntity fields included. I traced this into InheritanceInfoProvider.cs on line 737. I don't understand this code at all. It's adding the "leaf" fields from ItemEntity to my baseItemEntity select statement. This makes perfect sence, if I were asking for ItemEntity, for it to walk up the chain, and include BaseItemEntity fields, but it makes no sence to me why the leaf fields are being included.

I have confrimed that BaseItemEntity is the correct factory being used.

collectionToFill.EntityFactoryToUse

Reference Code. InheritanceInfoProviderBase.cs Line 737

// now add all fields from passed in name to all subtypes below this type, till all leafs. 
List<string> leafsBelowEntity = _entityToHierarchyLeafs[entityName];
foreach(string leafName in leafsBelowEntity)
{
    // grab path to root for leaf and walk that path from the current entity position + 1 till the end, as we've already included
    // the current entity's fields. 
    List<string> leafPath = _entityToPathToRoot[leafName];
    // has to be there.
    int indexOfCurrentEntity = leafPath.IndexOf(entityName);
    for(int i=indexOfCurrentEntity+1;i<leafPath.Count;i++)
    {
        IEntityFieldCore[] fieldsOfEntityOnPath = GetEntityFields(leafPath[i]);
        if(fieldsOfEntityOnPath!=null)
        {
            foreach(IEntityFieldCore field in fieldsOfEntityOnPath)
            {
                if(!alreadyIncludedFields.Contains(leafPath[i], field.Name))
                {
                    fieldsCollected.Add(field);
                    alreadyIncludedFields.Add(leafPath[i], field.Name);
                }
            }
        }
    }

    // add leaf's fields
    IEntityFieldCore[] fieldsOfLeaf = GetEntityFields(leafName);
    if(fieldsOfLeaf!=null)
    {
        foreach(IEntityFieldCore field in fieldsOfLeaf)
        {
            if(!alreadyIncludedFields.Contains(leafName, field.Name))
            {
                fieldsCollected.Add(field);
                alreadyIncludedFields.Add(leafName, field.Name);
            }
        }
    }
}

IEntityFieldCore[] toReturn = new IEntityFieldCore[fieldsCollected.Count];
for (int i = 0; i < fieldsCollected.Count; i++)
{
    IEntityFieldCore field = fieldsCollected[i];
    field.Alias = "F" + i;
    toReturn[i]=field;
}

return toReturn;

Summary. I'm sure this is something I'm doing wrong, or backwards, but I've been wroking on it all day, and I can figure it out. Thanks for the help!

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 02-Feb-2007 08:41:10   

When I ask for baseItemEntity, The select is still being created with the ItemEntity fields included.

That's by design, because those are considered as of type baseItemEntity too. (Inheritance rules).

Try to pass the super type Factory to the constructor of the collection. (I'm not sure if this will help, as you confired the factory was set, but just try it explicitly)

Also you can always fetch the super type in a Typed List or a Dynamic List.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 02-Feb-2007 10:56:50   

You can also limit your set of entity types to return by using a type filter. (See advanced filtering -> Filtering on entity type) and then specify true for negate. You should use this for a negate filter on Item, so you'd only get BaseItem instances which arent' an item.

Frans Bouma | Lead developer LLBLGen Pro
ScottCate
User
Posts: 48
Joined: 11-May-2005
# Posted on: 02-Feb-2007 15:38:38   

This kind of helps, at least I think we're going in the right direction.

But I'm still not getting the appropriate results.

Adding


//Add IPRedicate to negate the ItemEntity Fields from being included
expression.Add(ItemEntity.GetEntityTypeFilter(true));

Doesn't help. Here's why. ItemBaseEntity and ItemEntity are a one to one, Connected by ItemId. The above statement adds a predicate that effectively says "Where .... AND ([dbo].[ItemEntity].[ItemId] IS NULL)

This will always return an empty result set.

I'm trying to have no joins in this query. I only want what's found in ItemBaseEntity.

The reason that I'm splitting these out BTW, is because there are 4+million rows. So I've put all the binary fields in ItemEntity, and kept the very lean fields (which are the most selected/used fields) in ItemBaseEntity.

ScottCate
User
Posts: 48
Joined: 11-May-2005
# Posted on: 02-Feb-2007 15:44:52   

Side Notes/Conversation Someone reading this might think that a TypedList would be a better route. And in some cases that would be appropriate. But in the case, I'm using the Inheritance within the application with Generics in 2.0, as partially shown in the original message. I want to be able to bind an EntityCollection<T> Where T : ItemBaseEntity, to a custom ItemRepeater Control that we've built.

So not to distract from the original conversation, but I thought that I'd add, that there are outside reasons we're using the inheritance model, and not a separate object (like TypedList) for this solution.

ScottCate
User
Posts: 48
Joined: 11-May-2005
# Posted on: 02-Feb-2007 15:54:05   

Walaa wrote:

That's by design, because those are considered as of type ItemBaseEntity too. (Inheritance rules).

I don't understand this.

ItemEntity is-a ItemBaseEntity that's true. But when working with ItemBaseEntity in scope, nothing in ItemEntity matters.

consider the following..


EntityCollection<ItemBaseEntity> Items = new EntityCollection<ItemBaseEntity>();

When Items is fetched, i see no reason that ItemEntity would have to be joined, and the fields in ItemEntity would be automatically included.

If I'm asking for a base class, no super classes should be requested. In my example, ItemBaseEntity is what I'm trying to fetch. There is a one to one relationship setup to ItemEntity - but who cares about that? I'm not asking for any field, or any predicates from ItemEntity, only ItemBaseEntity.

I'm sure that I'm just not understanding something here. No angry tones here, I'm just trying to figure this out - it's driving me nuts.

Thanks for the help.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 02-Feb-2007 16:21:23   

It's very simple.

You have two entities: BaseItem and Item. Item derives from BaseItem. This means that if you tell the fetch logic to fetch BaseItem instances, you will get Item instances as well, because Item instances are also a BaseItem.

This is how inheritance on a relational model is implemented. The thing is that every BaseItem row is either a BaseItem OR part of an Item, as Item's data is spread across 2 entities: BaseItem and Item.

So if you tell the fetch logic to get all BaseItem instances, you WILL get data belonging to Item, and thus this data is stored in an Item instance instead, loading the Item data as well.

If you JUST want to load BaseItem instances which AREN'T an Item, you therefore need all rows which aren't part of an Item and thus which aren't of type Item.

If you want ALL BaseItem rows, no matter if they're in a BaseItem entity or in an Item entity, you can use a projection fetch: fetch BaseItem fields onto a BaseItem entity collection.

Say I have an inheritance model like Employee <- Manager <- Boardmember. (bad model, but illustrates the point). Say I fetch all Employees. What do you expect to get? All employees with just their Employee data? Or all Employees in their own type instance? (e.g. a manager has his data loaded in a ManagerEntity instance etc.)

I would definitely expect the latter, because if I want to load the data of manager because I want to work on manager, I have to perform per type a new query manually.

This all comes from the fact that the data of a subtype is fragmented across all supertype tables plus the table of the subtype. So when you look at a row in one of the tables, this is a fragment of an entity, and when you load entities, you can't deal with fragments, you need all data.


There are other things to consider: you might want to look into destroying the inheritance hierarchy between baseitem and item here, and just keep the 1:1 relation. This is perfectly workable.

In v2.1, we'll add delay loading for LOB fields (text/image/blob/clob etc.) so you can fetch them later on into an existing graph. This could solve your problem so we're aware of this and will offer a solution, yet that's not helping you today.

I hope this clears things up. If not, please feel free to ask more simple_smile

Frans Bouma | Lead developer LLBLGen Pro
ScottCate
User
Posts: 48
Joined: 11-May-2005
# Posted on: 02-Feb-2007 16:45:36   

Can you elaborate on this solution?

otis wrote:

If you want ALL BaseItem rows, no matter if they're in a BaseItem entity or in an Item entity, you can use a projection fetch: fetch BaseItem fields onto a BaseItem entity collection.

Will that return only the rows that are in BaseItem, and nothing else?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 02-Feb-2007 16:49:26   

ScottCate wrote:

Can you elaborate on this solution?

otis wrote:

If you want ALL BaseItem rows, no matter if they're in a BaseItem entity or in an Item entity, you can use a projection fetch: fetch BaseItem fields onto a BaseItem entity collection.

Will that return only the rows that are in BaseItem, and nothing else?

That will return all rows in baseitem, thus also the data which belongs to Item (which is partly also in baseitem as it's a subtype of baseitem). It's not entirely clear to me if you want that or that you want all baseitem data of entites which are NOT an item (i.e. which are of type baseitem and not of type item. )

Frans Bouma | Lead developer LLBLGen Pro
ScottCate
User
Posts: 48
Joined: 11-May-2005
# Posted on: 02-Feb-2007 16:56:15   

Its' a one-to-one required relationship.

If there are 100 BaseItemRows, there will always be 100 ItemRows

We can't have one without the other.

All the binary data, is in ItemRow.

If I'm selecting 150 rows from BaseItem, I can get results in sub second time. If I add the Item rows, it jumps to 3-10 seconds, becuase of the joins, and the binary data that comes back with the added relationship.

I'm looking for a way to get this.

Select * from ItemBase where ... 

(which will populate a collection of ItemBaseEntity)

And the only thing I can get LLBLGent to give me is ...

Select ib.*, i.* from ItemBase ib left join
Item i on ib.ItemId = i.ItemId
where ...

(Note: LLBLGen does not request *, it does qualified column names, but I used shorthand above)

ScottCate
User
Posts: 48
Joined: 11-May-2005
# Posted on: 02-Feb-2007 17:01:49   

Another supporting reason I want to use ItemBaseEntity in collections, and not have ANY thing to do with ItemEntity is for Paging. All the binary columns in ItemEntity disqualify the query from server side paging.

Please confirm if this is correct with LLBLGen V2.0.0.0 - It's true to say that LLBLGen does not support paging for any Entities that are part of an Inheritance chain (no matter their position) IF ANY Related Entity in the inheritance chain has a single binary column?

This would support your 2.1 feature enhancement, to delay loading the binary data.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 02-Feb-2007 17:13:35   

ScottCate wrote:

Its' a one-to-one required relationship.

If there are 100 BaseItemRows, there will always be 100 ItemRows

We can't have one without the other.

All the binary data, is in ItemRow.

If I'm selecting 150 rows from BaseItem, I can get results in sub second time. If I add the Item rows, it jumps to 3-10 seconds, becuase of the joins, and the binary data that comes back with the added relationship.

I'm looking for a way to get this.

Select * from ItemBase where ... 

(which will populate a collection of ItemBaseEntity)

And the only thing I can get LLBLGent to give me is ...

Select ib.*, i.* from ItemBase ib left join
Item i on ib.ItemId = i.ItemId
where ...

(Note: LLBLGen does not request *, it does qualified column names, but I used shorthand above)

The joins are there because of the hierarchy fetch. You'll always have them if you're going this route.

Now, that the 1:1 relation is mandatory, that's not really a problem. You can still have the Item data in a second entity which is RELATED to what you now have as BaseItem. However, you might want to keep using them in an inheritance fashion, that's up to you.

To fetch all ItemBaseEntity data into ItemBaseEntity instances without getting subtype types, you can use the following code. Be aware though that if you SAVE a NEW entity of that type, it will never become an Item entity.

It should look something like this (not tested)


EntityCollection<ItemBaseEntity> baseItems = new EntityCollection<ItemBaseEntity>(new ItemBaseEntityFactory());
IEntityFields2 fields = baseItems.EntityFactoryToUse.CreateFields();
List<IDataValueProjector> valueProjectors = new List<IDataValueProjectors>();
for(int i=0;i<fields.Count;i++)
{
    IEntityField2 field = fields[i];
    valueProjectors.Add(new DataValueProjector(field.Name, i, field.DataType));
}
DataProjectorToIEntityCollection2 projector = new DataProjectorToIEntityCollection2(baseItems);
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
    adapter.FetchProjection(valueProjectors, projector, fields, null, 0, true);
}

I didn't specify a filter.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 02-Feb-2007 17:19:04   

ScottCate wrote:

Another supporting reason I want to use ItemBaseEntity in collections, and not have ANY thing to do with ItemEntity is for Paging. All the binary columns in ItemEntity disqualify the query from server side paging.

Please confirm if this is correct with LLBLGen V2.0.0.0 - It's true to say that LLBLGen does not support paging for any Entities that are part of an Inheritance chain (no matter their position) IF ANY Related Entity in the inheritance chain has a single binary column?

Paging should work. Do you receive an error and if so, which one? We very recently fixed an issue in the sqlserver DQE for temptable issues related to varbinary(MAX) columns, this fix hasn't been released yet, though the dll is attached to this thread:

http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=8838

Frans Bouma | Lead developer LLBLGen Pro
ScottCate
User
Posts: 48
Joined: 11-May-2005
# Posted on: 02-Feb-2007 18:55:06   

Otis,

EntityCollection<ItemBaseEntity> baseItems = new EntityCollection<ItemBaseEntity>(new ItemBaseEntityFactory()); IEntityFields2 fields = baseItems.EntityFactoryToUse.CreateFields(); List<IDataValueProjector> valueProjectors = new List<IDataValueProjectors>(); for(int i=0;i<fields.Count;i++) { IEntityField2 field = fields[i]; valueProjectors.Add(new DataValueProjector(field.Name, i, field.DataType)); } DataProjectorToIEntityCollection2 projector = new DataProjectorToIEntityCollection2(baseItems); using(DataAccessAdapter adapter = new DataAccessAdapter()) { adapter.FetchProjection(valueProjectors, projector, fields, null, 0, true); }

This is EXACTLY!!! what I needed.

Thank you. Hopefully this thread will be helpful to others.

ScottCate
User
Posts: 48
Joined: 11-May-2005
# Posted on: 06-Feb-2007 21:33:09   

OK, now I'm having a problem with the Fetch Projection, because it's not accepting a prefetch.

Can you do a prefetch with the Fetch Projection?

Probably not huh?

ScottCate
User
Posts: 48
Joined: 11-May-2005
# Posted on: 06-Feb-2007 22:02:08   

I'm moving away from the inheritance chain.

I'll just create a one-to-one, and prefetch the extra binary data when needed.