简体   繁体   中英

c# linq filtering based on given list

I have situation where I need to use C# Linq and filter Tags based on admin scopes but stuck with one problem that I cannot find a proper way either its a problem with OR operator or I'm doing something wrong with .All().

Basically I need something like that: Read scope - must match all Then Delete scope should match only one or multiple, Then Write scope should match only one or multiple

I wrote comments on code maybe someone have better idea how to filter it?

Here is example:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp4
{
    public class Admin
    {
        public List<string> ReadScopes { get; set; } = new List<string>();
        public List<string> DeleteScopes { get; set; } = new List<string>();
        public List<string> WriteScopes { get; set; } = new List<string>();
    }

    public class Tag
    {
        public Guid Id { get; set; } = Guid.NewGuid();
        
        public string Name { get; set; }
        
        public List<Rules> Rules { get; set; }
    }

    public class Rules
    {
        public string Value { get; set; }

        public Scope Scope;
    }

    public enum Scope
    {
        Read, Delete, Write
    }


    class Program
    {
        static void Main(string[] args)
        {
            var tags = new List<Tag>
            {
                new Tag 
                { 
                    Name = "Tag0", 
                    // NO RULES AVAILABLE FOR ALL
                    Rules = new List<Rules>() 
                },
                new Tag { Name = "Tag1", Rules = new List<Rules>() 
                {
                    //MUST MATCH ALL:
                    new Rules { Value = "Read_With_1Scope", Scope = Scope.Read },
                    new Rules { Value = "Read_With_2Scope", Scope = Scope.Read }
                }},
                
                new Tag { Name = "Tag2", Rules = new List<Rules>()
                {
                    //MUST MATCH ALL:
                    new Rules { Value = "Read_With_1Scope", Scope = Scope.Read },
                    new Rules { Value = "Read_With_2Scope", Scope = Scope.Read },
                    // AND THEN MUST MATCH ONE OF THESE:
                    new Rules { Value = "Delete_With_1Scope", Scope = Scope.Delete },
                    new Rules { Value = "Delete_With_2Scope", Scope = Scope.Delete },
                }},
                
                new Tag { Name = "Tag3", Rules = new List<Rules>()
                {
                    //MUST MATCH ALL:
                    new Rules { Value = "Read_With_1Scope", Scope = Scope.Read },
                    new Rules { Value = "Read_With_2Scope", Scope = Scope.Read },
                    // AND THEN MUST MATCH ONE OF THESE:
                    new Rules { Value = "Write_With_1Scope", Scope = Scope.Write },
                    new Rules { Value = "Write_With_2Scope", Scope = Scope.Write },
                }},

                new Tag { Name = "Tag4", Rules = new List<Rules>()
                {
                    //MUST MATCH ALL:
                    new Rules { Value = "Read_With_1Scope", Scope = Scope.Read },
                    new Rules { Value = "Read_With_2Scope", Scope = Scope.Read },
                    // AND THEN MUST MATCH ONE OF THESE:
                    new Rules { Value = "Delete_With_1Scope", Scope = Scope.Delete },
                    new Rules { Value = "Delete_With_2Scope", Scope = Scope.Delete },
                    // AND THEN MUST MATCH ONE OF THESE:
                    new Rules { Value = "Write_With_1Scope", Scope = Scope.Write },
                    new Rules { Value = "Write_With_2Scope", Scope = Scope.Write },
                }},
                
                new Tag { Name = "Tag5", Rules = new List<Rules>()
                {
                    // ONLY MUST MATCH ONE OF THESE:
                    new Rules { Value = "Delete_With_1Scope", Scope = Scope.Delete },
                    new Rules { Value = "Delete_With_2Scope", Scope = Scope.Delete },
                    // AND THEN MUST MATCH ONE OF THESE:
                    new Rules { Value = "Write_With_1Scope", Scope = Scope.Write },
                    new Rules { Value = "Write_With_2Scope", Scope = Scope.Write },
                }},
                
                new Tag { Name = "Tag6", Rules = new List<Rules>()
                {
                    // ONLY MUST MATCH ONE OF THESE:
                    new Rules { Value = "Delete_With_1Scope", Scope = Scope.Delete },
                    new Rules { Value = "Delete_With_2Scope", Scope = Scope.Delete },
                }},
                
                new Tag { Name = "Tag7", Rules = new List<Rules>()
                {
                    // ONLY MUST MATCH ONE OF THESE:
                    new Rules { Value = "Write_With_1Scope", Scope = Scope.Write },
                    new Rules { Value = "Write_With_2Scope", Scope = Scope.Write },
                }},
            };

            //GET Tag0, Tag1
            var admin = new Admin
            {
                ReadScopes = new List<string> { "Read_With_1Scope", "Read_With_2Scope" },
            };

            //GET Tag0, Tag1, Tag2
            var admin1 = new Admin
            {
                ReadScopes = new List<string> { "Read_With_1Scope", "Read_With_2Scope" },
                DeleteScopes = new List<string> { "Delete_With_1Scope" },
            };
            //.....
            //.....

            //GET Tag0, Tag7
            var admin2 = new Admin
            {
                WriteScopes = new List<string> { "Write_With_1Scope" },
            };

            //ADMIN1 TEST - PROBLEM WITH TAG2
            var available_tags = tags
                .Where(tag => tag.Rules.All(rule => 
                    admin1.ReadScopes.Any(value => rule.Value == value) 
                    ||
                    admin1.DeleteScopes.Any(value => rule.Value == value)
                    ||
                    admin1.WriteScopes.Any(value => rule.Value == value)
                    ));
            foreach (var available_tag in available_tags)
            {

                Console.WriteLine(available_tag.Name);
            }

            Console.ReadLine();
        }
    }
}

Forgive me if I misunderstand, but let's have a look. You say admin1, should receive this tag:

        new Tag { Name = "Tag2", Rules = new List<Rules>()
        {
            //MUST MATCH ALL:
            new Rules { Value = "Read_With_1Scope", Scope = Scope.Read },
            new Rules { Value = "Read_With_2Scope", Scope = Scope.Read },
            // AND THEN MUST MATCH ONE OF THESE:
            new Rules { Value = "Delete_With_1Scope", Scope = Scope.Delete },
            new Rules { Value = "Delete_With_2Scope", Scope = Scope.Delete },
        }},

So in order for admin1 to receive this tag, he must satisfy ALL conditions to receive this tag. However, he's missing Delete_with_2Scope, so since he doesn't satisfy that condition, he does not receive the tag.

If you add that scope in his delete scopes for example, then he should receive tag2.

your original code goes through all the tags and filter to take only the tags that satisfy the condition for all the rules in that tag ( tag.Rules.All(...) ) and obviously not what you want

To fix it, you need to filter rules by scope for each tag, and use All() if you want all the filtered rules to match, or Any() if any of them to match. Something like this:

var available_tags = tags
    .Where(tag => 
        tag.Rules.Where(r => r.Scope == Scope.Read).All(r => admin1.ReadScopes.Contains(r.Value))) &&
        (tag.Rules.Where(r => r.Scope == Scope.Write).Count() == 0 || tag.Rules.Where(r => r.Scope == Scope.Write).Any(r => admin1.WriteScopes.Contains(r.Value))) &&
        (tag.Rules.Where(r => r.Scope == Scope.Delete).Count() == 0 || tag.Rules.Where(r => r.Scope == Scope.Delete).Any(r => admin1.DeleteScopes.Contains(r.Value))));

the checking for Count() == 0 is necessary because if there are no rules for that scope, Any() will return false.

To make it a bit nicer and more efficient, I'll create some helper methods like:

private static bool MatchAllRules(List<string> adminRules, List<Rules> allRules, Scope scope)
{
    var rulesByScope = allRules.Where(r => r.Scope == scope).ToList();
    return rulesByScope.All(r => adminRules.Contains(r.Value));
}

private static bool MatchAnyRule(List<string> adminRules, List<Rules> allRules, Scope scope)
{
    var rulesByScope = allRules.Where(r => r.Scope == scope).ToList();
    if (rulesByScope.Count == 0)
    {
        return true;
    }

    return rulesByScope.Any(r => adminRules.Contains(r.Value));
}

and use them like this:

var available_tags = tags
    .Where(tag => 
        MatchAllRules(admin1.ReadScopes, tag.Rules, Scope.Read) &&
        MatchAnyRule(admin1.WriteScopes, tag.Rules, Scope.Write) &&
        MatchAnyRule(admin1.DeleteScopes, tag.Rules, Scope.Delete));

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