【问题标题】:Select value and append number to it in one LINQ statement?在一个 LINQ 语句中选择值并附加数字?
【发布时间】:2013-05-13 15:23:39
【问题描述】:

我的数据库中有以下两个名称:SMITHSMITH1

如果数据库中存在名称,如果只有 SMITH 存在,我想给它附加一个 1,并且如果带有数字的名称已经存在,我想增加名称的数字部分。

是否可以在一个 LINQ 语句中执行此操作?现在,我正在做:

string name = string.Format("{0}{1}{2}",first_name,middle_name,last_name).ToUpper();

//Check for duplicates
while(names.SingleOrDefault (d => d.Name== name) != null)
{
    //Get last char
    char lastChar = name[name.Length - 1];

    //Last char is number
    if(Char.IsNumber(lastChar))
    {
        name = name.Replace(lastChar,Convert.ToChar(Convert.ToInt32(lastChar) + 1));
    }
    else
    {
        name = (name + 1);
    }
}

由于我的 while 循环中有 LINQ 查询,它是否会在每次必须循环返回时执行选择?

另外,如果有人知道一种方法可以使它更简洁但可读,那会很棒吗?

这确实适用于没有数字或 1 - 9 的名称,但是当我到达 10 时会发生什么,它只会替换最后一个字符,在这种情况下为 0,我该如何处理?

有没有办法在 LINQ 中获取数字部分,所以 SMITH12 会返回 12

【问题讨论】:

  • @TimSchmelter - while(names.SingleOrDefault (d => d.Name== name) != null)。这不是 LINQ 查询吗?
  • @TimSchmelter:他的 while 循环中有一个 LINQ 语法语句作为他的 if 条件。
  • @TimSchmelter - 它的正确名称是什么?
  • 我认为这是一个 LINQ 查询,因为我认为它会像 SELECT Name FROM Table WHERE NAME = 'xxxxx' 这样对数据库进行查询。
  • 我支持@Jeff。您的方法需要大量迭代,而检查用户名并建议替代方案(通过适当的可用性检查)是一种更简单的方法。它还可以防止您的系统泄露与注册的名为“JohnDoe”的用户数量相关的信息。

标签: c# linq


【解决方案1】:

您的代码似乎不正确,无法编译。

这应该可行:

var sameNames = names
    .Where(n => n.Name.Length >= name.Length && n.Name.Substring(0,name.Length)==name);
if (sameNames.Any())
{
    var lastNameWithNumber = sameNames
        .Where(n => Char.IsDigit(n.Name.Last()))
        .Select(n => new { 
            n.Name, 
            Num = int.Parse(new string(n.Name.Reverse().TakeWhile(Char.IsDigit).Reverse().ToArray()))
        })
        .OrderByDescending(x => x.Num)
        .FirstOrDefault();
    if (lastNameWithNumber != null)
        name = name + (lastNameWithNumber.Num + 1);
    else
        name = name + "2";
}

这会检查一个或多个对象的Name 是否以给定名称开头。如果这是真的,第一个 linq 查询会检查是否有重复的数字结尾。然后它将按此编号 desc 排序并选择第一个(编号最高的)。新名称将是名称+最高数字+1。否则它将只使用名称+“2”,因为它是第二个名称。

【讨论】:

  • 我遗漏了一些部分,但它确实可以编译和运行。我自己做过。我会试试你的解决方案。
  • 您的解决方案有效,但我有几个问题:1)这会访问数据库多少次? 2) n.Name.Last() 是否只检查最后一个字符,如果我想检查最后哪些字符是数字怎么办?这需要正则表达式吗?
  • 如果永远不会有重复,OrderByDescending 是否必要?
  • @Xaisoft:你在唱什么 linq-provider,Linq-To-Sql?我很惊讶它完全适用于所有 text 和 char 方法,我忽略了您正在使用数据库。根查询检查是否有重复项。 lastNameWithNumber 采用具有最高数字结尾的副本,因为 OrderByDescending 它适用于零个、一个或多个数字副本。
  • 我正在使用 Linqpad 对内存中的集合进行尝试,但是当我针对我的实际数据库进行尝试时,我得到了错误:LINQ to Entities does not recognize the method 'Boolean IsDigit(Char)' method, and this method cannot be translated into a store expression.
【解决方案2】:

使用类似的东西。

IEnumerable<string> existingNames = ...;

string rawName = ...;

string finalName = (from suffix in Enumerable.Range(0, int.MaxValue)
                    let testName = rawName + (suffix == 0 ? "" : suffix.ToString())
                    where existingNames.All(n => n != testName)
                    select testName).FirstOrDefault();

请注意,existingNames 在每个 suffix 中枚举一次。我建议使用 existingNames 的 HashSet 而不是 IEnumerable&lt;string&gt;

HashSet<string> existingNames = ...;

string rawName = ...;

string finalName = (from suffix in Enumerable.Range(0, int.MaxValue)
                    let testName = rawName + (suffix == 0 ? "" : suffix.ToString())
                    where !existingNames.Contains(testName)
                    select testName).FirstOrDefault();

【讨论】:

    【解决方案3】:

    这将检索下一个名称(例如,如果最近的增量是 NAME10,下一个名称将是 NAME11):

    int tmp;
    var existingNames = from name in names
                    let numericIndex = name.IndexOfAny(new char[] {'1','2','3','4','5','6','7','8','9','0'}, 0)
                    let subName = numericIndex > -1 ? name.Substring(0, numericIndex) : name
                    let IndexAsString = numericIndex > -1 ? name.Substring(numericIndex) : "0"
                    let index = int.TryParse(IndexAsString, out tmp) ? tmp : 0
                    group new { subName, index } by subName into gs
                    select gs;
    var newNames = from existingName in existingNames
                    select new { BaseName = existingName.Key, Index = existingName.Max(a => a.index) + 1 };
    if (newNames.Any(a => a.BaseName == nameToAdd)) {
        // do your work to change the name
    }
    

    【讨论】:

      【解决方案4】:

      你应该做些类似的事情:

      遍历您的可枚举名称列表。对于每个这样的名称,

      • 将其拆分为其组成部分(基本名称和可选的数字后缀)
      • 扔掉任何基本名称与所需名称不匹配的项目
      • 找出剩余集合的最高后缀值(可能没有)。

      如果您找到了最大后缀,请将其加 1 并将其附加到所需的名称,否则按原样返回所需的名称。

      一个例子:

      private static readonly Regex rxNameSuffix = new Regex( @"^(?<name>.+)(?<suffix>\d*)$" ) ;
      string GenerateName( string name , IEnumerable<string> existingNames )
      {
          if ( string.IsNullOrWhiteSpace(name) ) throw new ArgumentException("name") ;
          int? maxSuffix = existingNames.Select( s => {
                  Match m = rxNameSuffix.Match(s) ;
                  if ( !m.Success ) throw new InvalidOperationException("that shouldn't have happened") ;
                  string pfx = m.Groups["name"].Value ;
                  int?   sfx = m.Groups["suffix"].Value.Length == 0 ? (int?)null : int.Parse( m.Groups["suffix"].Value ) ;
                  return new Tuple<string,int?>( pfx , sfx ) ;
              })
              .Where( x => name.Equals( x.Item1 , StringComparison.OrdinalIgnoreCase) )
              .Aggregate( (int?)null , (acc,x) => {
                  int? newacc ;
                  if ( !x.Item2.HasValue ) return acc ;
                  return !acc.HasValue || acc.Value < x.Item2.Value ? x.Item2 : acc ;
              }) ;
          int? suffix = maxSuffix.HasValue ? maxSuffix+1 : null ;
          return name + ( suffix.HasValue ? suffix.ToString() : "" ) ;
      }
      

      【讨论】:

        【解决方案5】:

        这是一种非常幼稚但易于阅读的方式来做你想做的事情:

        //Check for duplicates
        bool nameValid = names.SingleOrDefault (d => d.Name == name) == null;
        int count = 1;
        while(!nameValid)
        {
            if( names.SingleOrDefault (d => d.Name == name + count.ToString()) == null )
            {
                name = name + count.ToString();
                nameValid = true;
            }
            else
            {
                count++;
            }
        }
        // name now contains a unique name
        

        有很多方法可以提高效率,其中一些会牺牲可读性。

        如果需要,我会将这段代码的优化留给你作为练习。

        【讨论】:

        • 我试过这个。如果我已经有类似 JSMITH 的东西,它会返回 JSMITH,而不是 JSMITH1
        • 对不起,我看错了你的评论,然后在上面的代码中找到了一个错误。
        • bool 的初始设置不正确 - 仅当存在欺骗时才设置为 'true'(说名称很好)。对不起。
        猜你喜欢
        • 1970-01-01
        • 2014-09-16
        • 2011-10-28
        • 1970-01-01
        • 2012-06-06
        • 1970-01-01
        • 1970-01-01
        • 2017-09-25
        • 1970-01-01
        相关资源
        最近更新 更多