Start a new topic

Setting constituent attribute conditionally

Is there a way to set a constituent attribute conditionally during an import? For example, the import file contains incoming values for the attribute. But I want to update the value of the existing attribute (in the target constituent record) only if it meets certain criteria. I have inserted code in the AfterConstituentOpen event to get the existing value of the constituent attribute and determine if it meets the criteria. If it doesn't, I clear the incoming attribute value field(s) in the import file so the existing attribute value is not updated.

I thought this was working; however, I have recently discovered that if there is a value specified for the attribute in the import file, by the time the code in the AfterConstituentOpen event runs to get the existing value of the attribute in the constituent record, the attribute in the constituent record will have already been updated by the incoming data. If I want to be able to check the existing value of an attribute, I have to clear the corresponding incoming data fields before the AfterConstituentOpen event. But then I no longer have the incoming attribute value to update the existing value if I need to.

To summarize:
Value of attribute field in import file is "A"
Value of attribute field in constituent record is "B"
I want to update the attribute in the constituent record to the incoming value "A" if the existing value is "B"
When the code in AfterConstituentOpen is run, it returns the value of the attribute in the constituent record as "A" and as a result, it will not be updated
The code will only return the existing value of the attribute in the constituent record as "B" if I first clear the incoming attribute fields

I don't recall this happening before when I first wrote the code. Has something changed in IOM or is this how it has always been?

More importantly, how do I accomplish what I'm trying to do? Is there another event that fires after the constituent record is opened but before the import file values are loaded into the record? Do I have to create an additional virtual field to temporarily store the incoming attribute value so it can be copied to the actual (mapped) attribute field during the AfterConstituentOpen event if needed? I think I tried this but it didn't seem to work. Is it not possible to set any mapped import field at this point?

Any help is appreciated. Thanks!


Hi Raymond,
The AfterConstituentOpen event is the first event that fires and the record has not been modified by IOM yet, it should absolutely contain the correct existing data. If you want to post the block of code you have in AfterConstituentOpen we can take a closer look.
Regards,
Steve
Below is a portion of my code in the AfterConstituentOpen event. The MailFrequency field in the import file is mapped to the Mail Frequency constituent attribute. The value of the Mail Frequency constituent attribute (in the constituent record) assigned to the sMailFrequency variable is the same as the incoming value of the MailFrequency field in the import file when it is checked in the AfterConstituentOpen event. As a result, the IF statement determines that the Mail Frequency constituent attribute should not be updated, even if the incoming value in the import file is not the same as the actual existing value in constituent attribute at time of import:

Public Overrides Sub AfterConstituentOpen(ByVal oRec As Blackbaud.PIA.RE7.BBREAPI.CRecord, ByVal Cancel As ImportOM.API.iCancel)
MyBase.AfterConstituentOpen(oRec, Cancel)

'get constituent ID of existing matched constituent record
Dim sConsID As String = oRec.Fields(ERECORDSFIELDS.RECORDS_fld_CONSTITUENT_ID)
sConsID = oRec.Fields(ERECORDSFIELDS.RECORDS_fld_CONSTITUENT_ID)

'get Mail Frequency constituent attribute
Dim oAttrTypeServer As New CAttributeTypeServer
oAttrTypeServer.Init(Import.SessionContext)
Dim lAttrTypeID As Long

Dim oAttr As IBBAttribute
Dim sMailFrequency As String = ""
Dim sMailFrequencyDate As Date
Dim sMailFrequencyComments As String = ""

For Each oAttr In oRec.Attributes
lAttrTypeID = oAttr.Fields(EattributeFields.Attribute_fld_ATTRIBUTETYPES_ID)

If oAttrTypeServer.GetAttributeTypeDescription(lAttrTypeID) = "Mail Frequency" Then
sMailFrequency = oAttr.Fields(EattributeFields.Attribute_fld_VALUE)
sMailFrequencyDate = oAttr.Fields(EattributeFields.Attribute_fld_ATTRIBUTEDATE)
sMailFrequencyComments = oAttr.Fields(EattributeFields.Attribute_fld_COMMENTS)
End If
Next oAttr

With Import.Fields
'don't update mail frequency attribute if matched record is an existing constituent (not a newly created one)
'AND (incoming Mail Frequency (sMailFrequency) in import file matches existing Mail Frequency (MailFrequency) in constituent record
'OR donor is an existing Good Samaritan (Mail Frequency constituent attribute contains "Good Samaritan"))
If sConsID "" Then 'constituent ID already assigned; existing constituent record
If .GetByName("MailFrequency").Value = sMailFrequency Or Instr(sMailFrequency, "Good Samaritan") > 0 Then
.GetByName("MailFrequency").Value = ""
.GetByName("MailFrequencyDate").Value = ""
.GetByName("MailFrequencyComments").Value = ""
End If
End If
End With
End Sub

If I add virtual columns for the Mail Frequency attribute description, date and comments (MailFreqDesc, MailFreqDate, MailFreqCom) and map them (instead of the original MailFrequency fields in the import file) to the Mail Frequency constituent attribute fields and then modify the WITH block to set the values of those virtual columns only if the Mail Frequency attribute should be updated, I can get the actual existing Mail Frequency attribute during the AfterConstituentOpen event; however, if I need to also set the Mail Frequency attribute (i.e., because the incoming value is different from the existing one and the existing one does not contain "Good Samaritan"), the code doesn't update the constituent attribute:
...
With Import.Fields
'update mail frequency attribute ONLY if matched record is an existing constituent (not a newly created one)
'AND incoming Mail Frequency (sMailFrequency) in import file does NOT match existing Mail Frequency (MailFrequency) in constituent record
'AND donor is not an existing Good Samaritan (Mail Frequency constituent attribute does NOT contain "Good Samaritan")
If sConsID "" Then 'constituent ID already assigned; existing constituent record
If .GetByName("MailFrequency").Value sMailFrequency And Instr(sMailFrequency, "Good Samaritan") = 0 Then
.GetByName("MailFreqDesc").Value = .GetByName("MailFrequency").Value
.GetByName("MailFreqDate").Value = .GetByName("MailFrequencyDate").Value
.GetByName("MailFreqCom").Value = .GetByName("MailFrequencyComments").Value
End If
End If
End With
...

So either the Mail Frequency constituent attribute is not updated in the constituent record because the existing value is always returned as the same as the incoming value when compared, or the actual existing Mail Frequency attribute is returned correctly but the Mail Frequency constituent attribute is not updated in the constituent record.

What is wrong?
Isn't the Constituent ID set to -1 when it is a new record?
Not that it is directly related to the issue you are seeing...
So it seems that if the Mail Frequency column in the import file is mapped to the Mail Frequency constituent attribute, IOM will always update the constituent attribute (and report the updated value) before it is retrieved by the For...Next loop in the AfterConstituentOpen event. The problem is not caused by the additional business logic that sets the Mail Frequency in the BeforeDictionaries event. Even if I set a Mail Frequency value in the import file and don't modify it through the API, the existing Mail Frequency attribute from the constituent record will always be reported as the incoming value as long as the Mail Frequency column in the import file is mapped to the constituent attribute. It's only when I remove the mapping that the true existing Mail Frequency constituent attribute value is reported.

To get around this, I have decided to not map the Mail Frequency column to the constituent attribute and to insert additional code in the For...Next loop in the AfterConstituentOpen event to set the constituent attribute by assigning a value directly to oAttr.Fields(EattributeFields.Attribute_fld_VALUE) instead of setting the value of the Mail Frequency column in the import file. Here is some code to illustrate:

    Public Overrides Sub AfterConstituentOpen(ByVal oRec As Blackbaud.PIA.RE7.BBREAPI.CRecord, ByVal Cancel As ImportOM.API.iCancel)
        MyBase.AfterConstituentOpen(oRec, Cancel)
       
        Dim oAttrTypeServer As New CAttributeTypeServer
        oAttrTypeServer.Init(Import.SessionContext)
        Dim lAttrTypeID As Long
        Dim oAttr As IBBAttribute
        Dim sMailFrequency As String = ""
   
        For Each oAttr In oRec.Attributes
            lAttrTypeID = oAttr.Fields(EattributeFields.Attribute_fld_ATTRIBUTETYPES_ID)
           
            If oAttrTypeServer.GetAttributeTypeDescription(lAttrTypeID) = "Mail Frequency" Then
                sMailFrequency = oAttr.Fields(EattributeFields.Attribute_fld_VALUE)
                MsgBox("Existing Mail Frequency in constituent record: " & sMailFrequency)
               
                With Import.Fields
                    If .GetByName("MailFrequency").Value sMailFrequency Then
                        If sMailFrequency.Contains("Good Samaritan") = False Then
                            oAttr.Fields(EattributeFields.Attribute_fld_VALUE) = .GetByName("MailFrequency").Value
                            MsgBox("Updated Mail Frequency in constituent record: " & oAttr.Fields(EattributeFields.Attribute_fld_VALUE))
                        Else
                            MsgBox("Existing Mail Frequency contains 'Good Samaritan'. Mail Frequency not updated.")
                        End If
                    Else
                        MsgBox("Incoming Mail Frequency is the same as the existing Mail Frequency. Mail Frequency not updated.")
                    End If
                End With
               
                Exit For
            End If
        Next oAttr
    End Sub


I haven't written all of the code yet but this seems to now do what I want: check/set/modify the incoming Mail Frequency value but also conditionally set/update the constituent attribute.

I was so used to using the API to only set/modify column values in the import file that I didn't think of setting the corresponding attribute fields directly (i.e., without setting the column values in the import file) in the AfterConstituentOpen event. This will certainly open up some new possibilities!

Thanks to all who helped.
I just built a simplified version of this in IOM and it seems to work as expected. My attribute name is "Room Number" but it pops up the current value in a MsgBox and then modifies the value from the import profile. If I run it again then it pops up the new value.

If you run this simple version in a new profile does it still show strange results?

[code] Public Overrides Sub AfterConstituentOpen(ByVal oRec As Blackbaud.PIA.RE7.BBREAPI.CRecord, ByVal Cancel As ImportOM.API.iCancel) Dim oAttrTypeServer As New CAttributeTypeServer oAttrTypeServer.Init(Import.SessionContext) Dim lAttrTypeID As Long Dim oAttr As IBBAttribute Dim sMailFrequency As String = "" For Each oAttr In oRec.Attributes lAttrTypeID = oAttr.Fields(EattributeFields.Attribute_fld_ATTRIBUTETYPES_ID) If oAttrTypeServer.GetAttributeTypeDescription(lAttrTypeID) = "Room Number" Then sMailFrequency = oAttr.Fields(EattributeFields.Attribute_fld_VALUE) MsgBox("Found Att: " & sMailFrequency) End If Next oAttr End Sub [/code]
Hi Wayne,

I made a copy of the import profile (so could reuse the same test import file without having to redo the field mapping) and replaced my code in the AfterConstituentOpen event with your code (actually, I commented out mine and I replaced "Room Number" in your code with "Mail Frequency" since I don't have an attribute with that name). I also commented out the MyBase.AfterConstituentOpen(oRec, Cancel) line since it wasn't in your simplified code.

When I tested the import, the result was no different from what happens when using my own code: your message box incorrectly reported the existing value of the Mail Frequency attribute as the incoming value of the constituent attribute. As with my code, if I also commented out the code in the BeforeDictionaries event that assigns a value to the Mail Frequency field (which is mapped to the constituent attribute) according to business rules (i.e., the incoming value is left untouched), then the existing value of the Mail Frequency constituent attribute is reported correctly.

Does your test import file include a column that is mapped to the constituent attribute in the profile and does your BeforeDictionaries event include code to assign a value to that field (e.g., Import.Fields.GetByName("MappedAttribute").Value = "some value")? If not, could you add them and see if you can recreate the behaviour I'm observing?

By the way, I don't know if this makes a difference but for reference, I'm using IOM v 3.3.2.0.
Since commenting out code in BeforeDictionaries makes it work I suspect that it's something happening before AfterConstituentOpen that is messing this up.

Try starting a whole new profile and putting only the test code in with just a couple of columns for the constituent id and a new value for the attribute in question. You really just need 1 test row to see if it works.

If you want to post your complete code here we might be able to pick something out that is setting the value before you are reading it.
Hi Raymond,
Two things:
1) By deafult, the ConsID is populated automatically when adding a new record in RE, but that can be turned off in Config and I presume it is in your case. If it is turned off, existing records will have a blank Cons ID until it is populated by some process or manually. The RECORDS_fld_ID is always -1 on new records and always has a value for existing records.

2) There may be an issue with the following:
If .GetByName("MailFrequency").Value = sMailFrequency Or Instr(sMailFrequency, "Good Samaritan") > 0 Then

Recent version of .NET have replaced And Or with AndAlso OrElse, because the statement above does not have any () it may be evaluated in an unexpected way.
It could be any of the following:
If .GetByName("MailFrequency").Value = (sMailFrequency Or Instr(sMailFrequency, "Good Samaritan")) > 0 Then
If .GetByName("MailFrequency").Value = (sMailFrequency Or (Instr(sMailFrequency, "Good Samaritan") > 0)) Then
If ((.GetByName("MailFrequency").Value = sMailFrequency) Or Instr(sMailFrequency, "Good Samaritan")) > 0 Then
If (.GetByName("MailFrequency").Value = sMailFrequency) Or (Instr(sMailFrequency, "Good Samaritan") > 0) Then

The last one is what you want.

Also, I am guessing that the Mail Frequency attribute is a unique attribute so only one per record is permitted. In your For Each attribute loop you could add an Exit For after you find a match so that you don't loop through all the attributes for no reason. Probably not an issue at all, just good practice

Also, when reading incoming field values you might want to wrap them with this to remove any leading or trailing spaces.
Convert.ToString(.GetByName("MailFrequency").Value).Trim()

Please let us know if you are still getting unexpected results.
First of all, thanks, Steve and Wayne for replying...

I wasn't aware that the RECORDS_fld_ID field could be used to test whether a constituent record is new or existing (I knew only about checking whether or not RECORDS_fld_CONSTITUENT_ID was blank). Is there any benefit/reason to use one method over the other?

Also, even if I modify the IF statement as Steve has suggested, I've noticed that if I insert a MsgBox before the IF statement to display the current values for sMailFrequency and .GetByName("MailFrequency").Value, they are reported as identical by the time the IF statement is encountered. When I change the incoming Mail Frequency in the import file, that value is assigned to sMailFrequency from oAttr.Fields(EattributeFields.Attribute_fld_VALUE). I have screen shots but don't know how to attach them to this post. Let me know if you want me to send them by e-mail.

I should also mention that there is also code in the BeforeDictionaries event that assigns a value to the MailFrequency field in the import file based on certain business rules. If I insert a MsgBox to report the current value at the end of the AfterDictionaries event, the value is the same value reported later on in the AfterConstituentOpen event before the IF statement. The only way to get the actual Mail Frequency value from the constituent record during the AfterConstituentOpen event is to clear or not assign any value to the MailFrequency field in the import file; otherwise, when I request the value of the Mail Frequency attribute in the constituent record, it is always returned as the incoming value in the import file.

Any other suggestions?
The problem with the Constituent ID field is that it is not required and can be blank on existing records, if you have the option set to automatically generate constituent IDs then I would expect that new records will always have a value by the time they loaded (AfterOpen). If the auto generate option is not turned on then the Cons ID will always be blank on new records until some process adds it, either manual or otherwise. The record system ID, RECORDS_fld_ID, is always -1 until the record is saved. That applies to all dataobjects in RE.
I am not sure why it would behaving the way you are seeing, perhaps it could be checked in BeforeConstituentOpen? I have not done much in IOM customizations, but I know the RE API very well. I am not positive about the order of events in IOM processing.
Login to post a comment