C# 基于不同条件的Linq倍数

C# 基于不同条件的Linq倍数,c#,performance,linq,C#,Performance,Linq,在搜索页面中,我有一些基于它们的选项搜索查询必须不同。我写过这样的话: int userId = Convert.ToInt32(HttpContext.User.Identity.GetUserId()); var followings = (from f in _context.Followers where f.FollowersFollowerId == userId && f.FollowersIsAccept == true

在搜索页面中,我有一些基于它们的选项搜索查询必须不同。我写过这样的话:

int userId = Convert.ToInt32(HttpContext.User.Identity.GetUserId());

var followings = (from f in _context.Followers
                  where f.FollowersFollowerId == userId && f.FollowersIsAccept == true
                  select f.FollowersUserId).ToList();

int value;

if (spto.Page == 0)
{
    var post = _context.Posts.AsNoTracking().Where(p => (followings.Contains(p.PostsUserId) || p.PostsUser.UserIsPublic == true || p.PostsUserId == userId) && p.PostIsAccept == true).Select(p => p).AsEnumerable();

    if(spto.MinCost != null)
    {
        post = post.Where(p => int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost).Select(p => p);
    }

    if (spto.MaxCost != null)
    {
        post = post.Where(p => int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost).Select(p => p);
    }

    if (spto.TypeId != null)
    {
        post = post.Where(p => p.PostTypeId == spto.TypeId).Select(p => p);
    }

    if (spto.CityId != null)
    {
        post = post.Where(p => p.PostCityId == spto.CityId).Select(p => p);
    }

    if (spto.IsImmidiate != null)
    {
        post = post.Where(p => p.PostIsImmediate == true).Select(p => p);
    }

    var posts = post.Select(p => new
     {
         p.Id,
         Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
         p.PostCity.CityName,
         p.PostType.TypeName
     }).AsEnumerable().Take(15).Select(p => p).ToList();

    if (posts.Count != 0)
        return Ok(posts);

    return NotFound();

在本例中,我有6个查询需要时间,性能很低,代码太长。有没有更好的方法来编写更好的代码?

我已经解决了三元运算符的问题:

var post = _context.Posts.AsNoTracking().Where(p => 
(followings.Contains(p.PostsUserId) || p.PostsUser.UserIsPublic == true || p.PostsUserId == userId) && p.PostIsAccept == true
&& (spto.MinCost != null ? int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost : 1 == 1)
&& (spto.MaxCost != null ? int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost : 1 == 1)
&& (spto.TypeId != null ? p.PostTypeId == spto.TypeId : 1 == 1)
&& (spto.CityId != null ? p.PostCityId == spto.CityId : 1 == 1)
&& (spto.IsImmidiate != null && spto.IsImmidiate == true ? p.PostIsImmediate == true : 1 == 1)).Select(p => new
{
    p.Id,
    Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
    p.PostCity.CityName,
    p.PostType.TypeName
}).Skip(spto.Page * 15).Take(15).ToList();
编辑更好的代码:

Thanx到@ZoharPeled、@haraldcopoolse、@JonasH我已将代码更改如下:

int value;

var post = _context.Posts.AsNoTracking().Where(p =>
    (followings.Contains(p.PostsUserId) || p.PostsUser.UserIsPublic == true || p.PostsUserId == userId) && p.PostIsAccept == true
    && (spto.MinCost == null || (int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost))
    && (spto.MaxCost == null || (int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost))
    && (spto.TypeId == null || p.PostTypeId == spto.TypeId)
    && (spto.CityId == null || p.PostCityId == spto.CityId)
    && (spto.IsImmidiate == null || p.PostIsImmediate == true)).Select(p => new
    {
        p.Id,
        Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
        p.PostCity.CityName,
        p.PostType.TypeName
    }).Skip(spto.Page * 15).Take(15).ToList();
编辑最佳代码:


我已经解决了三元运算符的问题:

var post = _context.Posts.AsNoTracking().Where(p => 
(followings.Contains(p.PostsUserId) || p.PostsUser.UserIsPublic == true || p.PostsUserId == userId) && p.PostIsAccept == true
&& (spto.MinCost != null ? int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost : 1 == 1)
&& (spto.MaxCost != null ? int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost : 1 == 1)
&& (spto.TypeId != null ? p.PostTypeId == spto.TypeId : 1 == 1)
&& (spto.CityId != null ? p.PostCityId == spto.CityId : 1 == 1)
&& (spto.IsImmidiate != null && spto.IsImmidiate == true ? p.PostIsImmediate == true : 1 == 1)).Select(p => new
{
    p.Id,
    Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
    p.PostCity.CityName,
    p.PostType.TypeName
}).Skip(spto.Page * 15).Take(15).ToList();
编辑更好的代码:

Thanx到@ZoharPeled、@haraldcopoolse、@JonasH我已将代码更改如下:

int value;

var post = _context.Posts.AsNoTracking().Where(p =>
    (followings.Contains(p.PostsUserId) || p.PostsUser.UserIsPublic == true || p.PostsUserId == userId) && p.PostIsAccept == true
    && (spto.MinCost == null || (int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost))
    && (spto.MaxCost == null || (int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost))
    && (spto.TypeId == null || p.PostTypeId == spto.TypeId)
    && (spto.CityId == null || p.PostCityId == spto.CityId)
    && (spto.IsImmidiate == null || p.PostIsImmediate == true)).Select(p => new
    {
        p.Id,
        Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
        p.PostCity.CityName,
        p.PostType.TypeName
    }).Skip(spto.Page * 15).Take(15).ToList();
编辑最佳代码:

一些意见:

.AsEnumerable()
如果使用自定义集合,这意味着隐藏where运算符。在这种情况下不应该需要它

.Select(p => p)
我看不出这有什么用,把它拿走

int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost
解析可能会很昂贵,因此您希望尽可能少地进行解析,如果同时使用值的直接比较(即int.TryParsep.PostCost、out value&&value>=spo.MinCost)进行min和max.replace,则解析会进行两次,即四次。我还建议在同时存在最小和最大成本的情况下使用显式示例,以避免两次解析

followings.Contains(p.PostsUserId) 
下面是一个列表,所以它将搜索所有项目。使用哈希集来提高性能。即,在创建以下列表时,将ToList替换为。HashSet使用哈希表使包含常数时间操作而不是线性操作

查询顺序

您可能希望订购检查以尽早消除尽可能多的项目,并在慢速检查之前进行简单、快速的检查

在何处合并运算符

通常情况下,接线员比多个呼叫速度快的单次呼叫

使用普通循环

如果您确实需要尽可能高的性能,那么最好使用常规循环。Linq非常适合编写紧凑的代码,但普通循环的性能通常更好

侧面图

无论何时谈论绩效,都必须指出绩效的重要性。上面的评论是合理的起点,但可能会有一些完全不同的事情需要时间。了解的唯一方法是了解个人资料。这也可以很好地说明改进情况。

一些观察结果:

.AsEnumerable()
如果使用自定义集合,这意味着隐藏where运算符。在这种情况下不应该需要它

.Select(p => p)
我看不出这有什么用,把它拿走

int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost
解析可能会很昂贵,因此您希望尽可能少地进行解析,如果同时使用值的直接比较(即int.TryParsep.PostCost、out value&&value>=spo.MinCost)进行min和max.replace,则解析会进行两次,即四次。我还建议在同时存在最小和最大成本的情况下使用显式示例,以避免两次解析

followings.Contains(p.PostsUserId) 
下面是一个列表,所以它将搜索所有项目。使用哈希集来提高性能。即,在创建以下列表时,将ToList替换为。HashSet使用哈希表使包含常数时间操作而不是线性操作

查询顺序

您可能希望订购检查以尽早消除尽可能多的项目,并在慢速检查之前进行简单、快速的检查

在何处合并运算符

通常情况下,接线员比多个呼叫速度快的单次呼叫

使用普通循环

如果您确实需要尽可能高的性能,那么最好使用常规循环。Linq非常适合编写紧凑的代码,但普通循环的性能通常更好

侧面图


无论何时谈论绩效,都必须指出绩效的重要性。上面的评论是合理的起点,但可能会有一些完全不同的事情需要时间。了解的唯一方法是了解个人资料。这也可以很好地说明改进情况。

简短回答:如果直到最后才执行ToList和AsEnumerable,那么只会在dbContext上执行一个查询

因此,在创建列表帖子之前,请将所有内容保持为IQueryable:

可数可数 出于跳过所有ToList/AsEnumerable有助于提高性能的原因,您需要了解IEnumerable和IQueryable之间的区别

数不清 实现IEnumerable的类的对象表示在该对象可以生成的序列上枚举的可能性

对象保存所有内容以生成序列。一旦请求序列,本地进程将执行代码以生成序列

在底层,您可以使用GetEnumerator生成序列,并重复调用MoveNext。只要MoveNext返回true,序列中就有下一个元素。您可以使用属性Current访问下一个元素

按如下方式枚举序列:

IEnumerable<Customer> customers = ...
using (IEnumarator<Customer> customerEnumerator = customers.GetEnumerator())
{
    while (customerEnumerator.MoveNext())
    {
        // there is still a Customer in the sequence, fetch it and process it
        Customer customer = customerEnumerator.Current;
        ProcessCustomer(customer);
    }
}
现在您知道了forea背后的代码 ch,你可能知道foreach的第一行发生了什么

请务必记住,IEnumerable是由本地进程处理的。IEnumerable可以调用本地进程可以调用的每个方法

易变的 实现IQueryable的类的对象看起来非常像IEnumerable,它还表示生成类似对象的可枚举序列的可能性。然而,不同之处在于,应该有另一个过程来提供数据

为此,IQueryable对象包含一个表达式和一个提供程序。表达式表示必须以某种通用格式获取哪些数据的公式;提供者知道谁必须提供数据(通常是数据库管理系统),以及使用什么语言与数据库管理系统(通常是SQL)通信

只要连接LINQ方法或您自己只返回IQueryable的方法,就只会更改表达式。未执行任何查询,未联系数据库。连接这样的语句是一种快速方法

只有在使用GetEnumerator/MoveNext/Current在最低级别或使用foreach在更高级别开始枚举时,表达式才会发送给提供程序,由提供程序将其转换为SQL并从数据库中获取数据。返回的数据表示为调用方的可枚举序列

请注意,有些LINQ方法不返回IQueryable,而是返回List、TResult、bool或int等:ToList/FirstOrDefault/Any/Count/etc。这些方法将在内部深处调用GetEnumerator/MoveNext/Current`;这些就是从数据库中获取数据的方法

回到你的问题上来 数据库管理系统在处理数据方面进行了极大的优化:获取、排序、筛选等。数据库查询中较慢的部分之一是将获取的数据传输到本地进程

因此,明智的做法是让DBMS尽可能多地处理数据库,只将数据传输到您实际计划使用的本地进程

因此,如果本地进程不使用获取的数据,请尽量避免使用ToList。在您的情况下:您将以下内容传输到本地进程,只是将其传输回IQueryable.Contains方法中的数据库

此外,这在一定程度上取决于您使用的框架,AsEnumerable将数据传输到本地进程,因此您的本地进程必须使用Where和Contains进行过滤

唉,你忘了在所有帖子中给我们描述你的需求,只给我那些……的帖子,对我来说分析你的所有查询有点太多了,但是如果你尽可能长时间地保持所有内容都是可查询的,你就会获得最大的效率

Int.TryParse可能有一些问题。。。。您的提供商可能不知道如何将其转换为SQL。有几种可能的解决方案:

显然邮政编码代表一个数字。考虑把它存储为一个数字。如果它是一个数量的价格或某物,一些小数有限的东西,考虑把它存储为十进制。 如果你真的不能说服你的项目负责人,数字应该被存储为小数,或者搜索一个合适的数据库的工作,或者考虑创建一个存储过程,将PostCost中的字符串转换为十进制/int。 如果只使用15个元素,请使用IQueryable.Take15,而不是IEnumerable.Take15。 进一步优化:

int userId = 

var followerUserIds = _context.Followers
    .Where(follower => follower.FollowersFollowerId == userId
                    && follower.FollowersIsAccept)
    .Select(follower => follower.FollowersUserId);
换句话说:使以下内容成为IQueryable,但不要执行它:对于所有追随者,只保留那些被接受并且FollowersFollowerId等于userId的追随者。从剩余的FollowersUserId中获取FollowersUserId

似乎您只打算在页面为零时使用它。如果页面不为零,为什么还要创建此查询

顺便说一句,不要使用where a==true这样的语句,或者更糟糕的语句:如果a==true那么b==true或者b==false,这会给读者一种印象,即你很难理解布尔的概念,只需使用:where a和b=a

接下来,您决定创建一个包含零个或多个post的查询,并认为最好给它一个单数名词作为标识符:post

Contains将导致与Followers表的联接。如果您只使用followers表加入已接受的帖子,可能会更有效率。因此,在决定加入之前,首先检查postsaccept和其他谓词:

.Where(post => post.PostIsAccept
            && (post.PostsUser.UserIsPublic || post.PostsUserId == userId
                || followings.Contains(post.PostsUserId));
所有不被接受的职位将不必与以下人员合并;取决于您的提供商是否足够聪明:它不会加入所有公共用户,或者加入具有userId的用户,因为它知道它已经通过了筛选

考虑使用Contains,而不是Any

在我看来,你想要的是:

我有一个用户ID;给我所有被接受的帖子,要么来自这个用户,要么来自一个公共用户,要么有一个被接受的追随者

请注意:我仍然没有执行查询,我只是更改了表达式

在第一次定义帖子之后,将根据spto的各种值进一步过滤帖子。你可以考虑做一个大的查询,但我认为这不会加速这个过程。这只会使它更不可读

最后:为什么使用:

.Select(post => post)

这对您的序列没有任何影响,只会使它变慢。

简短回答:如果直到最后才执行ToList和AsEnumerable,那么您将只在dbContext上执行一个查询

因此,在创建列表帖子之前,请将所有内容保持为IQueryable:

可数可数 出于跳过所有ToList/AsEnumerable有助于提高性能的原因,您需要了解IEnumerable和IQueryable之间的区别

数不清 实现IEnumerable的类的对象表示在该对象可以生成的序列上枚举的可能性

对象保存所有内容以生成序列。一旦请求序列,本地进程将执行代码以生成序列

在底层,您可以使用GetEnumerator生成序列,并重复调用MoveNext。只要MoveNext返回true,序列中就有下一个元素。您可以使用属性Current访问下一个元素

按如下方式枚举序列:

IEnumerable<Customer> customers = ...
using (IEnumarator<Customer> customerEnumerator = customers.GetEnumerator())
{
    while (customerEnumerator.MoveNext())
    {
        // there is still a Customer in the sequence, fetch it and process it
        Customer customer = customerEnumerator.Current;
        ProcessCustomer(customer);
    }
}
现在您已经知道了foreach背后的代码,您可能已经了解了foreach的第一行中发生了什么

请务必记住,IEnumerable是由本地进程处理的。IEnumerable可以调用本地进程可以调用的每个方法

易变的 实现IQueryable的类的对象看起来非常像IEnumerable,它还表示生成类似对象的可枚举序列的可能性。然而,不同之处在于,应该有另一个过程来提供数据

为此,IQueryable对象包含一个表达式和一个提供程序。表达式表示必须以某种通用格式获取哪些数据的公式;提供者知道谁必须提供数据(通常是数据库管理系统),以及使用什么语言与数据库管理系统(通常是SQL)通信

只要连接LINQ方法或您自己只返回IQueryable的方法,就只会更改表达式。未执行任何查询,未联系数据库。连接这样的语句是一种快速方法

只有在使用GetEnumerator/MoveNext/Current在最低级别或使用foreach在更高级别开始枚举时,表达式才会发送给提供程序,由提供程序将其转换为SQL并从数据库中获取数据。返回的数据表示为调用方的可枚举序列

请注意,有些LINQ方法不返回IQueryable,而是返回List、TResult、bool或int等:ToList/FirstOrDefault/Any/Count/etc。这些方法将在内部深处调用GetEnumerator/MoveNext/Current`;这些就是从数据库中获取数据的方法

回到你的问题上来 数据库管理系统在处理数据方面进行了极大的优化:获取、排序、筛选等。数据库查询中较慢的部分之一是将获取的数据传输到本地进程

因此,明智的做法是让DBMS尽可能多地处理数据库,只将数据传输到您实际计划使用的本地进程

因此,如果本地进程不使用获取的数据,请尽量避免使用ToList。在您的情况下:您将以下内容传输到本地进程,只是将其传输回IQueryable.Contains方法中的数据库

此外,这在一定程度上取决于您使用的框架,AsEnumerable将数据传输到本地进程,因此您的本地进程必须使用Where和Contains进行过滤

唉,你忘了在所有帖子中给我们描述你的需求,只给我那些……的帖子,对我来说分析你的所有查询有点太多了,但是如果你尽可能长时间地保持所有内容都是可查询的,你就会获得最大的效率

Int.TryParse可能有一些问题。。。。您的提供商可能不知道如何将其转换为SQL。有几种可能的解决方案:

显然邮政编码代表一个数字。考虑把它存储为一个数字。如果它是一个数量的价格或某物,一些小数有限的东西,考虑把它存储为十进制。 如果你真的不能说服你的项目负责人,数字应该被存储为小数,或者搜索一个合适的数据库的工作,或者考虑创建一个存储过程,将PostCost中的字符串转换为十进制/int。 如果只使用15个元素,请使用IQueryable.Take15,而不是IEnumerable.Tak e15。 进一步优化:

int userId = 

var followerUserIds = _context.Followers
    .Where(follower => follower.FollowersFollowerId == userId
                    && follower.FollowersIsAccept)
    .Select(follower => follower.FollowersUserId);
换句话说:使以下内容成为IQueryable,但不要执行它:对于所有追随者,只保留那些被接受并且FollowersFollowerId等于userId的追随者。从剩余的FollowersUserId中获取FollowersUserId

似乎您只打算在页面为零时使用它。如果页面不为零,为什么还要创建此查询

顺便说一句,不要使用where a==true这样的语句,或者更糟糕的语句:如果a==true那么b==true或者b==false,这会给读者一种印象,即你很难理解布尔的概念,只需使用:where a和b=a

接下来,您决定创建一个包含零个或多个post的查询,并认为最好给它一个单数名词作为标识符:post

Contains将导致与Followers表的联接。如果您只使用followers表加入已接受的帖子,可能会更有效率。因此,在决定加入之前,首先检查postsaccept和其他谓词:

.Where(post => post.PostIsAccept
            && (post.PostsUser.UserIsPublic || post.PostsUserId == userId
                || followings.Contains(post.PostsUserId));
所有不被接受的职位将不必与以下人员合并;取决于您的提供商是否足够聪明:它不会加入所有公共用户,或者加入具有userId的用户,因为它知道它已经通过了筛选

考虑使用Contains,而不是Any

在我看来,你想要的是:

我有一个用户ID;给我所有被接受的帖子,要么来自这个用户,要么来自一个公共用户,要么有一个被接受的追随者

请注意:我仍然没有执行查询,我只是更改了表达式

在第一次定义帖子之后,将根据spto的各种值进一步过滤帖子。你可以考虑做一个大的查询,但我认为这不会加速这个过程。这只会使它更不可读

最后:为什么使用:

.Select(post => post)


这对您的序列没有任何影响,只会使它变慢。

这是用于过滤的吗?如果是,请为p中的元素创建一个方法。例如,public bool示例元素e{YOUR FILTERING CODES}。之后,只需使用post.Where p=>Examplep==true@A是的,如果有必要使用spto,请将其作为参数插入方法中。似乎IsImmidiate具有最高优先级。如果IsImmidiate不为空,则代码不为空,代码不需要检查CityID是否为空。这是用于筛选的吗?如果是,则为p中的元素创建一个方法。例如,public bool示例元素e{YOUR FILTERING CODES}。之后,只需使用post.Where p=>Examplep==true@A是的,如果有必要使用spto,请将其作为参数插入方法中。似乎IsImmidiate具有最高优先级。如果IsImmidiate不为空,则代码不为空,代码不需要检查CityID是否为空。spto.MinCost!=无效的int.TryParsep.PostCost,out value&&Convert.ToInt32p.PostCost>=spto.MinCost:1==1可以使用| |-如下所示:spto.MinCost为null | | int.TryParsep.PostCost,out PostCost&&PostCost>=spto.MinCost。当然,这对你所有的情况都是正确的。另外,当x是布尔时,ifx==true可以简化为ifx。我建议删除一个可计算项,这样Take15将命中数据库,从而避免将所有项放入memory@ZoharPeled我想检查spto.MinCost是否不为null do…这正是我的建议的原因-| |是短路或条件-这意味着只有在左侧side为false-因此spto.MinCost为null | |如果spto.MinCost为null,则whatever将不会计算whatever…@ZoharPeled Thanx很多,但当我使用spto.MinCost为null时,得到此错误表达式树可能不包含“is”模式匹配运算符pto.MinCost!=无效的int.TryParsep.PostCost,out value&&Convert.ToInt32p.PostCost>=spto.MinCost:1==1可以使用| |-如下所示:spto.MinCost为null | | int.TryParsep.PostCost,out PostCost&&PostCost>=spto.MinCost。当然,这对你所有的情况都是正确的。另外,当x是布尔时,ifx==true可以简化为ifx。我建议删除一个可计算项,这样Take15将命中数据库,从而避免将所有项放入memory@ZoharPeled我想检查spto.MinCost是否不为null do…这正是我的建议的原因-| |是短路或条件-这意味着只有在左侧side为false-因此spto.MinCost为null | |如果spto.MinCost为null,则whatever将不会计算whatever…@ZoharPeled Thanx很多,但当我使用spto.MinCost为null时出现此错误表达式树可能不包含“is”模式匹配运算符谢谢。我已删除.AsEnumerable和.Selectp=>p。我不懂HashSet。你能解释一下吗?我在答案中合并Where操作符。你看到了吗?@mm sh添加了hashSet细节和分析段落谢谢。我已删除.AsEnumerable和.Selectp=>p。我不懂HashSet。你能解释一下吗?我在答案中合并Where操作符。你会说话吗
e这?@mm sh添加了hashSet细节和分析段落,我在分页前使用页面变量。如果spto.Page==0,我会在后面加上else,这样做:.skippsto.Page*15.take15我们可以将数字存储为十进制,但在某些情况下,用户不喜欢输入成本,而不是他/她在协议下输入的成本,或者打电话给我,或者…我在分页前使用页面变量。如果spto.Page==0,我会在后面加上else,这样做:.skippto.Page*15.take15我们可以将数字存储为十进制,但在某些情况下,用户不喜欢输入成本,而不是他/她在协议下输入的成本,或者打电话给我或。。。