查询在两个表中包含所有请求记录的用户

时间:2014-10-15 08:53:00

标签: c# entity-framework linq-to-sql

数据结构如下:

用户(ID)

UserApp(user_id,app_id)

UserSkill(user_id,skill_id)

使用linq-to-sql或EF,如何构建查询以优雅地仅返回拥有所有请求的应用和技能的用户?

此外,我如何调整查询以返回拥有至少一个所请求的应用或技能的用户?基本上是OR与AND(上图)。

更新1:

所以我认为我们已经关闭了。基本上我想只返回拥有所有请求的应用和技能的用户。如果我们有两个技能和应用程序的请求ID数组:

int[] requestedAppIDs // [1, 2, 3]
int[] requestedSkillIDs // [4, 5, 6]

如果用户拥有应用1,2,3和技能4,5,6。

,我只想返回用户
var usersWithAllSelectedAppsAndSkills = 
    context.Users
    .GroupJoin(context.UserApp,
        k => k.id,
        k => k.user_id,
        (o, i) => new { User = o, UserApps = i })
    .GroupJoin(context.UserSkill,
        k => k.User.id,
        k => k.user_id,
        (o, i) => new { User = o.User, o.UserApps, UserSkills = i })
    .Where(w => !requestedAppIDs.Except(w.UserApps.Select(x => x.app_id).ToArray()).Any() && !requestedSkillIDs.Except(w.UserSkills.Select(x => x.skill_id).ToArray()).Any())
    .Select(s => s.User)
    .ToList();

显然,LINQ不知道如何在我的Where()到SQL中翻译UserSkills.Select()。ToArray()。我怎么能做到这一点?

其次,OR解决方案(用户拥有任何一个所请求的应用程序或技能)。

5 个答案:

答案 0 :(得分:3)

只要UserApp和UserSkill表中的user_id - app_id和user_id - skill_id值是唯一的,就可以完成此任务。

var requestedSkillIDs = new[] { 4, 5, 6 };
var skillCount = requestedSkillIDs.Length;
var requestedAppIDs = new[] { 1, 2, 3 };
var appCount = requestedAppIDs.Length;

using (var context = new TestContext()) {
    context.Database.CreateIfNotExists();

    var appQuery = context.UserApp.Where(p => requestedAppIDs.Contains(p.AppId))
                        .GroupBy(p => p.UserId)
                        .Where(p => p.Count() == appCount);

    var skillQuery = context.UserSkill.Where(p => requestedSkillIDs.Contains(p.SkillId))
                        .GroupBy(p => p.UserId)
                        .Where(p => p.Count() == skillCount);

    var result = from a in appQuery
                    join s in skillQuery on a.Key equals s.Key
                    join u in context.Users on s.Key equals u.Id
                    select u;


    var users = result.ToList();
}

答案 1 :(得分:2)

这是一种方法,我希望我的语法正确:)

   using (var context = new YourContext())
           {
              var usersWithAllSkills = context.User
                                 .Where(w => w.id == yourId)
                                 .Join(context.UserApp,
                                        k => k.id,
                                        k => k.user_id,
                                        (o,i) => o)
                                 .Join(context.UserSkill,
                                        k => k.id,
                                        k => k.user_id,
                                        (o,i) => o)
                                 .ToList();

         var usersWithAnySkill = context.User
                            .Where(w => w.id == yourId)
                            .GroupJoin(context.UserSkill,
                                        k => k.id,
                                        k => k.user_id,
                                        (o,i) => new { User = o, UserSkills = i })
                            .GroupJoin(context.UserApp,
                                        k => k.User.id,
                                        k => k.user_id,
                                        (o,i) => new { User = o.User, o.UserSkills ,UserApps = i  })
                            .Where(w => w.UserSkills != null || w.UserApps != null)
                            .Select(s => s.User)
                            .ToList();

       }

答案 2 :(得分:2)

对于第一种情况(AND)你只需要进行如下所示的内连接:

from t1 in db.UserApp
join t2 in db.UserSkill on t1.user_id equals t2.user_id
where t1.app_id == "someID" && t2.skill_id == "someID"
select new { t1.user_id,t1.user_app_id, t2.user_skill}

对于第二种情况,只需用||(OR)交换&&(AND)。

答案 3 :(得分:2)

使用L2E编写所需查询有一种更直接的方法。要编写这些查询,您必须忘记在SQL中思考并开始在LINQ中思考。

对于第一种情况,请寻找具备所有技能的用户:

var usersWithAll = ctx.Users2.Where(u =>
    appIds.All(aid => u.Apps.Any(a => a.AppId == aid))
    && skillIds.All(sid => u.Skills.Any(s => s.SkillId == sid))
    );

翻译为:获取用户所在的应用程序,用户已使用该应用程序ID获取应用程序,并且对于所有skillIds,用户至少拥有一个具有该ID的技能

并且,对于第二种情况,具有任何应用程序和任何技能的用户:

var usersWithAny = ctx.Users2.Where(u =>
    appIds.Any(aid => u.Apps.Any(a => a.AppId == aid))
    && skillIds.Any(sid => u.Skills.Any(s => s.SkillId == sid))
    ).ToList();

翻译为:获取用户所在的位置,对于至少一个应用程序,用户拥有具有该应用程序ID的应用程序,对于任何skillIds,用户至少拥有一个具有该ID的技能

如果您运行此测试类,您还会看到已执行的查询(请注意,为此,我使用Log的{​​{1}}属性。我认为它只是可从EF6获得。

Database

答案 4 :(得分:1)

我相信answer by codeworx对于吸引所有技能/应用的用户来说是正确的

顺便说一句 - 我最近用纯SQL解决方案(SQL Server)回答了几乎相同的问题 - 可以将其转换为存储过程(带有表值参数) - 如果感兴趣,请参阅here。对于大量值,这将表现得更好。实体框架会将列表中的每个技能/应用程序转换为自己的SQL参数,这要慢得多。

不幸的是,Entity框架实际上并不支持表值参数 - 尽管您可以使用实体框架连接直接使用表值参数调用存储过程(根据this article

回到手头的问题...... 我将添加(更简单的)查询以选择具有任何技能和用户的用户。任何应用程序:

var result = from u in context.Users
             join _a in (
                from a in context.UserApp
                where requestedAppIDs.Contains(a.AppId)
                select a.UserId;
             ) on u.Id equals _a
             into aGrp
             join _s in (
                from s in context.UserSkill
                where requestedSkillIDs.Contains(s.SkillId)
                select s.UserId;
             ) on u.Id equals _s
             into sGrp
             where aGrp.Any()
                && sGrp.Any()
             select u;

仅仅为了完整性 - 再次提出所有解决方案:

var skillCount = requestedSkillIDs.Length;
var appCount = requestedAppIDs.Length;
var result = from u in context.Users
             join _a in (
                from a in context.UserApp
                where requestedAppIDs.Contains(a.AppId)
                select a.UserId;
             ) on u.Id equals _a
             into aGrp
             join _s in (
                from s in context.UserSkill
                where requestedSkillIDs.Contains(s.SkillId)
                select s.UserId;
             ) on u.Id equals _s
             into sGrp
             where aGrp.Count() == appCount
                && sGrp.Count() == skillCount
             select u;

最后 - 修复主查询体的示例,但您可以根据AND / OR要求添加不同的where子句

bool onlyReturnWhereAllAreMatched = false;
var skillCount = requestedSkillIDs.Length;
var appCount = requestedAppIDs.Length;
IQueryable<User> result;
var query = from u in context.Users
             join _a in (
                from a in context.UserApp
                where requestedAppIDs.Contains(a.AppId)
                select a.UserId;
             ) on u.Id equals _a
             into aGrp
             join _s in (
                from s in context.UserSkill
                where requestedSkillIDs.Contains(s.SkillId)
                select s.UserId;
             ) on u.Id equals _s
             into sGrp
             select new {u, aCount = aGrp.Count(), sCount = sGrp.Count()};
if (onlyReturnWhereAllAreMatched)
{
   result = from x in query 
            where  x.aCount == appCount
                && x.sCount == skillCount
            select x.u;
} else {
   result = from x in query 
            where  x.aCount > 0
                && x.sCount > 0
            select x.u;
}