简体   繁体   中英

Why doesn't this if statement work properly

This code is designed to perform a cleanup after a mailbox rehydration (from Symantec Enterprise Vault). We take a snapshot index of the MessageId and ConversationId of all items in the mailbox before the rehydration.

After the rehydration this code

    if (string.Equals(item.ItemClass, "IPM.Note.EnterpriseVault.Shortcut", StringComparison.InvariantCulture) || ((existingIds.Any(x => x.ConversationId == item.ConversationId.ToString()) == false || (item.ItemClass == "IPM.Appointment" && existingIds.Any(x => x.MessageId == item.Id.ToString()) == false) && item.DateTimeReceived < snapshotDate)))
    {
        item.Delete(DeleteMode.HardDelete);
    }

should delete

  1. Any items which have an ItemClass of "IPM.Note.EnterpriseVault.Shortcut"
  2. Any items which have an ItemClass of "IPM.Appointment" where the Id is not in the existingIds list of MessageId s, unless they were received after the `snapshotDate
  3. Any other items where the ConversationId is not in the existingIds list, unless they were received after the snapshotDate .

After running this code a user reported having lost some email that was received after the snapshotDate so it seems I have got the if statement wrong: .( Could somebody tell me please what I have got wrong (or a way that I can break this down to understand it better) and what this code will actually have done so I can let the user know what has been lost. I know lofical ORs are notoriously hard to get write and I think I have made a mistake with the brackets somewhere but I just can't see it.

I find the easiest way to look at this sort of problem is to use lots of line breaks and indentation. I add a break and increase indentation after every ( (except trivial () ), bring matching ) s below their matching pair and put the operators on separate lines between the items they're joining:

if (
    string.Equals(
        item.ItemClass,
        "IPM.Note.EnterpriseVault.Shortcut", StringComparison.InvariantCulture
    )
    ||
    (
        (
            existingIds.Any(
                x => x.ConversationId == item.ConversationId.ToString()
            ) == false
            ||
            (
                item.ItemClass == "IPM.Appointment"
                &&
                existingIds.Any(
                    x => x.MessageId == item.Id.ToString()
                ) == false
            )
            &&
            item.DateTimeReceived < snapshotDate
        )
    )
)
{
    item.Delete(DeleteMode.HardDelete);
}

I can immediately spot two things - there's a pair of parentheses that just contains another pair, and we have && and || occurring at the same "level" so we're relying on operator precedence.

I'm guessing you wanted the && outside of the inner parentheses so that it is applied against both the appointment check and the existing Ids one. Eg this instead:

if (
    string.Equals(
        item.ItemClass,
        "IPM.Note.EnterpriseVault.Shortcut", StringComparison.InvariantCulture
    )
    ||
    (
        (
            existingIds.Any(
                x => x.ConversationId == item.ConversationId.ToString()
            ) == false
            ||
            (
                item.ItemClass == "IPM.Appointment"
                &&
                existingIds.Any(
                    x => x.MessageId == item.Id.ToString()
                ) == false
            )
        )
        &&
        item.DateTimeReceived < snapshotDate
    )
)
{
    item.Delete(DeleteMode.HardDelete);
}

(Once you've confirmed everything matches up as you need, you can collapse back down to fewer lines)

I would recommend you to split if check into local functions it will be much easier to debug.

 if (IsShourtcut(item) || NotExistingAppItment(item) || ExistingConversation(item) && IsReceivedBeforSnapshot(item)) 
    {
            // to delete 
    }

bool IsShourtcut(Item item) => string.Equals(item.ItemClass, "IPM.Note.EnterpriseVault.Shortcut", StringComparison.InvariantCulture);
bool NotExistingAppItment(Item item) => item.ItemClass == "IPM.Appointment" && existingIds.All(x => x.MessageId != item.Id.ToString());
bool ExistingConversation(Item item) => existingIds.All(x => x.ConversationId != item.ConversationId.ToString();
bool IsReceivedBeforSnapshot(Item item) => item.DateTimeReceived < snapshotDate;

Both of the other answers to this were extremely helpful and I have upvoted accordingly. However I thought I would post my final solution to this as inspired by https://stackoverflow.com/a/61009776/470014 and https://stackoverflow.com/a/61008198/470014 , leaning particularly heavily on the use of local functions as recommended by OxQ. I have now written unit tests (which I should have done earlier) and these all pass using my new method whereas five of them were failing before. I believe this code more closely matches the stated problem and is fairly straightforward to read and understand. I have had to use scalar values rather than the whole Item object, so I can unit test this as Exchange Web Services managed API doesn't use interfaces.

switch (itemClass)
{
    case "IPM.Note.EnterpriseVault.Shortcut":
        return true;
    case "IPM.Appointment":
        return NotExistingMessage() && IsReceivedBeforeSnapshot();
    default:
        return NotExistingConversation() && IsReceivedBeforeSnapshot();
}

bool NotExistingMessage() => existingIds.All(x => x.MessageId != messageId);
bool NotExistingConversation() => existingIds.All(x => x.ConversationId != conversationId);
bool IsReceivedBeforeSnapshot() => dateTimeReceived < uploadDate;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM