locked
How to query a dictionary by another dictionary in C# RRS feed

  • Question

  • User-1188570427 posted

    Hello,

    I need to query a dictionary by another dictionary

    var dictionary = new Dictionary<string, string>();

    var queryDictionary = new Dictionary<string, string>();

    So I want to query dictionary and only pull back what matches in queryDictionary based on the <key, value>().

    I can't seem to figure it out.

    I've tried KeyValue  pairs, but it doesn't work. 

    Monday, January 18, 2021 3:28 PM

Answers

  • User303363814 posted

    I would create a little method since the logic is complex

    IEnumerable<MyFiles> MatchOnQuery(
                                IDictionary<string, string> query, 
                                IList<MyFiles> files)
    {
       return files
                .Where(f => query
                               .All(q => f.Metadata.ContainsKey(q.Key) &&
                                         f.Metadata[q.Key] == q.Value)
                      );
    }

    Calling MatchOnQuery(queryDictionary, list) returns you the matching MyFiles

    (BTW - personal bugbear of mine, MtFiles seems to model a single thing - a file.  Give it a singular name - MyFile.  It really makes a big difference when you are thinking about the logic and playing with possible code to have singular names for single things and plural names for plural things.)

    (BTW-2 - Thanks for the succinct sample, data and expectations.  It would be great if more questions were presented like you have done.  Having a sample which can be executed with known results makes it so much easier for people to help)

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, January 18, 2021 10:39 PM
  • User-1188570427 posted

    PaulTheSmith

    I would create a little method since the logic is complex

    IEnumerable<MyFiles> MatchOnQuery(
                                IDictionary<string, string> query, 
                                IList<MyFiles> files)
    {
       return files
                .Where(f => query
                               .All(q => f.Metadata.ContainsKey(q.Key) &&
                                         f.Metadata[q.Key] == q.Value)
                      );
    }

    Calling MatchOnQuery(queryDictionary, list) returns you the matching MyFiles

    (BTW - personal bugbear of mine, MtFiles seems to model a single thing - a file.  Give it a singular name - MyFile.  It really makes a big difference when you are thinking about the logic and playing with possible code to have singular names for single things and plural names for plural things.)

    (BTW-2 - Thanks for the succinct sample, data and expectations.  It would be great if more questions were presented like you have done.  Having a sample which can be executed with known results makes it so much easier for people to help)

    Thanks PaulTheSmith!

    This is close to what I ended up doing.

    Here is mine:

                // only pull blobItems with metadata that match AND that are not deleted.
                blobItems = blobItems.Where(
                    mf => metadata.All(
                        kvp => mf.Metadata.TryGetValue(kvp.Key, out var outMetadata) && outMetadata == kvp.Value
                               && (mf.Metadata.TryGetValue(FileManagerMetadataContants.IsFileDeleted, out var outIsFileDelete) && outIsFileDelete == FileManagerMetadataContants.IsFileDeletedValue) == false));

    Other *possible items: c# - How to query a nested object dictionary against another dictionary key/values matching - Stack Overflow

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, January 19, 2021 2:41 PM

All replies

  • User475983607 posted

    Shared sample data, code, and expected results. 

    Monday, January 18, 2021 3:35 PM
  • User-1188570427 posted

    mgebhard

    Shared sample data, code, and expected results. 

    Data, Code, expected results:

        public class MyFiles
        {
            public int Id { get; set; }
            public string FileName { get; set; }
    
            public IDictionary<string, string> Metadata { get; set; } = new Dictionary<string, string>();
        }
            private void button1_Click(object sender, EventArgs e)
            {
                var list = new List<MyFiles>();
    
                list.Add(new MyFiles
                {
                    Id = 1,
                    FileName = "myTestfile.jpg",
                    Metadata = new Dictionary<string, string>
                    {
                        { "FileSize", "599" },
                        { "GroupId", "25" },
                        { "TypeId", "Finance" },
    
                    }
                });
    
                list.Add(new MyFiles
                {
                    Id = 2,
                    FileName = "myTestfile1.jpg",
                    Metadata = new Dictionary<string, string>
                    {
                        { "FileSize", "748" },
                        { "GroupId", "25" },
                        { "TypeId", "HR" },
    
                    }
                });
    
                list.Add(new MyFiles
                {
                    Id = 3,
                    FileName = "myTestfile23.jpg",
                    Metadata = new Dictionary<string, string>
                    {
                        { "FileSize", "718" },
                        { "GroupId", "78" },
                        { "TypeId", "IT" },
    
                    }
                });
    
                list.Add(new MyFiles
                {
                    Id = 4,
                    FileName = "myTestfile2.jpg",
                    Metadata = new Dictionary<string, string>
                    {
                        { "FileSize", "783" },
                        { "GroupId", "78" },
                        { "TypeId", "IT" },
    
                    }
                });
    
    
                // Instance one
                var queryDictionary = new Dictionary<string, string>();
    
                queryDictionary.Add("GroupId", "78");
                queryDictionary.Add("TypeId", "IT");
    
                // Expected results would be Ids: 3 and 4
    
                // Instance two
                queryDictionary.Clear();
    
                queryDictionary.Add("GroupId", "78");
                queryDictionary.Add("FileSize", "783");
    
                // Expected results would be Ids: 4
    
            }
            public IEnumerable<AzureStorageFileDownloadResultDTO> GetFiles(IDictionary<string, string> metadata, BlobContainerClientDTO blobContainerClient)
            {
    
            }

    Directly above is what I would like it to be, but I can't figure out how to query it properly.

    Monday, January 18, 2021 3:57 PM
  • User-1188570427 posted

    Here is the initial code.

    Here I'm only checking one key value pair where I need to check a dictionary of metadata <string, string>():

            public IEnumerable<AzureStorageFileDownloadResultDTO> GetFiles(ModuleType moduleType, string metadataKey, string metadataValue, BlobContainerClientDTO blobContainerClient)
            {
                if (blobContainerClient == null)
                {
                    throw new ArgumentNullException(nameof(blobContainerClient));
                }
    
                if (string.IsNullOrWhiteSpace(blobContainerClient.ConnectionString) == true)
                {
                    throw new PropertyNullOrWhiteSpaceException(nameof(blobContainerClient.ConnectionString));
                }
    
                if (string.IsNullOrWhiteSpace(blobContainerClient.ContainerName) == true)
                {
                    throw new PropertyNullOrWhiteSpaceException(nameof(blobContainerClient.ContainerName));
                }
    
                if (moduleType == ModuleType.None)
                {
                    throw new ArgumentNullException(nameof(moduleType));
                }
    
                if (string.IsNullOrWhiteSpace(metadataKey) == true)
                {
                    throw new ArgumentOutOfRangeException(nameof(metadataKey));
                }
    
                if (string.IsNullOrWhiteSpace(metadataKey) == true)
                {
                    throw new PropertyNullOrWhiteSpaceException(nameof(metadataKey));
                }
    
                if (string.IsNullOrWhiteSpace(metadataValue) == true)
                {
                    throw new PropertyNullOrWhiteSpaceException(nameof(metadataValue));
                }
    
                BlobContainerClient container = GetBlobContainerForDownloads(blobContainerClient);
    
                var blobItems = GetAllBlobsForContainer(container);
    
                if (blobItems == null)
                {
                    return Enumerable.Empty<AzureStorageFileDownloadResultDTO>();
                }
    
                IList<AzureStorageFileDownloadResultDTO> results = new List<AzureStorageFileDownloadResultDTO>();
    
                AzureStorageFileDownloadResultDTO result;
    
                // pull all blob items within the container where there is a meta data match for the file manager module type AND the meta data key and value passed in AND there is not a value of IsFileDeleted as "true"
                foreach (var item in blobItems.Where(w => w.Metadata.Contains(new KeyValuePair<string, string>(FileManagerMetadataContants.ModuleType, moduleType.ToString())) == true && w.Metadata.Contains(new KeyValuePair<string, string>(metadataKey, metadataValue)) == true && w.Metadata.Contains(new KeyValuePair<string, string>(FileManagerMetadataContants.IsFileDeleted, FileManagerMetadataContants.IsFileDeletedValue)) == false))
                {
                    result = new AzureStorageFileDownloadResultDTO()
                    {
                        FileData = null, // do not pull the file data when returning all the files ; the developer will return back to the API to get the actual file with the blob name
                        FileFound = true,
                        BlobName = item.Name,
                        FileName = GetFileName(item.Metadata),
                        FileNameWithExtension = GetFileNameWithExtension(item.Metadata),
                        ContentType = item.Properties.ContentType,
                        FileExtension = item.Properties.ContentType,
                        Metadata = item.Metadata
                    };
    
                    results.Add(result);
                }
    
                return results;
            }

    Monday, January 18, 2021 4:07 PM
  • User-1188570427 posted

    Here is what the error looks like when I try to just do .Contains() on the metadata:

    Error    CS1503    Argument 1: cannot convert from 'System.Collections.Generic.IDictionary<string, string>' to 'System.Collections.Generic.KeyValuePair<string, string>'

    https://imgur.com/a/BMdRXLf

    Monday, January 18, 2021 4:23 PM
  • User-1188570427 posted

    Shared sample data, code, and expected results. 

    I've tried this, but it isn't working. It will remove the blob item with a meta data custom item as IsFileDeleted as true and IsFileDeletedValue was true, but does not removed the blobItem with a ModuleId as 3, as I am passing in for the ModuleId item to be 5.

                foreach (var item in blobItems.Where(w => w.Metadata.Any(a => (metadata.Select(s => s.Key).Contains(a.Key) == true
                                                                           && metadata.Select(s => s.Value).Contains(a.Value) == true)
                                                                           && (metadata.Select(s => s.Key).Contains(FileManagerMetadataContants.IsFileDeleted) == false
                                                                           && metadata.Select(s => s.Key).Contains(FileManagerMetadataContants.IsFileDeletedValue) == false)) == true))
                {
                    var newFileItem = new FileItemDTO
                    {
                        Id = item.Name,
                        FileId = GetMetadataValue(item.Metadata, FileManagerMetadataContants.FileId),
                        FileName = GetMetadataValue(item.Metadata, FileManagerMetadataContants.FileName),
                        FileNameWithExtension = GetMetadataValue(item.Metadata, FileManagerMetadataContants.FileNameWithExtension),
                        FileExtension = GetMetadataValue(item.Metadata, FileManagerMetadataContants.FileExtension),
                        FileSize = item.Properties.ContentLength ?? 0,
                        ModuleId = GetMetadataValue(item.Metadata, FileManagerMetadataContants.ModuleId),
                        EndItemId = GetMetadataValue(item.Metadata, FileManagerMetadataContants.EndItemId),
                        EndItemTypeId = GetMetadataValue(item.Metadata, FileManagerMetadataContants.EndItemTypeId),
                        MetadataDictionary = item.Metadata,
                    };
    
                    // Convert the metadata dictionary into a IList<MetadataDTO> so that the developer can query against it easier compared to querying against a dictionary
                    foreach (var itemMetadataJson in newFileItem.MetadataDictionary)
                    {
                        newFileItem.Metadata.Add(new MetadataDTO { Key = itemMetadataJson.Key, Value = itemMetadataJson.Value });
                    }
    
                    fileItems.Add(newFileItem);
                }

    Monday, January 18, 2021 8:51 PM
  • User-158764254 posted

    I think i would approach this by looking for the Intersect of the 2 disctionaries.

    https://stackoverflow.com/questions/10685142/c-sharp-dictionaries-intersect

    Monday, January 18, 2021 8:53 PM
  • User-1188570427 posted

    I think i would approach this by looking for the Intersect of the 2 disctionaries.

    https://stackoverflow.com/questions/10685142/c-sharp-dictionaries-intersect

    This is just checking the key though. I need to check the Key and the Value to make sure they match?

    Monday, January 18, 2021 9:22 PM
  • User-1188570427 posted

    I think i would approach this by looking for the Intersect of the 2 disctionaries.

    https://stackoverflow.com/questions/10685142/c-sharp-dictionaries-intersect

    Mine are set as IDictionary, I think they need to be Dictionary before the .ContainsValue is available.

    Monday, January 18, 2021 9:29 PM
  • User303363814 posted

    I would create a little method since the logic is complex

    IEnumerable<MyFiles> MatchOnQuery(
                                IDictionary<string, string> query, 
                                IList<MyFiles> files)
    {
       return files
                .Where(f => query
                               .All(q => f.Metadata.ContainsKey(q.Key) &&
                                         f.Metadata[q.Key] == q.Value)
                      );
    }

    Calling MatchOnQuery(queryDictionary, list) returns you the matching MyFiles

    (BTW - personal bugbear of mine, MtFiles seems to model a single thing - a file.  Give it a singular name - MyFile.  It really makes a big difference when you are thinking about the logic and playing with possible code to have singular names for single things and plural names for plural things.)

    (BTW-2 - Thanks for the succinct sample, data and expectations.  It would be great if more questions were presented like you have done.  Having a sample which can be executed with known results makes it so much easier for people to help)

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, January 18, 2021 10:39 PM
  • User-1188570427 posted

    PaulTheSmith

    I would create a little method since the logic is complex

    IEnumerable<MyFiles> MatchOnQuery(
                                IDictionary<string, string> query, 
                                IList<MyFiles> files)
    {
       return files
                .Where(f => query
                               .All(q => f.Metadata.ContainsKey(q.Key) &&
                                         f.Metadata[q.Key] == q.Value)
                      );
    }

    Calling MatchOnQuery(queryDictionary, list) returns you the matching MyFiles

    (BTW - personal bugbear of mine, MtFiles seems to model a single thing - a file.  Give it a singular name - MyFile.  It really makes a big difference when you are thinking about the logic and playing with possible code to have singular names for single things and plural names for plural things.)

    (BTW-2 - Thanks for the succinct sample, data and expectations.  It would be great if more questions were presented like you have done.  Having a sample which can be executed with known results makes it so much easier for people to help)

    Thanks PaulTheSmith!

    This is close to what I ended up doing.

    Here is mine:

                // only pull blobItems with metadata that match AND that are not deleted.
                blobItems = blobItems.Where(
                    mf => metadata.All(
                        kvp => mf.Metadata.TryGetValue(kvp.Key, out var outMetadata) && outMetadata == kvp.Value
                               && (mf.Metadata.TryGetValue(FileManagerMetadataContants.IsFileDeleted, out var outIsFileDelete) && outIsFileDelete == FileManagerMetadataContants.IsFileDeletedValue) == false));

    Other *possible items: c# - How to query a nested object dictionary against another dictionary key/values matching - Stack Overflow

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, January 19, 2021 2:41 PM