0

I am trying to create a multi-key dictionary where the values are enumerable. The goal is to be able to perform a lookup using the first key, and if there are no matches, to perform anther lookup using the second key.

Here is my solution so far:

public class MultipleKeyDictionary<TEmployeeIDKey, TBadgeNumberKey, TValue> : IEnumerable<TValue>
{
    private object m_data_lock = new object();
    private Dictionary<TBadgeNumberKey, TEmployeeIDKey> badgeNumberDictionary = new Dictionary<TBadgeNumberKey, TEmployeeIDKey>();
    private Dictionary<TEmployeeIDKey, TValue> empIDDictionary = new Dictionary<TEmployeeIDKey, TValue>();


    public void AddValue(TEmployeeIDKey empIDKey, TBadgeNumberKey badgeKey, TValue value)
    {
        lock (m_data_lock)
        {
            badgeNumberDictionary[badgeKey] = empIDKey;
            empIDDictionary[empIDKey] = value;
        }
    }

    public TValue getByBadgeNumber(TBadgeNumberKey badgeKey)
    {
        lock (m_data_lock)
            return empIDDictionary[badgeNumberDictionary[badgeKey]];
    }

    public TValue getByEmployeeID(TEmployeeIDKey empIDKey)
    {
        lock (m_data_lock)
            return empIDDictionary[empIDKey];
    }


    public bool TryGetValueByEmployeeID(TEmployeeIDKey empIDKey, out TValue value)
    {

        return empIDDictionary.TryGetValue(empIDKey, out value);

    }

    public bool TryGetValueByBadgeKey(TBadgeNumberKey badgeNumberKey, out TValue value)
    {
        value = default(TValue);

        if (badgeNumberDictionary.TryGetValue(badgeNumberKey, out TEmployeeIDKey empIDKey))
        {
            return empIDDictionary.TryGetValue(empIDKey, out value);
        }

        return false;
    }


    public IEnumerator<TValue> GetEnumerator()
    {
        return empIDDictionary.Values.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
  }

The problem is that both EmployeeID and BadgeNumber, or either of them could be null. How can I modify this so that I can perform my desired lookup if either key can be null?

EDIT: This is the code I am using to perform the Lookup where gateLogGroups is a group of Gate Logs grouped by BadgeNumber and EmployeeID and invoiceRecords is of class MultipleKeyDictionary

        foreach (var cardNumberGroup in gateLogGroups)
        {
            invoiceRecords.TryGetValueByEmployeeID(cardNumberGroup.Key.EmpIDNum, out InvoiceRecord invoiceRecord);

            if (invoiceRecord == null)
            {
                invoiceRecords.TryGetValueByBadgeKey(cardNumberGroup.Key.CardNumber, out invoiceRecord);

                if (invoiceRecord == null)
                {
                    exceptions.Add(new GateLogWithNoMatchingInvoiceException(cardNumberGroup.GateLogs.ElementAt(0), invoiceRecords.ElementAt(0).InvoiceHeaderId));
                    continue;
                }
            }
Mkodamore
  • 1
  • 2
  • The only way I know of is to wrap your value eg. https://stackoverflow.com/questions/4632945/dictionary-with-null-key (second answer) – Rand Random Jul 20 '20 at 16:57
  • You just have to decide what the semantics are for `null` keys. Nothing prevents your type from allowing them. – Aluan Haddad Jul 20 '20 at 16:58
  • @AluanHaddad the way I have it currently makes it to where if I search by badgeNumber, but employeeID is null then the key that the badgeNumber dictionary returns is null. Meaning I am not able to use it in my lookup using my employeeID dictionary. That is the main issue I am having. – Mkodamore Jul 20 '20 at 17:03
  • If that result means there is no employee, you can check for it like `if (badgeNumberDictionary[badgeKey] == null) throw new KeyNotFoundException();` – Aluan Haddad Jul 20 '20 at 17:08
  • 1
    I feel like the phrase *"multiple key dictionary"* is completely inaccurate, as the class does not implement a dictionary interface. You have two dictionaries, with different key and value types, and a class that provides methods to get a value using one of the two different types. – Rufus L Jul 20 '20 at 17:09
  • Can you please share sample code of what you mean? In otherwords, what code would you like the user to be able to write in order to retrieve a value, where either badge or employee id could be null? – Rufus L Jul 20 '20 at 17:12
  • When you're adding values, if badge number and/or employee id are `null`, then the associated value will overwrite any previous `null` key that was added to the associated dictionary. So it doesn't seem feasible that you could accept `null` in a lookup method for either of those "keys" and still return an accurate value. – Rufus L Jul 20 '20 at 17:15
  • A null EmployeeID doesn't mean that the Employee doesn't exist. The employee data is "bad" and there are two possible identifiers: BadgeNumber and EmployeeID. For each record, both fields could be populated, neither fields could be populated, or either could be populated. If even one of those fields are populated, I want to be able to try and find the employee based on that field. I.E. Search by EmployeeID - EmployeeID is null so no match. Search by BadgeNumber - Match found but returns EmployeeID which is null so I am unable to use it to get the Value from the BadgeNumber Dictionary. – Mkodamore Jul 20 '20 at 17:19
  • @RufusL Added example code for calling the TryGetValueFunctions. I agree with your comment. What other solution could I use to do what I am trying to do? – Mkodamore Jul 20 '20 at 17:33
  • The code you posted does not make much sense to me. It seems that you only use `TBadgeNumberKey` to look up the `TEmployeeIDKey` value. Any code that already has `TEmployeeIDKey` could get the employee record with a _single_ key, and there is a 1-to-1 relationship between `TEmployeeIDKey` and `TBadgeNumberKey`. In other words, this isn't a multi-key dictionary, but rather just a single dictionary that optionally supports a kind of aliasing between keys. In any case, the `null` issue is well-documented, and work-arounds are similarly well-documented. See duplicates. – Peter Duniho Jul 20 '20 at 17:40
  • From the code you've shown, BadgeNumber is just a way to lookup an EmployeeId. But the value associated with EmployeeId is the data we care about. When adding records, if EmployeeId is `null`, then `empIDDictionary[empIDKey] = value;` will work fine for the first record with a null EmployeeId, but if another one comes along, that line of code will overwrite the previous data. So I guess my question is, how can you ever find the data associated with a null EmployeeId in this scenario? – Rufus L Jul 20 '20 at 17:42
  • @RufusL This is my issue that I am having. I understand that the way I went about trying to acheive my goal is incorrect, but I am not sure how to correct it. Essentially what I am after is some sort of Dictionary lookup where I can use either key to find a match even if one of the keys is 'null' – Mkodamore Jul 20 '20 at 17:48
  • You can do it if one key is null, but not both, by using a Tuple that contains both as a key. – Rufus L Jul 20 '20 at 17:49
  • @RufusL Wouldn't using a Tuple make it where both keys have to match? There is the possibility that the EmployeeID will match, but the BadgeNumber doesn't and vice versa. This is okay and expected. – Mkodamore Jul 20 '20 at 17:56
  • Technically a custom class that overrides the `Equals` method would be better (so it handle null values and avoid querying the dictionary keys), but with tuples you could do: `public TValue getByEmployeeID(TEmployeeIDKey empIDKey) { lock (_mDataLock) return _empIdDictionary.First(i => i.Key.Item1.Equals(empIDKey)).Value; }` – Rufus L Jul 20 '20 at 18:02
  • @RufusL that makes sense. Thank you for your help! – Mkodamore Jul 20 '20 at 18:05
  • Here's an untested version that might do the trick: https://dotnetfiddle.net/eTcKxt – Rufus L Jul 20 '20 at 18:27

0 Answers0