在 .Any() LINQ 查询中将 'string' 转换为 'int'

Convert 'string' to 'int' in an .Any() LINQ query

我有一个条件语句来评估针对我的数据库的 .Any() LINQ 查询。

当 casting/converting 一个字符串为 long 值时抛出错误。

(long.TryParse(m.Reference,out t)? t : long.MaxValue)

错误类似于:

LINQ to Entities does not recognize the method '....' method, and this method cannot be translated into a store expression

我是不是做错了什么?我怎样才能做到这一点?

using (var ctx = new DatabaseEntities())
{
    long t;
    if(!ctx.CustomerInboxes.Any(m=>m.CustomerId == customerId
        && m.Reference == item.ShoppingCartWebId.ToString() 
        && m.SubjectId == HerdbookConstants.PendingCartMessage 
        && item.ShoppingCartWebId > (long.TryParse(m.Reference,out t)? t : long.MaxValue)))
    )){
        // do something special
    }
}

长度检查与 string.Compare 的组合应该可以解决问题。

逻辑

  • 如果item.Reference.Length长度更长,它也是一个更大的数字。
  • 如果它们的长度相同,则执行 string.Compare 从左到右求值,这也适用于数字。

string.Compare(string a, string b)。这个returns:

  • 1 如果 a 大于 b
  • 0 如果 a 等于 b
  • -1 如果 b 大于 a

代码:

using (var ctx = new DatabaseEntities())
{
    var webId = item.ShoppingCartWebId.ToString();
    var webIdLength = webId.Length;

    if (!ctx.CustomerInboxes.Any(m => m.CustomerId == customerId
        && m.Reference == item.ShoppingCartWebId.ToString()
        && m.SubjectId == HerdbookConstants.PendingCartMessage
        && (m.Reference.Length > webIdLength || (m.Reference.Length == webIdLength && string.Compare(m.Reference, webId) > 0))))) {
            //do something special
        }
}

好处

索引!因为这里不涉及填充,所以实际的比较检查仍然可以受益于在该列上使用任何索引。如果您的数据增长,它不应该对性能产生太大影响。不幸的是,长度比较不会使用我认为的任何索引,但是比较 可能 比查询的其他部分

对性能的影响更小

假设

  • 没有 ,.
  • 等格式字符
  • 没有使用 0 或空格
  • 填充字符串的情况
  • 你总是在比较整数(没有分数)
using (var ctx = new DatabaseEntities())
{
    long t;
    if(!ctx.CustomerInboxes.ToList().Any(m=>m.CustomerId == customerId
        && m.Reference == item.ShoppingCartWebId.ToString() 
        && m.SubjectId == HerdbookConstants.PendingCartMessage 
        && item.ShoppingCartWebId > (long.TryParse(m.Reference,out t)? t : long.MaxValue)))
    ))
    {
        // do something special
    }
}

只需添加 .ToList()。虽然这会将您的整个列表返回给客户端,但它允许您执行您想要执行的自定义操作符。

根据此处的评论,另一种方法是从数据库中取回一个子集,然后执行自定义解析运算符。

long t;
var initialQuery = ctx.CustomerInboxes.Where(x => m.CustomerId == customerId
    && m.Reference == item.ShoppingCartWebId.ToString()
    && m.SubjectId == HerdbookConstants.PendingCartMessage).ToList();

if (!initialQuery.Any(m => item.ShoppingCartWebId > (long.TryParse(m.Reference, out t) ? t : long.MaxValue)))
{
    // do something special
}

一个可能的解决方案是在 CustomerInboxes source table 中添加一个计算列(我假设它是一个 table,但它可以是一个视图或一个存储过程),这样您就不必进行 LINQ2SQL 无法翻译的丑陋比较:

ReferenceAsLong AS ISNULL(TRY_CONVERT(BIGINT, Reference), CAST(9223372036854775807 AS BIGINT)) PERSISTED 

令您烦恼的 LINQ 条件已得到简化并且应该可以正常工作:

item.ShoppingCartWebId > ReferenceAsLong

您可以尝试使用 Convert.ToInt64 而不是 long.TryParse。

根据您的代码,当 m.Reference 不是有效数字时,条件应该失败,这可以使用 SqlFunctions.IsNumeric() 来完成。

要比较数字,您可以使用 string.Compare 并用 0s 模拟数字比较填充(可以用 SqlFunctions.Replicate())。

不太漂亮,但应该可以:

var itemId = item.ShoppingCartWebId.ToString();

ctx.CustomerInboxes.Any(m => ...
                          && SqlFunctions.IsNumeric(m.Reference) != 0 
                          && string.Compare(SqlFunctions.Replicate("0", m.Reference.Length > itemId.Length ? m.Reference.Length - itemId.Length : 0) + itemId, m.Reference) > 0);

但是您总是可以切换到 Linq to Objects 来检查这个特定部分:

ctx.CustomerInboxes.Where(m => m.CustomerId == customerId &&
                               m.Reference == item.ShoppingCartWebId.ToString()  &&
                               m.SubjectId == HerdbookConstants.PendingCartMessage)
                    .AsEnumerable()
                    .Any(c => item.ShoppingCartWebId > (long.TryParse(c.Reference, out t) ? t : long.MaxValue))

这个查询看起来会很糟糕。理想情况下,您应该在数据库中将其切换为一个 int 字段。

(只要没有前置的 0)
您可以修改逻辑,以便如果 ShoppingCartWebId 的长度长于满足条件,或者如果长度相等,则字符串比较应该可以正常工作

using (var ctx = new DatabaseEntities())
{
   long t;
  if(!ctx.CustomerInboxes.Any(m=>m.CustomerId == customerId
   && m.Reference == item.ShoppingCartWebId.ToString() 
   && m.SubjectId == HerdbookConstants.PendingCartMessage 
   && (item.ShoppingCartWebId.length > SqlFunctions.StringConvert((long).Reference) 
   || (item.ShoppingCartWebId.length == SqlFunctions.StringConvert((long).Reference
       && item.ShoppingCartWebId > SqlFunctions.StringConvert((long)m.Reference)
 ))){
     //do something special
   }
}