我正在撰写一个包含这样一个嵌套结构的应用程序:
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();
如果您希望在生产中使用此功能,需要考虑以下几点:
- 查找
List
s 是低效的。您可能希望每个物体都有一个帮助型别,该型别将Dictionary
通过名称查找其子项,然后在最后转换为您的原始模型。 - 肯定有一种方法可以使它更通用并避免代码重复,但是,参考一句经典的话,“我没有时间把它缩短”。
但是,它确实适用于不同型别的查询。你在过滤状态?只需传递null
给City
和Shop
的FilteredPath
。
作业演示:https : //dotnetfiddle.net/IHIOZk
0 评论