首页 > 解决方案 > 如何使用 linq 找到存在特定字符串的列表?

问题描述

我有一个包含两个列表的类

  public class SchemaView
  {
        public int Version { get; set; }
        public IEnumerable<EntityView> Entities { get; set; } 
        public IEnumerable<RelationView> Relations { get; set; } 
  }

当我有多个版本时,我将有一个带有升序版本号的 IEnumerable。

我正在寻找 linq 查询,它可以找到我具有属性 ==“某物”的实体或关系?

但是我将如何选择,两者都在哪里?

目前我有这样的东西,它只查看一个列表。

            .Select(x => x.Entities  )
            .Where(x => x.InternalName == entityName)
            .SelectMany(x => x.Attributes)
            .Select(x => x.InternalName)
            .ToList();

可以在某处添加或条件?以便它在关系中搜索一个名为某事物的内部名称?

上面的在关系列表中搜索失败,但是这个搜索不能包含在同一个 linq 中,还是我需要为此创建一个单独的 linq,或者可能是有条件的。

它可以做得更有效吗?

.Entities.Any(x => x.InternalName == entityName) ? .Entities                                                                                                         
.FirstOrDefault(x => x.InternalName == entityName).Attributes                                                                                                             .Select(x => x.InternalName) : .Relations.FirstOrDefault(x => x.InternalName == entityName).Attributes.Select(x => x.InternalName);

标签: c#linq

解决方案


所以你有一个序列SchemaViews,每个 SchemaView 都有零个或多个Entities,零个或多个Relations

你写了:

我正在寻找 linq 查询,它可以找到具有属性 ==“某物”的实体或关系?

是否只有一个实体/与属性的关系 == 某物?或者您的 SchemaViews 是否可以具有多个具有此属性值的实体和关系。您是在寻找实体和关系中的任何属性,还是寻找某些特定属性,例如属性InternalName?并且只有实体有这个属性,还是关系也有一个InternalName?或者您想为关系使用不同的属性?

一旦你找到了这样一个实体或关系,你想要完整的实体/关系,还是你想选择它的一些属性?

这么少的文字里有这么多的问题。考虑编辑您的问题并编写一个明确的要求。

我想你的意思是:

要求

给定一个 SchemaView 序列,其中每个 SchemaView 有零个或多个实体和零个或多个关系,每个实体都有一个 TKey 类型的属性 PropertyA,每个关系都有一个相同类型 TKey 的属性 PropertyB。还给定一个 TKey 类型的对象value,给我所有 SchemaViews 的所有实体和关系的串联,这些 SchemaViews 的 PropertyA 或 PropertyB 等于value

问题当然是 Relation 不是 Entity,因此您不能将它们放在一个序列中,否则它将是 的序列objects,这可能不是您想要的。显然你还需要一个elementSelectorA选择实体的属性和一个elementSelectorB选择关系的属性,并且两个选择的元素应该是相同的类型。

您当然可以使用Concat, 来连接实体和关系:

IEnumerable<SchemaView> schemaViews = ...
IEnumerable<TResult> selectedEntities = schemaViews.SelectMany(...).Where(...)
IEnumerable<TResult> selectedRelations = schemaView.SeelctMany(...).Where(...)

IEnumerable<TResult> result = selectedEntities.Concat(selectedRelations);

你是对的,如果你要SelectMany枚举所有实体,并SelectMany枚举所有关系,你将不得不枚举schemaViews两次。

如果您创建自己的扩展方法,则可以使所有序列仅枚举一次。使用扩展方法,您可以像使用标准现有 LINQ 方法一样使用它。

如果您不熟悉扩展方法,请参阅扩展方法揭秘

有两种可能:

  • 使用 SchemaViews 专门针对此问题制作扩展方法,
  • 或者制作一个通用方法,您可以将其用于具有两个子序列的每个序列,这使其成为SelectMany两个子类的某种形式。

如果您认为这是一个非常具体的问题,您只会在 SchemaVies 中看到,请选择第一个解决方案。如果您认为您还必须为其他课程解决此问题,请选择第二个解决方案

SchemaView 解决方案

以下解决方案将最多枚举每个 SchemaView / Entity / Relation:

public static IEnumerable<MyResultClass> SelectDoubleMany(
    this IEnumerable<SchemaView> schemaViews
    string internalName)
{
    foreach (var schemaView in schemaViews)
    {
        foreach (var entity in schemaView.Entities)
        {
            if (entity.PropertyA == internalName)
            {
                // create a MyResultClass, using properties from entity
                MyResultClass result = new MyResultClass
                {
                   Id = entity.Id,
                   Name = entity.Name,
                   ...
                };
                yield return result;
            }
        }

        foreach (var relation in schemaView.Relations)
        {
            if (relation.PropertyB == internalName)
            {
                // create a MyResultClass, using properties from relation
                MyResultClass result = new MyResultClass
                {
                   Id = relation.Id,
                   Name = relation.Name,
                   ...
                };
                yield return result;
            }
        }
    }        
}

用法:

string internalName = ...
IEnumerable<SchemaView> schemaViews = ...

IEnumerable<MyResultClass> results = schemaViews.SelectDoubleMany(internalName);

您甚至可以将它与其他 LINQ 语句交织在一起:

var results = schemaVies.Where(schamaView => schemaView.Id == 42)
                        .SelectDoubleMany(internalName)
                        .GroupBy(myResult => myResult.Id)
                        .FirstOrDefault();

一旦找到一个实体或关系,这将停止枚举

为了简单起见,我没有使用任何 LINQ。我认为这是最有效的方法(嗯,除了低级 GetEnumerator / MoveNext)。仅枚举您在最终结果中实际使用的元素。

通用解决方案

如果你想要它作为通用方法,你将需要很多参数

  • 输入 `IEnumerable 源。
  • 每个 TSource 都有两个类型的属性:IEnumerable<TSub1>和 `IEnumerable. 你需要两个 collectionSelector,就像在 SelectMany
  • 每个 TSub1 都有一个类型的属性TKey;每个 TSub2 都有一个相同类型的属性TKey,你需要一个 keySelector 就像在Join
  • 您只需要 TKey 值等于某个输入值的项目。
  • 您需要一个参数来将每个剩余的 TSub1 转换为 TResult
  • 您需要一个参数来将每个剩余的 TSub2 转换为 TResult
  • 最后:对于完整的通用:你需要一个IEqualityComparer<TKey>

我的,这是一个相当长的参数列表。但是随后您可以使用它来 SelectDoubleMany 每个带有两个子集合的集合。

这些参数类似于 SelectMany 与 Where 的组合,只是一切都是双重的。第一个没有EqualityComparer,和很多LINQ方法一样,只会调用另一个重载。

public static IEnumerable<TResult> SelectDoubleMany<TSource, Tsub1, Tsub2, TKey, TResult>(
    IEnumerable<TSource> source,

    // Two subcollection selectors, like in SelectMany:
    Func<TSource, TSub1> collectionSelector1,
    Func<STrouce, TSub2> CollectionSelector2,

    // the two KeySelectors, like in Join:
    Func<Tsub1, TKey> key1Selector,
    Func<Tsub2, TKey> key2Selector,

    // the "InternalName" that you want
    TKey value,

    // Two resultSelectors that convert Tsub1 and Tsub2 into TResult
    Func<Tsub1, TResult> resultSelector1,
    Func<Tsub1, TResult> resultSelector2)
{
    // call the overload with an equalitycomparer:
    return source.SelectDoubleMany(
        collectionSelector1,
        collectionSelector2,
        keySelector1,
        keySelector2,
        value,
        resultSelector1,
        resultSelector2,
        null);                // <== null equalityComparer
}

做真正事情的过载。如果相等比较器为空,则使用默认相等比较器

public static IEnumerable<TResult> SelectDoubleMany<TSource, Tsub1, Tsub2, TKey, TResult>(
    IEnumerable<TSource> source,

    // Two subcollection selectors, like in SelectMany:
    Func<TSource, IEnumerable<Tsub1>> collectionSelector1,
    Func<TSource, IEnumerable<Tsub2>> CollectionSelector2,

    // the two KeySelectors, like in Join:
    Func<Tsub1, TKey> key1Selector,
    Func<Tsub2, TKey> key2Selector,

    // the "InternalName" that you want
    TKey value,

    // Two resultSelectors that convert Tsub1 and Tsub2 into TResult
    Func<Tsub1, TResult> resultSelector1,
    Func<Tsub1, TResult> resultSelector2,

    IEqualityComparer<TKey> comparer)
{
    // If the equality comparer is null, use the default comparer:
    if (comparer == null)
    {
        comparer = EqualityComparer<TKey>.Default;
    }

    // code is similar to the code above, only generic:
    foreach (TSource sourceElement in source)
    {
         IEnumerable<Tsub1> subCollection1 = collectionSelector1(sourceElement);
         foreach(Tsub1 subCollectionElement in subCollection1)
         {
             TKey key = keySelector1(subCollectionElement);
             if (comparer.Equals(key, value))
             {
                 TResult result = result1Selector(subCollectionElement);
                 yield return result;
             }
         }

         IEnumerable<Tsub2> subCollection2 = collectionSelector2(sourceElement);
         foreach(Tsub2 subCollectionElement in subCollection2)
         {
             TKey key = keySelector1(subCollectionElement);
             if (comparer.Equals(key, value))
             {
                 TResult result = result1Selector(subCollectionElement);
                 yield return result;
             }
         }
     }
}

虽然这非常有效:每个元素将最多枚举一次,只枚举您需要的项目,我不确定您是否会重用这样的通用方法。


推荐阅读