LINQ 根据孙子条件返回具有子项子集的父项
本文关键字:子集 返回 条件 LINQ | 更新日期: 2023-09-27 18:30:25
我无法弄清楚如何完成此 LINQ 查询。 我有一个数据库结构如下:
User -> UserAccess <- Terminal <- Site
终端属于站点,UserAccess 指定哪些用户有权访问哪些终端。 对于给定的用户,我想按站点检索所有可用的终端。 鉴于Site
已经维护了一个Terminals
集合,这将是一个Site[]
,但只填充了Terminals
的一个子集。
从(我认为)错误的一端开始,我能够检索Sites
列表,并过滤子Terminals
:
using (MsSqlDataContextDataContext db = new MsSqlDataContextDataContext())
{
// This is part of a WCF Data Service. If I don't LoadWith I have lazy-loading problems
DataLoadOptions options = new DataLoadOptions();
options.LoadWith<site>(s => s.Terminals);
// I found AssociateWith can filter on a child collection, but I can't work out
// how to do the same for grand-child.
options.AssociateWith<site>(s => s.Terminals.Where(t => t.Active));
db.LoadOptions = options;
return db.sites.Where(s => s.Active).ToArray();
}
但是我无法按其子集合UserAccess
过滤Terminals
。
决定再次尝试从中间查询,我想我越来越接近了。 我现在可以检索Terminals
列表,但无法检索Sites
列表:
return db.UserAccess.Where(a => a.Username.Equals(username) && a.HasAccess ))
.Select(a => a.Terminal).ToArray();
我尝试添加.GroupBy(t => t.Site)
但是当我真的希望用这些结果填充我的现有类型时,这会创建一个匿名类型。 当我试图返回IGrouping<Site, Terminal>
时,WCF也有联系。
我有一种感觉,如果我设法自己制定出一个解决方案,它将是 11 个链式命令,而有一种方法可以在 2 中做到这一点,它会让人们看到它畏缩。
Linq 查询的答案是根据孙子的属性返回祖父、父和孙子,为每个结果生成一个匿名类型,您将获得数百个终端,每个终端都有单独的站点,没有分组。
Linq 查询返回过滤数据的问题就是我要做的,但(像他一样)我想避免显式迭代数据。
编辑:尝试提供最小、完整和可验证的示例。 不幸的是,这非常可怕。 我正在尝试复制我现有的类结构,即 LINQ to SQL 模型。 它生成循环引用(Terminal
有一个用于Site
的外键,但Site
有一个Terminals
的集合)。
public void LinqQuery()
{
Site s1 = new Site { SiteName = "Site 1", Terminals = new List<Terminal>() };
Site s2 = new Site { SiteName = "Site 2", Terminals = new List<Terminal>() };
Terminal t1 = new Terminal { TerminalName = "Terminal 1:1", site = s1, AccessList = new List<UserAccess>() };
Terminal t2 = new Terminal { TerminalName = "Terminal 2:1", site = s1, AccessList = new List<UserAccess>() };
s1.Terminals.Add(t1);
s1.Terminals.Add(t2);
Terminal t3 = new Terminal { TerminalName = "Terminal 3:2", site = s2, AccessList = new List<UserAccess>() };
Terminal t4 = new Terminal { TerminalName = "Terminal 4:2", site = s2, AccessList = new List<UserAccess>() };
s2.Terminals.Add(t3);
s2.Terminals.Add(t4);
User u1 = new User { Name = "Ian" };
UserAccess ua1 = new UserAccess { user = u1, terminal = t1 };
t1.AccessList.Add(ua1);
UserAccess ua2 = new UserAccess { user = u1, terminal = t2 };
t2.AccessList.Add(ua2);
UserAccess ua3 = new UserAccess { user = u1, terminal = t4 };
t4.AccessList.Add(ua3);
Site[] allSites = {s1, s2};
Terminal[] allTerminals = {t1, t2, t3, t4};
UserAccess[] allUserAccess = {ua1, ua2, ua3};
// This gives me a list of all terminals where user "Ian" has access (3 Terminals)
var v = allUserAccess.Where(ua => ua.user.Name.Equals("Ian")).Select(ua => ua.terminal).ToArray();
// If I take it one step further and Select the site, I end up with an array of 3 sites (only 2 exist)
var v2 = v.Select(t => t.site).ToArray();
// What I want is the allSites array but with the terminals filtered.
}
internal class Site
{
public string SiteName { get; set; }
public List<Terminal> Terminals { get; set; }
}
internal class Terminal
{
public string TerminalName { get; set; }
public Site site { get; set; }
public List<UserAccess> AccessList { get; set; }
}
internal class UserAccess
{
public User user { get; set; }
public Terminal terminal { get; set; }
}
internal class User
{
public string Name { get; set; }
}
就像我在链接问题中@DeclanMcD的姐夫一样,我不得不求助于显式过滤数据。 我觉得这个解决方案可能更干净一些,但我仍然不喜欢它:
// What I want is the allSites array but with the terminals filtered.
List<Site> toReturn = new List<Site>(allSites);
foreach (Site s in toReturn)
{
// Look through every Terminal
// And check if they have any UserAccess that would allow access.
// If no access (!t.AccessList.Any), remove this Terminal.
s.Terminals.RemoveAll(t => !t.AccessList.Any(
a => a.user.Name.Equals("Ian")));
}
// Also if I want empty Sites removed:
toReturn.RemoveAll(s => !s.Terminals.Any());
外部foreach
可能是 LINQ 化的,但这样我就无法再阅读它了。
任何更好的答案都会得到投票和赞赏。