【问题标题】:Matching objects by property name and value using Linq使用 Linq 按属性名称和值匹配对象
【发布时间】:2014-08-05 22:04:41
【问题描述】:

我需要能够通过使用单个 Linq 查询匹配属性名称和值来将对象与记录匹配。我不明白为什么这不可能,但我无法弄清楚如何使这项工作。现在我可以使用循环来完成,但这很慢。

剧情如下:

我设置了表来存储任何给定实体的记录,方法是将它们的主键放入具有键的属性名称和值的关联表中。

如果我在运行时有一个随机对象,我需要能够检查该对象的副本是否存在于数据库中,方法是检查对象是否具有与记录中记录的所有键匹配的属性名称数据库(这意味着它们将是相同类型的对象),然后检查每个键的值是否匹配,给我相同的记录。

这是我使用循环使其工作的方法(稍微简化了一点):

public IQueryable<ResultDataType> MatchingRecordFor(object entity)
{
    var result = Enumerable.Empty<ResultDataType>();
    var records = _context.DataBaseRecords

    var entityType = entity.GetType();
    var properties = entityType.GetProperties().Where(p => p.PropertyType.Namespace == "System");

    foreach (var property in properties)
    {
        var name = property.Name;
        var value = property.GetValue(entity);
        if (value != null)
        {
            var matchingRecords = records.Where(c => c.DataBaseRecordKeys.Any(k => k.DataBaseRecordKeyName == name && k.DataBaseRecordValue == value.ToString()));
            if (matchingRecords.Count() > 0)
            {
                records = matchingRecords;
            }
        }
    }

    result = (from c in records
                from p in c.DataBaseRecordProperties
                select new ResultDataType()
                {
                    ResultDataTypeId = c.ResultDataTypeID,
                    SubmitDate = c.SubmitDate,
                    SubmitUserId = c.SubmitUserId,
                    PropertyName = p.PropertyName
                });

    return result.AsQueryable();
}

最后一条语句将与数据库记录相关的属性表与所有属性的信息连接起来。

这对于单条记录来说已经足够好了,但我想摆脱那个循环,这样我就可以加快处理多条记录的速度。

【问题讨论】:

  • 你能把类型存储在表中吗?这样,您就不必调查属性,因为您已经知道表中所有内容的类型。
  • 已经完成了。问题在于,在某些情况下,我们需要将视图模型或 DTO 与 EF 实体进行比较,因此属性都相同但类型会不同。
  • 抱歉,我想我没有说清楚。我的意思是“类型的完全限定名称”中的类型,而不是“类型的实例”中的类型。如果您已经知道数据库中的哪些属性与System.a.b.c 的实例相关,那么您只需比较这些属性的值,而不必检查每个属性以查看传入的对象是否包含该属性。
  • 对,但在某些情况下,我们会将 system.a.b.c 与 System.a.d.f 进行比较

标签: c# linq entity-framework key-value


【解决方案1】:
using System.Reflection;

public IQueryable<ResultDataType> MatchingRecordFor(object entity)
{
    var records = _context.DataBaseRecords;

    var entityType = entity.GetType();
    var properties = entityType.GetProperties().Where(p => p.PropertyType.Namespace == "System");

    Func<KeyType, PropertyInfo, bool> keyMatchesProperty =
       (k, p) => p.Name == k.DataBaseRecordKeyName && p.GetValue(entity).ToString() == k.DataBaseRecordValue;

    var result =
        from r in records
        where r.DataBaseRecordKeys.All(k => properties.Any(pr => keyMatchesProperty(k, pr)))
        from p in r.DataBaseRecordProperties
        select new ResultDataType()
        {
            ResultDataTypeId = r.ResultDataTypeId,
            SubmitDate = r.SubmitDate,
            SubmitUserId = r.SubmitUserId,
            PropertyName = p.PropertyName
        });

    return result.AsQueryable();
}

希望我的查询语言正确。您必须对其进行基准测试,看看它是否比您原来的方法更有效。

编辑:这是错误的,请参阅 cmets

【讨论】:

  • 当我运行它时,我收到错误“无法创建类型为 'System.Reflection.PropertyInfo' 的常量值。在此上下文中仅支持原始类型或枚举类型。”如果该错误不存在,则不会使用 Any 导致它返回只有一个键匹配的行?在 DataBaseRecord 键上使用 All() 有意义吗?
  • 是的,我认为你是对的,应该是 All()。抱歉,我的思绪有点融化,我已将其更改为 All() 在我的回答中,但我不确定该错误的解决方案是什么。
  • 当我写那个答案时,我不明白 IEnumerable 和 IQueryable 之间的区别。我猜 DataBaseRecords 是一个 IQueryable,因此 LINQ 查询被转换为一些更特定于实现的查询语言,如 SQL,其限制是只能从查询外部引用原语和原语的枚举。一种解决方法可能是在查询之前创建IEnumerable&lt;string&gt; propertyNamesIEnumerable&lt;string&gt; propertyValues,但我不确定从那里做什么。应该如何在查询本身中引用它们?
  • 对,它使用实体框架,因此 IQueryable DataBaseRecords 映射到 sql 数据库。我可以为名称和值创建那些 IEnumerables,但是如果有两个单独的列表,我不知道如何将它们配对在一起,以便我可以检查给定的键是否与其值匹配。
  • 我可以使用这样的两个 IEnumerable 方法使其工作:dataBaseRecords.Where(c =&gt; c.DataBaseRecordKeys.All(k =&gt; propertyNames.Contains(k.DataBaseRecordKeyName) &amp;&amp; propertyValues.Contains(k.DataBaseRecordKeyValue))); 但除非我弄错了,any 属性值会碰巧匹配数据库记录的键值会被接受吗?我可以通过首先删除名称不在更改键名称集合中的所有属性来降低发生这种情况的可能性,我只是不知道这是否足够。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-29
  • 2014-02-12
  • 2011-02-17
  • 2020-11-03
  • 1970-01-01
  • 2013-02-13
  • 2010-09-25
相关资源
最近更新 更多