我无法弄清楚如何完成这个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();
}
但我无法通过子集合Terminals
过滤UserAccess
。
我决定再次尝试从中间查询,我想我已经接近了。我现在可以检索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 query to return grandparent, parent and grandchild based on a grandchild's property的答案会为每个结果生成一个匿名类型,您可以获得数百个终端,每个终端都有一个单独的网站,没有分组。
问题Linq query to return filtered data是我想要做的,但是(像他一样)我想避免明确地迭代数据。
编辑: 尝试提供最小,完整且可验证的示例。不幸的是,这太可怕了。我正在尝试复制我现有的类结构,它们是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; }
}
答案 0 :(得分:0)
就像linked question中的兄弟@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-ified,但后来我再也无法读取它了。
任何更好的答案都会获得投票和赞赏。