LINQ根据孙子标准

时间:2016-01-15 05:17:34

标签: c# linq

我无法弄清楚如何完成这个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; }
}

1 个答案:

答案 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,但后来我再也无法读取它了。

任何更好的答案都会获得投票和赞赏。