Problem Filtering on Discriminator field

Posts   
 
    
jsaylor
User
Posts: 37
Joined: 20-Sep-2004
# Posted on: 08-May-2009 18:12:21   

Using LL v2.6, I found that I can't get matches when applying a Predicate against a Discriminator field (in this case, an Int32) on an entity that has not yet been saved and is in-memory only.

Below are 2 tests where I'm searching for the first entry in a collection that the entity came from: both should result in at least 1 match (the entity providing the values). The ActionReason value is a Discriminator field, the ScheduleOrder is not. For the first test, I get 0 matches, for the second I get 1.

Dim entries As ScheduleEntryCollection = CurrentMgr.ScheduledMgr._ScheduleEntries
Dim firstEntry As ScheduleEntryEntity = entries.Item(0)
debugMsg("ActionReason matches" & entries.FindMatches(New PredicateExpression(ScheduleEntryFields.ActionReason = firstEntry.ActionReason)).Count)
debugMsg("ScheduleOrder matches" & entries.FindMatches(New PredicateExpression(ScheduleEntryFields.ScheduleOrder = firstEntry.ScheduleOrder)).Count)

I grabbed the LLSource (awesome!) and was able to track it down to FieldUtilities.cs CheckIfCurrentFieldValueIsNull (line270) which has this:

internal static bool CheckIfCurrentFieldValueIsNull(IEntityFieldCore field, bool entityIsNew)
{
    if(entityIsNew)
    {
        // a new entity field's value is null / not defined if it's not changed yet, or IF it's changed, it's set to null.
        return (!field.IsChanged || (field.IsChanged && (field.CurrentValue == null)));
    }
    else ...
}

Since my Entity is in-memory (IsNew=true) and the Discriminator field's value is "baked in" (IsChanged=false) this returns True because of the !field.IsChanged even though the field.CurrentValue has a value.

That doesn't seem right, I would expect that while IsChanged is false, the fact that it does have a CurrentValue is more important - or am I off base?

As always - awesome product!! I've been an LL'er for 4'ish years now and love every day of it simple_smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 09-May-2009 10:44:35   

jsaylor wrote:

Using LL v2.6, I found that I can't get matches when applying a Predicate against a Discriminator field (in this case, an Int32) on an entity that has not yet been saved and is in-memory only.

Below are 2 tests where I'm searching for the first entry in a collection that the entity came from: both should result in at least 1 match (the entity providing the values). The ActionReason value is a Discriminator field, the ScheduleOrder is not. For the first test, I get 0 matches, for the second I get 1.

Dim entries As ScheduleEntryCollection = CurrentMgr.ScheduledMgr._ScheduleEntries
Dim firstEntry As ScheduleEntryEntity = entries.Item(0)
debugMsg("ActionReason matches" & entries.FindMatches(New PredicateExpression(ScheduleEntryFields.ActionReason = firstEntry.ActionReason)).Count)
debugMsg("ScheduleOrder matches" & entries.FindMatches(New PredicateExpression(ScheduleEntryFields.ScheduleOrder = firstEntry.ScheduleOrder)).Count)

I grabbed the LLSource (awesome!) and was able to track it down to FieldUtilities.cs CheckIfCurrentFieldValueIsNull (line270) which has this:

internal static bool CheckIfCurrentFieldValueIsNull(IEntityFieldCore field, bool entityIsNew)
{
    if(entityIsNew)
    {
        // a new entity field's value is null / not defined if it's not changed yet, or IF it's changed, it's set to null.
        return (!field.IsChanged || (field.IsChanged && (field.CurrentValue == null)));
    }
    else ...
}

Since my Entity is in-memory (IsNew=true) and the Discriminator field's value is "baked in" (IsChanged=false) this returns True because of the !field.IsChanged even though the field.CurrentValue has a value.

That doesn't seem right, I would expect that while IsChanged is false, the fact that it does have a CurrentValue is more important - or am I off base?

The code you refer to is doing a semantic test, so if a value isn't changed, and the entity is new, it is considered 'null' as all fields are considered 'null' (i.e.: 'undefined') when the entity is new and nothing has been set yet. A discriminator field isn't set through normal channels by making it a dirty field because that would make the entity 'dirty' and always mark it for persistence, which might not always be what you want.

What you should use instead is a DelegatePredicate to test on the type (the .NET type) of the entity instead for in-memory filters on target-per-entity-hierarchy entities. Would that work for you?

As always - awesome product!! I've been an LL'er for 4'ish years now and love every day of it simple_smile

smile Glad you like our work! simple_smile

Frans Bouma | Lead developer LLBLGen Pro
jsaylor
User
Posts: 37
Joined: 20-Sep-2004
# Posted on: 09-May-2009 16:00:37   

Otis wrote:

The code you refer to is doing a semantic test, so if a value isn't changed, and the entity is new, it is considered 'null' as all fields are considered 'null' (i.e.: 'undefined') when the entity is new and nothing has been set yet. A discriminator field isn't set through normal channels by making it a dirty field because that would make the entity 'dirty' and always mark it for persistence, which might not always be what you want.

Hmm, I understand the point, but it still doesn't seem correct. I can understand that LL setting the discriminator value shouldn't mark it as dirty, but it also shouldn't interpret the value as null (and not use the predicate test) because it's not dirty. That said, it's not my code wink and I understand that there are probably many other aspects that I am not taking into account. Of course I'm being simplistic and greedy in solving for my one, narrow, specific problem.

Otis wrote:

What you should use instead is a DelegatePredicate to test on the type (the .NET type) of the entity instead for in-memory filters on target-per-entity-hierarchy entities. Would that work for you?

Hmmm, my discriminator field contains bit values that I'm already using masking on in a lot of other scenarios and that's what I wanted to do here. I haven't yet checked, but it sounds like the DelegatePredicate isn't going to support such operations.

Again, it's your code - and I'm not as schooled in ORM theory as you are, so it's possible my suggestion might be off the reservation - but are you sure that my suggestion on the null test [or the mechanics that arrive at the situation] above isn't the right answer? If that was one of my junior's code, I'd be telling them that either their code was wrong, or at the very least, the naming of the function was misleading.

If ( field.CurrentValue = 512 ) Then CheckIfCurrentFieldValueIsNull(field) = true simple_smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 10-May-2009 11:25:45   

jsaylor wrote:

Otis wrote:

The code you refer to is doing a semantic test, so if a value isn't changed, and the entity is new, it is considered 'null' as all fields are considered 'null' (i.e.: 'undefined') when the entity is new and nothing has been set yet. A discriminator field isn't set through normal channels by making it a dirty field because that would make the entity 'dirty' and always mark it for persistence, which might not always be what you want.

Hmm, I understand the point, but it still doesn't seem correct. I can understand that LL setting the discriminator value shouldn't mark it as dirty, but it also shouldn't interpret the value as null (and not use the predicate test) because it's not dirty. That said, it's not my code wink and I understand that there are probably many other aspects that I am not taking into account. Of course I'm being simplistic and greedy in solving for my one, narrow, specific problem.

the routine you referred to is meant to determine if a field is indeed set to a value in the context of 'is this entity dirty'. In that light, what it does now is correct. For your situation, it lacks a test. The problem is, if I add the test for the discriminator field, you'll get false positives in other situations, hence the reason it's as it is now. simple_smile

It might be better to focus on what you want to do: filter on a type in-memory.

Otis wrote:

What you should use instead is a DelegatePredicate to test on the type (the .NET type) of the entity instead for in-memory filters on target-per-entity-hierarchy entities. Would that work for you?

Hmmm, my discriminator field contains bit values that I'm already using masking on in a lot of other scenarios and that's what I wanted to do here. I haven't yet checked, but it sounds like the DelegatePredicate isn't going to support such operations.

You don't have to. An entity object has a .NET type, so you can just use the 'is' operator or use Type.IsAssignableFrom(..) to test whether an entity is of the type you're looking for. I.o.w. you don't need a test on the discriminator field.

Again, it's your code - and I'm not as schooled in ORM theory as you are, so it's possible my suggestion might be off the reservation - but are you sure that my suggestion on the null test [or the mechanics that arrive at the situation] above isn't the right answer? If that was one of my junior's code, I'd be telling them that either their code was wrong, or at the very least, the naming of the function was misleading. If ( field.CurrentValue = 512 ) Then CheckIfCurrentFieldValueIsNull(field) = true simple_smile

For the o/r mapper, a discriminator field is never 'set' to a value, so it is correct in that context.

Frans Bouma | Lead developer LLBLGen Pro
jsaylor
User
Posts: 37
Joined: 20-Sep-2004
# Posted on: 10-May-2009 17:32:31   

Otis wrote:

It might be better to focus on what you want to do: filter on a type in-memory.

Alright, let's stick to that.

Otis wrote:

What you should use instead is a DelegatePredicate to test on the type (the .NET type) of the entity instead for in-memory filters on target-per-entity-hierarchy entities. Would that work for you?

jsaylor wrote:

Hmmm, my discriminator field contains bit values that I'm already using masking on in a lot of other scenarios and that's what I wanted to do here. I haven't yet checked, but it sounds like the DelegatePredicate isn't going to support such operations.

You don't have to. An entity object has a .NET type, so you can just use the 'is' operator or use Type.IsAssignableFrom(..) to test whether an entity is of the type you're looking for. I.o.w. you don't need a test on the discriminator field.

I took a stab at using the DelegratePredicate; I can successfully compare types there using "is", isassignablefrom, or even the actual discriminator value (if I wanted), but I can't pass params to it I'm VB 2.0, so no lambdas - sigh). I could work around that with a stately var that the delegate's expression refers to, but since the delegate is on a view I need to ensure that the state var for the function is in sync with the view usage context. That's doable, but risky and error prone.

IOW if the user wants to filter by X, I could set that to a var that is used by the fn that the DelegatePredicate references, but now I need to make sure that the state of the var and the view itself are always in sync, rather than the predicate containing the values itself.

Hmmm, lambdas look like the real answer here. Or is there another option that I am missing? confused

Again, appreciation in advance for the input. Cheers!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 11-May-2009 17:41:55   

jsaylor wrote:

Otis wrote:

What you should use instead is a DelegatePredicate to test on the type (the .NET type) of the entity instead for in-memory filters on target-per-entity-hierarchy entities. Would that work for you?

jsaylor wrote:

Hmmm, my discriminator field contains bit values that I'm already using masking on in a lot of other scenarios and that's what I wanted to do here. I haven't yet checked, but it sounds like the DelegatePredicate isn't going to support such operations.

You don't have to. An entity object has a .NET type, so you can just use the 'is' operator or use Type.IsAssignableFrom(..) to test whether an entity is of the type you're looking for. I.o.w. you don't need a test on the discriminator field.

I took a stab at using the DelegratePredicate; I can successfully compare types there using "is", isassignablefrom, or even the actual discriminator value (if I wanted), but I can't pass params to it I'm VB 2.0, so no lambdas - sigh). I could work around that with a stately var that the delegate's expression refers to, but since the delegate is on a view I need to ensure that the state var for the function is in sync with the view usage context. That's doable, but risky and error prone.

IOW if the user wants to filter by X, I could set that to a var that is used by the fn that the DelegatePredicate references, but now I need to make sure that the state of the var and the view itself are always in sync, rather than the predicate containing the values itself.

Hmmm, lambdas look like the real answer here. Or is there another option that I am missing? confused

Again, appreciation in advance for the input. Cheers!

Aha, I see your problem indeed...

I think the solution is quite simple, but it does require some coding. Instead of using the delegatepredicate, you could use a custom class, derived from Predicate (the base class for all predicate classes) and in the constructor you pass in the type to filter on, and in the override of InterpretPredicate, you simply compare the type of the passed in entity with the type passed to the constructor of the predicate and return true if it's a match, and false if not.

In your filter code, simply use an instance of your predicate class (which can be in VB.NET and can be located in your own vs.net project) instead of the fieldcomparevalue predicate you use now for testing on the discriminator field type.

If you need help with this, please let me know. simple_smile

Frans Bouma | Lead developer LLBLGen Pro
jsaylor
User
Posts: 37
Joined: 20-Sep-2004
# Posted on: 11-May-2009 21:35:22   

Otis wrote:

I think the solution is quite simple, but it does require some coding. Instead of using the delegatepredicate, you could use a custom class, derived from Predicate (the base class for all predicate classes) and in the constructor you pass in the type to filter on, and in the override of InterpretPredicate, you simply compare the type of the passed in entity with the type passed to the constructor of the predicate and return true if it's a match, and false if not.

(slaps self upside forehead). Yep, worked like a champ. The upside is that the constructor is now typed to the enum bits that we're using, so we actually get intellisense and better dev guidance simple_smile

I'm sorry I didn't think of this earlier. Inheriting from 3rd party stuff is not my normal go-to answer - primarily because most third party stuff isn't thought out well enough to effectively allow it. But I've found LL to be great at extensibility - heck, in the current project we're already inheriting a number of other objs so I don't know why inheriting a Predicate didn't occur to me before now.

Thanks again for the effort, much appreciated!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 12-May-2009 10:38:16   

jsaylor wrote:

Otis wrote:

I think the solution is quite simple, but it does require some coding. Instead of using the delegatepredicate, you could use a custom class, derived from Predicate (the base class for all predicate classes) and in the constructor you pass in the type to filter on, and in the override of InterpretPredicate, you simply compare the type of the passed in entity with the type passed to the constructor of the predicate and return true if it's a match, and false if not.

(slaps self upside forehead). Yep, worked like a champ. The upside is that the constructor is now typed to the enum bits that we're using, so we actually get intellisense and better dev guidance simple_smile

I'm sorry I didn't think of this earlier. Inheriting from 3rd party stuff is not my normal go-to answer - primarily because most third party stuff isn't thought out well enough to effectively allow it. But I've found LL to be great at extensibility - heck, in the current project we're already inheriting a number of other objs so I don't know why inheriting a Predicate didn't occur to me before now.

Thanks again for the effort, much appreciated!

simple_smile Glad you've fixed this one simple_smile The predicate system is designed to be extensible actually, though indeed one would think that everything should be inside the box and just work, that's a thought I'd have had as well, though as that's impossible, we tried to make things extensible as much as possible without forcing the developer to do extension by default because a lot is missing (like the entity framework).

Frans Bouma | Lead developer LLBLGen Pro