我正在撰寫一個包含這樣一個嵌套結構的應用程式:
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();
如果您希望在生產中使用此功能,需要考慮以下幾點:
- 查找
Lists 是低效的。您可能希望每個物體都有一個幫助型別,該型別將Dictionary通過名稱查找其子項,然后在最后轉換為您的原始模型。 - 肯定有一種方法可以使它更通用并避免代碼重復,但是,參考一句經典的話,“我沒有時間把它縮短”。
但是,它確實適用于不同型別的查詢。你在過濾狀態?只需傳遞null給City和Shop的FilteredPath。
作業演示:https : //dotnetfiddle.net/IHIOZk
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/405921.html
標籤:
上一篇:用C#和LinQ匹配兩個資料集
