数据结构如下:
用户(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解决方案(用户拥有任何一个所请求的应用程序或技能)。
答案 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;
}