我有一些如下的业务对象:
class Project
{
public int ID
{
get;set;
}
public string ProjectName
{
get;set;
}
public IList<ProjectTag> ProjectTags
{
get;set;
}
}
class ProjectTag
{
public int ID
{
get;set;
}
public int ProjectID
{
get;set;
}
public string Name
{
get;set;
}
public string Value
{
get;set;
}
}
示例数据:
Project:
ID ProjectName
1 MyProject
ProjectTags:
ID ProjectID Name Value
1 1 Name 1 Value 1
2 1 Name 2 Value 2
3 1 Name 3 Value 3
基本上,这是我们的用户在Project上定义自己的列的一种方式。因此,重要的是要记住,在设计时我不知道ProjectTag条目的名称。
我要完成的工作是使我们的用户能够使用System.Linq.Dynamic根据搜索条件选择项目。例如,要在上面的示例中仅选择项目,我们的用户可以输入以下内容:
ProjectName == "MyProject"
更复杂的方面是将过滤器应用于ProjectTag。我们的应用程序当前允许用户执行此操作,以便通过其ProjectTag过滤项目:
ProjectTags.Any(Name == "Name 1" and Value == "Value 1")
可以,但是开始变得有些混乱,供最终用户使用。理想情况下,我想写一些能让他们做以下事情的东西:
Name 1 == "Value 1"
或者,如果有必要(由于名称中有空格),则类似于以下内容...
[Name 1] == "Value 1"
"Name 1" == "Value 1"
由于缺乏更好的解释,似乎我想在ProjectTags上执行等效的SQL透视,然后仍然能够执行针对该语句的where子句。我已经看过StackOverflow上有关枢轴和动态枢轴的一些问题,但是我发现没有什么太有用的了。
我还一直在考虑遍历所有ProjectTag名称,并使用每个名称的左联接来构建动态查询。我猜是这样的:
select
Project.*,
Name1Table.Value [Name 1],
Name2Table.Value [Name 2],
Name3Table.Value [Name 3]
from
Project
left join ProjectTag Name1Table on Name = 'Name 1'
left join ProjectTag Name2Table on Name = 'Name 2'
left join ProjectTag Name3Table on Name = 'Name 3'
然后执行该查询并对其应用一个where子句。但是我不太确定如何在Linq中执行此操作以及如何处理名称中的空白。
我还遇到了ExpandoObject。我以为可以将Project转换为ExpandoObject。然后遍历所有已知的ProjectTag名称,将每个名称添加到ExpandoObject,如果该Project具有该名称的ProjectTag,则使用该ProjectTag值作为值,否则使用空字符串。例如...
private static object Expand(
Project project,
List<string> projectTagNames)
{
var expando = new ExpandoObject();
var dictionary = (IDictionary<string, object>) expando;
foreach (var property in project.GetType()
.GetProperties())
{
dictionary.Add(property.Name, property.GetValue(project));
}
foreach (var tagName in projectTagNames)
{
var tagValue = project.ProjectTags.SingleOrDefault(p => p.Name.Equals(tagName));
dictionary.Add(tagName, tagValue?.Value ?? "");
}
return expando;
}
关于此解决方案的令人兴奋的事情是,我有一个对象看起来与我认为应该在使用where子句进行过滤之前完全一样。甚至似乎在属性名称中包含空格。
然后,我当然发现动态linq在ExpandoObject上无法很好地工作,因此找不到动态属性。我猜这是因为它本质上是一种Object类型,不会定义任何动态属性。也许可以在运行时生成匹配的类型?即使可行,我也不认为它可以解决名称中的空格。
我是否想通过此功能完成太多工作?我应该告诉用户使用诸如ProjectTags.Any(Name ==“ Name1”和Value ==“ Value1”)之类的语法吗?还是有某种方法可以诱使动态linq理解ExpandoObject?似乎有一种方法可以覆盖动态linq解析属性名称的方式,将非常方便。
答案 0 :(得分:0)
如何使用翻译器转换标签引用?
我假设包含空格的标签名称将用方括号([]
)包围,并且Project
字段名称是已知列表。
public static class TagTranslator {
public static string Replace(this string s, Regex re, string news) => re.Replace(s, news);
public static string Surround(this string src, string beforeandafter) => $"{beforeandafter}{src}{beforeandafter}";
public static string SurroundIfMissing(this string src, string beforeandafter) => (src.StartsWith(beforeandafter) && src.EndsWith(beforeandafter)) ? src : src.Surround(beforeandafter);
public static string Translate(string q) {
var projectFields = new[] { "ID", "ProjectName", "ProjectTags" }.ToHashSet();
var opREStr = @"(?<op>==|!=|<>|<=|>=|<|>)";
var revOps = new[] {
new { Fwd = "==", Rev = "==" },
new { Fwd = "!=", Rev = "!=" },
new { Fwd = "<>", Rev = "<>" },
new { Fwd = "<=", Rev = ">=" },
new { Fwd = ">=", Rev = "<=" },
new { Fwd = "<", Rev = ">" },
new { Fwd = ">", Rev = "<" }
}.ToDictionary(p => p.Fwd, p => p.Rev);
var openRE = new Regex(@"^\[", RegexOptions.Compiled);
var closeRE = new Regex(@"\]$", RegexOptions.Compiled);
var termREStr = @"""[^""]+""|(?:\w|\.)+|\[[^]]+\]";
var term1REStr = $"(?<term1>{termREStr})";
var term2REStr = $"(?<term2>{termREStr})";
var wsREStr = @"\s?";
var exprRE = new Regex($"{term1REStr}{wsREStr}{opREStr}{wsREStr}{term2REStr}", RegexOptions.Compiled);
var tq = exprRE.Replace(q, m => {
var term1 = m.Groups["term1"].Captures[0].Value.Replace(openRE, "").Replace(closeRE, "");
var term1q = term1.SurroundIfMissing("\"");
var term2 = m.Groups["term2"].Captures[0].Value.Replace(openRE, "").Replace(closeRE, "");
var term2q = term2.SurroundIfMissing("\"");
var op = m.Groups["op"].Captures[0].Value;
if (!projectFields.Contains(term1) && !term1.StartsWith("\"")) { // term1 is Name, term2 is Value
return $"ProjectTags.Any(Name == {term1q} && Value {op} {term2})";
}
else if (!projectFields.Contains(term2) && !term2.StartsWith("\"")) { // term2 is Name, term1 is Value
return $"ProjectTags.Any(Name == {term2q} && Value {revOps[op]} {term1})";
}
else
return m.Value;
});
return tq;
}
}
现在,您只需翻译查询:
var q = "ProjectName == \"Project1\" && [Name 1] == \"Value 1\" && [Name 3] == \"Value 3\"";
var tq = TagTranslator.Translate(q);