拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 Linq中嵌套类的通用层次过滤器

Linq中嵌套类的通用层次过滤器

白鹭 - 2022-02-11 1920 0 0

我正在撰写一个包含这样一个嵌套结构的应用程序:

public class Country
{
    public string Name { get; set; }
    public List<State> States { get; set; } = new();
}

public class State
{
    public string Name { get; set; }
    public List<City> Cities { get; set; }
}

public class City
{
    public string Name { get; set; }
    public List<Shop> Shops { get; set; }
}

public class Shop
{
    public string Name { get; set; }
    public int Area { get; set; }
}

使用模拟资料:

private static List<Country> GenerateData()
    {
        List<Country> countries = new();
        
        Country country =  new()
        {
            Name = "USA", 
            States = new List<State>()
            {
                new State()
                {
                    Name = "Texas",
                    Cities = new List<City>()
                    {
                        new City()
                        {
                            Name = "Dallas",
                            Shops = new List<Shop>()
                            {
                                new Shop()
                                {
                                    Name = "Walmart",
                                    Area = 30000
                                },
                                new Shop()
                                {
                                    Name = "Walmart",
                                    Area = 40000
                                }
                            }
                        },
                        new City()
                        {
                            Name = "Austin",
                            Shops = new List<Shop>()
                            {
                                new Shop()
                                {
                                    Name = "Walmart",
                                    Area = 20000
                                }
                            }
                        }
                    }
                },
                new State()
                {
                    Name = "Alabama",
                    Cities = new List<City>()
                    {
                        new City()
                        {
                            Name = "Auburn",
                            Shops = new List<Shop>()
                            {
                                new Shop()
                                {
                                    Name = "MyShop",
                                    Area = 500
                                }
                            }
                        },
                        new City()
                        {
                            Name = "Dothan",
                            Shops = new List<Shop>()
                            {
                                new Shop()
                                {
                                    Name = "MyShop2",
                                    Area = 6000
                                }
                            }
                        }
                    }
                }
            } 
                
        };
        
        countries.Add(country);

        return countries;
    }

我的目标是像这样过滤这个嵌套结构:

搜索包含“Dal”的城市名称结果应该是从根到商店的完整层次结构。在这种情况下:

- USA
 - Texas
  - Dallas
   - Walmart (Area: 30000)
   - Walmart (Area: 40000)

另一个过滤器可能会过滤商店名称,例如搜索“MyShop2”会导致:

- USA
 - Alabama
  - Dothan
   - MyShop2 (Area: 6000)

我对 linq 有点熟悉,所以过滤城市名称可能如下所示:

var result =
    from country in countries
    from state in country.States
    from city in state.Cities
    where city.Name.Contains("Dal", StringComparison.OrdinalIgnoreCase)
    select city; 

但在那种情况下,我只会得到城市和商店。如何在结果的顶部获取层次结构(国家和州)?

第二次搜索也是如此:

 var result =
     from country in countries
     from state in country.States
     from city in state.Cities
     from shop in city.Shops
     where shop.Name.Contains(nameFilter, StringComparison.OrdinalIgnoreCase)
     select shop;

在这里,我只会得到没有上面层次结构的商店......

uj5u.com热心网友回复:

由于您需要选择整个层次结构,因此您需要按最顶层节点(即国家/地区)对结果进行分组,然后从那里重建

var result =
    from country in countries
    from state in country.States
    from city in state.Cities
    from shop in city.Shops
    where shop.Name.Contains(nameFilter, StringComparison.OrdinalIgnoreCase)
    select new { country, state, city, shop } into p
    group p by p.country into g
    let shops = g.Select(x => x.shop)
    let cities = g.Select(x => x.city).Distinct()
    let states = g.Select(x => x.state).Distinct()
    select new Country()
    {
        Name = g.Key.Name,
        States = states.Select(s => new State()
        {
            Name = s.Name,
            Cities = s.Cities.Intersect(cities).Select(c => new City()
            {
                Name = c.Name,
                Shops = c.Shops.Intersect(shops).ToList()
            }).ToList()
        }).ToList()
    };

uj5u.com热心网友回复:

在您显示的查询中,您拥有某种意义上的相关路径

var result =
    from country in countries
    from state in country.States
    from city in state.Cities
    where city.Name.Contains("Dal", StringComparison.OrdinalIgnoreCase)
    select (country, state, city);

这将选择指定到给定城市的路径的三元组。现在你想把这样的三元组变成你的类结构,所以,我们就是这样做的。定义一个Path类:

class FilteredPath
{
    public Country? Country { get; init; }

    public State? State { get; init; }

    public City? City { get; init; }

    public Shop? Shop { get; init; }
}

然后我们写了很多代码:

class FilteredBuilder
{
    private List<Country> _countries = new();

    public FilteredBuilder AddPath(FilteredPath path)
    {
        if (path.Country is null)
        {
            return this;
        }

        if (path.State is null)
        {
            AddFullCountry(path.Country);
        }
        else
        {
            Country country = GetOrCreateBy(_countries, c => c.Name == path.Country.Name);
            country.Name = path.Country.Name;

            AddPathTo(country, path);
        }

        return this;
    }

    private void AddFullCountry(Country country)
    {
        _countries.RemoveAll(c => c.Name == country.Name);
        _countries.Add(country);
    }
    private void AddFullState(Country country, State state)
    {
        country.States.RemoveAll(s => s.Name == state.Name);
        country.States.Add(state);
    }

    private void AddFullCity(State state, City city)
    {
        state.Cities.RemoveAll(c => c.Name == city.Name);
        state.Cities.Add(city);
    }

    private void AddFullShop(City city, Shop shop)
    {
        city.Shops.RemoveAll(s => s.Name == shop.Name && s.Area == shop.Area);
        city.Shops.Add(shop);
    }

    private void AddPathTo(Country country, FilteredPath path)
    {
        Debug.Assert(path.State is not null);
        if (path.City is null)
        {
            AddFullState(country, path.State);
        }
        else
        {
            State state = GetOrCreateBy(country.States, s => s.Name == path.State.Name);
            state.Name = path.State.Name;

            AddPathTo(state, path);
        }
    }

    private void AddPathTo(State state, FilteredPath path)
    {
        Debug.Assert(path.City is not null);
        if (path.Shop is null)
        {
            AddFullCity(state, path.City);
        }
        else
        {
            City city = GetOrCreateBy(state.Cities, s => s.Name == path.City.Name);
            city.Name = path.City.Name;

            AddPathTo(city, path);
        }
    }

    private void AddPathTo(City city, FilteredPath path)
    {
        Debug.Assert(path.Shop is not null);
        AddFullShop(city, path.Shop);
    }

    private static T GetOrCreateBy<T>(ICollection<T> source, Func<T, bool> predicate) where T : new()
    {
        T? result = source.FirstOrDefault(predicate);

        if (result is null)
        {
            result = new T();
            source.Add(result);
        }

        return result;
    }
        
    public List<Country> Build()
    {
        var result = _countries;
        _countries = new();
        return result;
    }
}

像这样点燃它:

var result =
    from country in countries
    from state in country.States
    from city in state.Cities
    where city.Name.Contains("Dal", StringComparison.OrdinalIgnoreCase)
    select (country, state, city);

var paths = result.Select(r => new FilteredPath
{
    Country = r.country,
    State = r.state,
    City = r.city
});

var builder = new FilteredBuilder();

foreach (var path in paths)
{
    builder.AddPath(path);
}

var filtered = builder.Build();

如果您希望在生产中使用此功能,需要考虑以下几点:

  1. 查找Lists 是低效的。您可能希望每个物体都有一个帮助型别,该型别将Dictionary通过名称查找其子项,然后在最后转换为您的原始模型。
  2. 肯定有一种方法可以使它更通用并避免代码重复,但是,参考一句经典的话,“我没有时间把它缩短”。

但是,它确实适用于不同型别的查询。你在过滤状态?只需传递nullCityShopFilteredPath

作业演示:https : //dotnetfiddle.net/IHIOZk

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *