4

我正在开发一个 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 请求得到我正在寻找的结果 - 但是该请求仅搜索一个字段和一个值,并且查询具有硬编码的正确字段的索引;) (仅供参考 - itemList 是之前创建的没有过滤的集合)

itemList = (List<Bug>)itemList.Where(x => x.Fields[15].Value.ToString() == field.Value.ToString()).ToList();

我确实有代码可以根据 JSON 请求提供的搜索字段创建动态 LINQ 查询(链接 WHERE 表达式)(我在这里不包括此代码,因为它太长了 - 并且不确定它是否完全相关...然而)。但是,解析表达式的方式需要能够引用要搜索的属性的名称——这当然是未知的,因为它是 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)); 
}
4

3 回答 3

1

我知道这是旧的,但我是这样做的,而无需输入所有可能的名称:

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 动态对象转换为数组来添加行。

可以使用任何类型进行缩放,并允许您在构建数据时对其进行操作。

于 2014-06-06T16:10:17.437 回答
0

在花了将近整整 2 天的时间试图让这个解决方案工作而不在其他地方做大的改变之后,我最终决定对模型进行轻微的设计改变,以使其更容易。

我如何解决这个问题是将每个字段直接添加到模型中,而不是使用自定义对象列表。因此,我删除了 XML 反序列化,而是使用 XDocument 循环遍历节点内的所有元素 - 然后比较“名称”属性的值以确定它是哪个字段,并分配“值”属性的值到模型中的相应属性。

一些额外的代码和一个不错的小 switch 语句,有 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"));                
        }

干杯!

于 2013-03-26T21:04:46.580 回答
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);
}

这种方法效果很好,但一个很大的缺点是它会尝试使用反射过滤请求中的每个属性。在这种情况下,你会失去一些控制。

2) 只允许多个过滤器,并为每个过滤器设置一个过滤条件。在该选项中,您将只允许一个过滤器列表,并对每个过滤器执行某种 where 操作。这是一个简单的示例:假设您的控制器接收:

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。

让我知道它是否有帮助!

于 2013-03-25T17:02:40.717 回答