Linq OrderBy 计算属性(带连接​​)

Posted

技术标签:

【中文标题】Linq OrderBy 计算属性(带连接​​)【英文标题】:Linq OrderBy calculation of properties (with join) 【发布时间】:2012-08-29 16:27:50 【问题描述】:

我有两个数据库表映射到我的 MVC 应用程序中的模型:

public class BuildingLocation

    public int id  get; set; 
    public double Lat  get; set; 
    public double Lng  get; set; 


public class Building

    public int id  get; set; 
    public string title  get; set; 
    //OTHER STUFF

使用 Linq / Entities,我试图获取建筑物列表,按与地图上给定点的距离排序。

DefaultConnection db = new DefaultConnection();    

 public IEnumerable<dynamic> GetBuildings(double north, double south, double east, double west)
    
        double centreX = (east - west) / 2;
        double centreY = (north - south) / 2;
        var query = from b in db.Building
                    join l in db.BuildingLocation on
                    b.id equals l.id
                    select new b.id, b.title, l.Lat, l.Lng,
                            dist = Math.Sqrt(((centreX - l.Lat) * (centreX - l.Lat)) + ((centreY - l.Lng) * (centreY - l.Lng)))
                    ;

        query = query.Where(l => l.Lat > west);
        query = query.Where(l => l.Lat < east);
        query = query.Where(l => l.Lng > south);
        query = query.Where(l => l.Lng < north);
        query = query.OrderBy(c => c.dist);
        return query.AsEnumerable();
       

所以,显然这根本行不通。我以前从未使用过Linq。如何根据计算设置 OrderBy?

【问题讨论】:

说实话,不清楚它是否行不通 - 当你尝试它时发生了什么? (这取决于是否支持 Math.Sqrt 和所有算术。) 您可以在数据库中创建一个标量距离函数并将其作为查询的一部分调用。由于我自己只在 Linq2SQL 中做过,所以我无法给出完整的答案,但***.com/questions/3500509/… 可能会填补空白。 【参考方案1】:

试试这个:

public IEnumerable<dynamic> GetBuildings(double north, double south, double east, double west)

    double centreX = (east - west) / 2;
    double centreY = (north - south) / 2;
    var query = db.Building.Join(db.BuildingLocation.Where(l=> 
                             l.Lat > west &&  l.Lat < east 
                             && l.Lng > south && l.Lng < north), 
                             b => b.id , l => l.id, 
                             (b,l) => new 
                                            ID = b.id,
                                            Title = b.title,
                                            Lat = l.lat,
                                            Lng = l.Lng,
                                            dist =  Math.Sqrt(((centreX - l.Lat) * (centreX - l.Lat)) + ((centreY - l.Lng) * (centreY - l.Lng)))
                                          ).OrderBy(Q=>Q.dist);

    return query;

上面的查询是用 lambda 表达式编写的。如果您想在查询表达式中使用以下代码:

var query =  from result in (from b in db.Building
                join l in db.BuildingLocation on
                b.id equals l.id
                where l.Lat > west &&  l.Lat < east && l.Lng > south && l.Lng < north
                select new b.id, b.title, l.Lat, l.Lng,
                        dist = Math.Sqrt(((centreX - l.Lat) * (centreX - l.Lat)) + ((centreY - l.Lng) * (centreY - l.Lng))) )
                order by result.dist select result;

这将解决您的问题。

【讨论】:

【参考方案2】:

如果您使用实体框架,您可能会收到以下异常消息:

“LINQ to Entities 无法识别方法 'Double Sqrt(Double)' 方法,并且该方法无法转换为存储表达式。”

所以我会分两步解决这个问题,内部查询应该只从表中获取数据,而在外部(现在是内存查询)中将进行距离计算和排序:

double centreX = (east - west) / 2;
double centreY = (north - south) / 2;

var query = (from result in
                ((from b in db.Building
                    join l in db.BuildingLocation on b.id equals l.id
                    where l.Lat > west && l.Lat < east && l.Lng > south && l.Lng < north
                    select new  b.id, b.title, l.Lat, l.Lng ).AsEnumerable()
                )
            select new
            
                id = result.id,
                title = result.title,
                Lat = result.Lat,
                Lng = result.Lng,
                dist = Math.Sqrt(((centreX - l.Lat) * (centreX - l.Lat)) + ((centreY - l.Lng) * (centreY - l.Lng)))
            ).OrderBy(e => e.dist); 

【讨论】:

谢谢。我没有完全理解 Linq 转换为存储表达式与内存中发生的情况之间的区别。 如果您使用本地(内存中)查询或远程(EF、LINQ2SQL)查询,LINQ 的工作方式会有所不同。在第一种情况下,只涉及普通的 .net 代码,但在第二种情况下,首先生成表达式树,然后再进行解释。所以有一些 linq 查询可以在本地工作,但无法转换为表达式树,就像您在原始问题中的查询一样。 @user888734 这对您有帮助吗?【参考方案3】:

不胜枚举,而且只是为了建筑物,我会做这样的事情:

public IEnumerable<Building> GetBuildings(double north, double south, double east, double west)

    double centreX = (east - west) / 2;
    double centreY = (north - south) / 2;

    db.BuildingLocation.Where(l => l.Lat > west && l.Lat < east && l.Lng > south && l.Lng < north)
        .Join(
            db.Building, 
            b => b.id, 
            l => l.id, 
            (l, b) =>  new Building = b, l.Lat, l.Lng )
        .AsEnumerable()
        .OrderBy(l => Math.Sqrt(((centreX - l.Lat) * (centreX - l.Lat)) + ((centreY - l.Lng) * (centreY - l.Lng))))
        .Select(l => l.Building)

我假设它不起作用,因为距离计算无法转换为 SQL,所以我在 OrderBy 之前插入了 AsEnumerable 调用——这意味着过滤发生在数据库端,但排序使用 Linq to Objects 在内存中发生。

【讨论】:

以上是关于Linq OrderBy 计算属性(带连接​​)的主要内容,如果未能解决你的问题,请参考以下文章

LINQ to Entities OrderBy 表达式树

OrderBy 将 Dynamic Linq 与 Relation 结合使用

System.Linq.Dynamic的使用

LINQ 常规实践

C# LINQ Orderby - 真/假如何影响 orderby?

C# - 使用属性名称作为字符串由属性排序的代码