我正在开发一个ASP.NET Web Api服务,其中一个控制器操作接受一个带有0个或更多Key:Value对的JSON字符串来搜索一组对象。因此,我不知道在请求中过滤集合的字段名称。
现在,我的代码将通过根据提供的数据链接WHERE表达式来构建动态查询。在这种情况下的问题是,我不仅不知道需要过滤的字段名称,字段列表及其值存储在每个对象内的集合中 - 并且该集合中的对象只有两个属性:名称和价值。
数据正在从我无法控制的一堆XML文件中反序列化(因此无法更改格式),并且每个文件中包含的字段列表可能不同。 (见下面的课程定义)。
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public class Bug
{
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public int ID { get; set; }
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public DateTime ChangedDate { get; set; }
[System.Xml.Serialization.XmlArrayAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
[System.Xml.Serialization.XmlArrayItemAttribute("Field", typeof(BugField), Form = System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable = false)]
public List<BugField> Fields {get; set;}
}
如果我运行以下查询一切正常 - 我根据JSON请求得到我正在寻找的结果 - 但是该请求只搜索一个字段和一个值,并且查询具有正确字段的索引hardcoded;)(FYI - itemList是先前创建的没有过滤的集合)
itemList = (List<Bug>)itemList.Where(x => x.Fields[15].Value.ToString() == field.Value.ToString()).ToList();
我确实有代码来根据JSON请求提供的搜索字段创建动态LINQ查询(链接WHERE表达式)(我在这里不包括代码,因为它太长了 - 并且不确定它是完全相关的... YET)。但是,解析表达式的方式需要能够引用要搜索的属性的名称 - 这当然是未知的,因为它是Name属性的值。
那么 - 如何修改查询以考虑确定查询参数的字段名称事先是未知的?
编辑:以下代码块显示了我想要使用的内容(或者更适用于我的动态查询构建器)。如果类中的字段名定义与JSON字符串中提供的字段名相同,则第一行是效果很好的代码。第二个是我尝试访问内部集合字段名称属性时尝试的其中一个。
foreach (KeyValuePair<string, object> field in queryFields)
{
itemList = itemList.Where<Bug>(field.Key, field.Value, (FilterOperation)StringEnum.Parse(typeof(FilterOperation), "eq"));
itemList = itemList.Where<Bug>(x => x.Fields.Any(y => y.Name == field.Key && y.Value == field.Value));
}
答案 0 :(得分:0)
我看到了几种方法来解决这个问题:
1)列出所有可用于过滤的属性,匹配属性并将它们动态添加到您的linq查询中。
可行的方法是,您将接受您的json对象并迭代通过其值传递的属性。所以,假设你进入你的控制器:
{
Prop[0].Name = "Name"
Prop[0].Value = "Justin"
Prop[1].Name = "Email"
Prop[1].Value = "j@g.com"
}
例如,在您的控制器中,您将迭代每个键值并动态链接它。一些伪代码:
foreach(var keyValue in Filters){
var prop = keyValue.Name;
var val = keyValue.Value;
myLinqQuery.AddWhere(c => c.GetType().GetProperty().Name == prop && c.GetValue() == Value);
}
这个apprach会运行良好,但一个很大的缺点是它会尝试使用反射过滤请求中的每个属性。在这种情况下你会失去一些控制。
2)仅允许多个过滤器并为每个过滤器设置过滤条件。 在该选项中,您将只允许一个过滤器列表,并为每个过滤器提供某种操作。 这是一个简单的例子:假设您的控制器接收:
public ActionResult Filter(string name, string email){
//name and email are the filter values
var query = ( from c in (List<Bug>)itemList select c);
if(!string.IsNullOrEmpty(name))
query.AddWhere(c => c.Name == name);
if(!string.IsNullOrEmpty(email))
query.AddWhere(c => c.Email == email);
}
您可以通过为每个允许的过滤器创建一个单独的类并公开其过滤器操作来进一步设计更多OOP和SOLID。
让我知道它是否有帮助!
答案 1 :(得分:0)
在花了将近两整天的时间试图让这个解决方案无法在其他地方进行大的改动之后,我终于决定对模型进行一些细微的设计更改,以使这更容易。
我如何解决这个问题是直接将每个字段添加到模型中,而不是使用自定义对象列表。因此,我删除了XML反序列化,而是使用XDocument循环遍历Node中的所有元素 - 然后比较“Name”属性的值以确定它是哪个字段,并指定“Value”属性的值到模型中的相应属性。
一些额外的代码,以及一个很好的小开关语句,有22个案例来处理分配......
不是我想要的,但至少我仍然可以按时提供服务,然后试着看看以后是否有更好的方法来做这件事......
为了完整起见,我改变了这一点:
public class Bug
{
public int ID { get; set; }
public DateTime ChangedDate { get; set; }
public string State { get; set; }
public string AreaPath { get; set; }
public string Title { get; set; }
public string Status { get; set; }
public string SubStatus { get; set; }
public string OpenedBy { get; set; }
public string ChangedBy { get; set; }
public DateTime OpenedDate { get; set; }
public DateTime ResolvedDate { get; set; }
public string AssignedTo { get; set; }
public string IssueType { get; set; }
public string IssueSubtype { get; set; }
public string Priority { get; set; }
public string Severity { get; set; }
public string Keywords { get; set; }
public string ScreenID { get; set; }
public string ResolvedBy { get; set; }
public string Resolution { get; set; }
public string ReproSteps { get; set; }
public string HowFound { get; set; }
public string FullHistory { get; set; }
public string EverChangedBy { get; set; }
}
以及以前的反序列化方法:
internal static Bug Deserialize(string filePath)
{
XDocument doc = XDocument.Load(filePath);
Bug bug = new Bug();
bug.ID = int.Parse(doc.Root.Element("ID").Value);
bug.ChangedDate = DateTime.Parse(doc.Root.Element("ChangedDate").Value);
foreach (var el in doc.Root.Element("Fields").Elements())
{
XAttribute fieldName = el.Attributes("Name").Single();
XAttribute fieldValue = el.Attributes("Value").Single();
switch (fieldName.Value.ToString())
{
case "State":
bug.State = fieldValue.Value.ToString();
break;
case "Area Path":
bug.AreaPath = fieldValue.Value.ToString();
break;
case "Title":
bug.Title = fieldValue.Value.ToString();
break;
case "Status":
bug.Status = fieldValue.Value.ToString();
break;
case "SubStatus":
bug.SubStatus = fieldValue.Value.ToString();
break;
case "Opened By":
bug.OpenedBy = fieldValue.Value.ToString();
break;
case "ChangedBy":
bug.ChangedBy = fieldValue.Value.ToString();
break;
case "Opened Date":
bug.OpenedDate = DateTime.Parse(fieldValue.Value.ToString());
break;
case "Resolved Date":
bug.ResolvedDate = DateTime.Parse(fieldValue.Value.ToString());
break;
case "Assigned To":
bug.AssignedTo = fieldValue.Value.ToString();
break;
case "Issue Type":
bug.IssueType = fieldValue.Value.ToString();
break;
case "Issue Subtype":
bug.IssueSubtype = fieldValue.Value.ToString();
break;
case "Priority":
bug.Priority = fieldValue.Value.ToString();
break;
case "Severity":
bug.Severity = fieldValue.Value.ToString();
break;
case "Keywords":
bug.Keywords = fieldValue.Value.ToString();
break;
case "ScreenID":
bug.ScreenID = fieldValue.Value.ToString();
break;
case "ResolvedBy":
bug.ResolvedBy = fieldValue.Value.ToString();
break;
case "Resolution":
bug.Resolution = fieldValue.Value.ToString();
break;
case "ReproSteps":
bug.State = fieldValue.Value.ToString();
break;
case "HowFound":
bug.State = fieldValue.Value.ToString();
break;
case "FullHistory":
bug.State = fieldValue.Value.ToString();
break;
case "EverChangedBy":
bug.State = fieldValue.Value.ToString();
break;
}
}
return bug;
}
这允许我使用以下调用来构建LINQ查询:
foreach (KeyValuePair<string, object> field in queryFields)
{
itemList = itemList.Where<Bug>(field.Key, field.Value, (FilterOperation)StringEnum.Parse(typeof(FilterOperation), "eq"));
}
干杯!
答案 2 :(得分:0)
我知道这是旧的,但这是我如何做到这一点,而不必输入每个可能的名称:
1)匿名IEnumerable进入,根据需要转换为IEnumerable或IList
2)在第一个元素上,
var property = element.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
3)属性中的Foreach对象,向数据表中添加列:
table.Columns.Add(property.Name, typeof(property.GetValue(element, null));
4)从头开始迭代你的收藏:foreach:
var prop = item.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
dynamic i = new ExpandoObject();
IDictionary<string, object> d = (IDictionary<string, object>)i;
foreach (var p in prop)
{
d.Add(p.Name, p.GetValue(item));
}
list.Rows.Add(((IDictionary<string, object>)d).Values.ToArray());
这将获取匿名类型,将它们提取到ExpandoObject(可以与IDictionary完全互换),然后通过将IDictionary动态对象作为数组转换来添加行。
可以使用任何类型进行缩放,并允许您在构造数据时对其进行操作。