如何用LINQ和C#驱动调用存储JS函数为查询对象添加分数字段
好的,我来帮你解决这个问题。首先得明确:LINQ to MongoDB(也就是你用的AsQueryable()方式)并不支持直接在投影阶段调用数据库端的存储JavaScript函数——因为LINQ需要把表达式树转换成MongoDB原生查询语言,自定义存储函数不在它的转换范围内。所以我们得换用MongoDB的Aggregation Pipeline来实现需求,这也是最适合的方案。
下面分两种可行方案给你详细说明:
方案一:调用数据库存储的JavaScript函数(匹配你的需求)
首先要确保你的candidateScore函数已经正确注册到MongoDB中,你可以在Mongo Shell里执行以下命令完成注册:
db.system.js.save({ _id: "candidateScore", value: function(professionalProfileRequirements, professionalProfile) { var score = 0; if (professionalProfileRequirements.Province === professionalProfile.Province) { score += 1000; } if (professionalProfileRequirements.Role && professionalProfile.Roles && professionalProfile.ExperienceLevel){ if (professionalProfile.Roles.includes(professionalProfileRequirements.Role)) { switch (professionalProfile.ExperienceLevel) { case 'ExperienceLevel_NoExperience': score += 1; break; case 'ExperienceLevel_LessThanOneYear': score += 2; break; case 'ExperienceLevel_BetweenOneAndThreeYears': score += 4; break; case 'ExperienceLevel_BetweenThreeAndFiveYears': score += 6; break; case 'ExperienceLevel_MoreThan5Years': score += 10; break; } } } return score; } })
接下来用C#的Aggregation Pipeline实现完整逻辑,对应你原来的LINQ查询步骤:
// 先准备好分数计算的参数 var scoreRequirements = new ProfessionalProfileRequirements { Role = "你的目标角色", Province = "目标省份", LanguagesRequired = new List<string>{"中文"}, SkillsRequired = new List<string>{"C#"} }; // 构建聚合查询 var professionalProfiles = await _mongoContext.Database .GetCollection<Profile>("profiles") .Aggregate() // 1. 左连接Users集合(对应你原来的join操作) .Lookup( foreignCollectionName: "users", localField: nameof(Profile.UserId), foreignField: "_id", // 注意:这里要匹配User集合的Id存储字段,若你的User.Id映射为"Id.Value"则替换成该值 @as: "matchedUsers" ) // 2. 提取第一个匹配用户的IsConfirmed字段(对应users.First().IsConfirmed) .AddFields(new BsonDocument("IsConfirmed", new BsonDocument("$arrayElemAt", new BsonArray{"$matchedUsers.IsConfirmed", 0}))) // 3. 过滤已确认的用户 .Match(p => p.IsConfirmed) // 4. 调用存储的JS函数计算Score .AddFields(new BsonDocument("Score", new BsonDocument( "$function", new BsonDocument { {"body", "candidateScore"}, // 存储函数的名称 {"args", new BsonArray {BsonSerializer.Serialize(scoreRequirements), "$$ROOT"}}, // 传入需求参数和当前文档 {"lang", "js"} } ))) // 5. 投影成你需要的ProfessionalProfile结构 .Project<ProfessionalProfile>(new BsonDocument { {nameof(ProfessionalProfile.Id), "$_id"}, {nameof(ProfessionalProfile.Name), "$Name"}, {nameof(ProfessionalProfile.UserId), "$UserId"}, {nameof(ProfessionalProfile.IsConfirmed), "$IsConfirmed"}, {nameof(ProfessionalProfile.Score), "$Score"} }) // 6. 按名称排序(对应你原来的orderby) .Sort(Builders<ProfessionalProfile>.Sort.Ascending(p => p.Name)) .ToListAsync();
注意:$function是MongoDB 4.4及以上版本支持的特性,如果你的MongoDB版本较低,建议升级;若无法升级,可以用已废弃的$eval替代,但不推荐长期使用。
方案二:用MongoDB原生操作符实现分数计算(性能更优)
如果不想依赖存储JS函数,推荐把分数计算逻辑转换成MongoDB原生聚合操作符——因为JS函数是单线程执行的,大数据量下性能不如原生操作符。以下是等价的聚合实现:
var scoreRequirements = new ProfessionalProfileRequirements { Role = "你的目标角色", Province = "目标省份", // 其他参数... }; var professionalProfiles = await _mongoContext.Database .GetCollection<Profile>("profiles") .Aggregate() .Lookup("users", nameof(Profile.UserId), "_id", "matchedUsers") .AddFields(new BsonDocument("IsConfirmed", new BsonDocument("$arrayElemAt", new BsonArray{"$matchedUsers.IsConfirmed", 0}))) .Match(p => p.IsConfirmed) // 用原生操作符实现分数计算,替代JS函数 .AddFields(new BsonDocument("Score", new BsonDocument("$add", new BsonArray { // 省份匹配加1000分 new BsonDocument("$cond", new BsonArray { new BsonDocument("$eq", new BsonArray{"$Province", scoreRequirements.Province}), 1000, 0 }), // 角色匹配+经验等级加分 new BsonDocument("$cond", new BsonArray { new BsonDocument("$and", new BsonArray { scoreRequirements.Role != null, new BsonDocument("$ne", new BsonArray{"$Roles", BsonNull.Value}), new BsonDocument("$ne", new BsonArray{"$ExperienceLevel", BsonNull.Value}), new BsonDocument("$in", new BsonArray{scoreRequirements.Role, "$Roles"}) }), new BsonDocument("$switch", new BsonDocument { {"branches", new BsonArray { new BsonDocument{"case", new BsonDocument("$eq", new BsonArray{"$ExperienceLevel", "ExperienceLevel_NoExperience"}), "then", 1}, new BsonDocument{"case", new BsonDocument("$eq", new BsonArray{"$ExperienceLevel", "ExperienceLevel_LessThanOneYear"}), "then", 2}, new BsonDocument{"case", new BsonDocument("$eq", new BsonArray{"$ExperienceLevel", "ExperienceLevel_BetweenOneAndThreeYears"}), "then", 4}, new BsonDocument{"case", new BsonDocument("$eq", new BsonArray{"$ExperienceLevel", "ExperienceLevel_BetweenThreeAndFiveYears"}), "then", 6}, new BsonDocument{"case", new BsonDocument("$eq", new BsonArray{"$ExperienceLevel", "ExperienceLevel_MoreThan5Years"}), "then", 10} }}, {"default", 0} }), 0 }) }))) .Project<ProfessionalProfile>(new BsonDocument { {nameof(ProfessionalProfile.Id), "$_id"}, {nameof(ProfessionalProfile.Name), "$Name"}, {nameof(ProfessionalProfile.UserId), "$UserId"}, {nameof(ProfessionalProfile.IsConfirmed), "$IsConfirmed"}, {nameof(ProfessionalProfile.Score), "$Score"} }) .Sort(Builders<ProfessionalProfile>.Sort.Ascending(p => p.Name)) .ToListAsync();
这个方案的优势是完全使用MongoDB原生操作,性能更好,也不需要维护数据库端的JS函数,适合长期使用。
内容的提问来源于stack exchange,提问作者alesvi
相关产品推荐
相关产品推荐

