构建远程查找数据库和程序的最佳方法

时间:2014-01-17 23:05:38

标签: c# sql-server asp.net-mvc database-design architecture

在尝试检索特定半径(英里)内的项目列表时,以及根据项目的邮政编码,构建数据库和mvc应用程序以获得最快查找效果的最佳方法是什么。即使它涉及到我当前设置的更改,我仍然愿意接受建议。

Simple Schema

我目前正在使用包含邮政编码和纬度/经度坐标的.txt文件来计算距离。使用Haversine Formula,您可以使用坐标计算两点之间的距离。

我正在使用PagedList 1.17.0.0在我的索引控制器上分页我的数据。我面临的主要问题与EF有关。当选择50英里距离内的项目时,我的SQL语句变得太嵌套并在EF中抛出错误。

控制器的当前流量

public ActionResult Index(string sortOrder, string Categories, string Manufacturers, int? page, bool clearFilters = false
         ,string DistanceLimit = "", int Vehicle = 0)  

参数部分中的每个字符串都是EF可能使用的过滤器。因为我无法告诉EF object.Where(P => P.Distance <= 50)

我使用字符串构建器生成原始查询。我的ZipCode类检索半径50英里内的所有拉链,然后构造一个简单的选择,每个邮政编码作为where子句中的参数。这是问题发生的地方,因为where子句太多了。半径25英里是好的,但不是50英里。

如果上述功能有效,它会获取该距离内物品的主键。然后我选择EF Item.Include(P => P.Manufacturer).Include(P => P.Category).Include(3 other tables)

然后我在ToList()上拨打IQueryable并删除不在我之前的PK列表中的所有Item

最后,我调用ToPagedList()并将其返回到viewmodel中的视图。

正如你所看到的,这里有很多。

对于One :它在半径25英里范围内无效。

2 :它过于复杂,感觉就像Rube Goldberg机器。

3 :所有这些单独的查询执行都会显着影响搜索结果的速度。

正如我先前所说,我对任何制作了具有类似功能的系统的人提出任何建议都是开放的。我从未处理过地理空间数据,最近才知道SQL Server的地理类型;但是,根据我的理解,似乎我必须在每个纬度/经度记录上调用StDistance以确定哪些物品在附近。更不用说我必须将我的.txt文件移动到dB。

1 个答案:

答案 0 :(得分:1)

在尝试根据半径检索项目时,我不得不在公司中处理同样的问题。我们数据的唯一区别是我们已经将Lat,Long存储在我们需要的每个项目的数据库中。在某些情况下,我们开始使用GeographyPoint数据类型在返回期间删除服务器上的其他工作负载。我们的一些工具无法创建地理数据类型,因此其使用受到限制。

这是我们用来执行此操作的存储过程的示例。看看评论。

CREATE PROCEDURE [dbo].GetItems_ByRadius
(
 @pLat DECIMAL(20, 13)      --= 35.151
,@pLon DECIMAL(20, 13)      --= -86.59
,@pRadius DECIMAL(7, 2)     --= 2
)
AS
BEGIN
    SET NOCOUNT ON;
    /*Declare Local Variables to avoid parameter sniffing*/
    DECLARE @Lat VARCHAR(20) = @pLat
       ,@Lon VARCHAR(20) = @pLon
       ,@Radius DECIMAL(7, 2) = @pRadius
       ,@Earth_Radius INT = 6371000

    /*Declare additional variables that are needed for calculations*/   
    DECLARE @Distance DECIMAL(10, 2) = @Radius * 1609.344
       ,@Point_geo GEOGRAPHY
       ,@Min_Lat DECIMAL(20, 13)
       ,@Max_Lat DECIMAL(20, 13)
       ,@Min_Long DECIMAL(20, 13)
       ,@Max_Long DECIMAL(20, 13)

    /*Convert original Lat Long parameters to GeographyPoint this will be used to check radius distance*/
    SET @Point_geo = GEOGRAPHY::STGeomFromText('POINT(' + @Lon + ' ' + @Lat + ')', 4326)

    /*Build Bounding Box*/           
    SET @Min_Lat = @Lat - DEGREES(@distance / @Earth_Radius)
    SET @Max_Lat = @Lat + DEGREES(@distance / @Earth_Radius)    
    SET @Min_Long = @Lon - DEGREES(@distance / @Earth_Radius / COS(RADIANS(@Lat)))
    SET @Max_Long = @Lon + DEGREES(@distance / @Earth_Radius / COS(RADIANS(@Lat)));

    WITH    MyBoxResults
              AS ( SELECT *
                       ,GEOGRAPHY::STPointFromText('POINT(' + CAST(Geog_Long AS VARCHAR(20)) + ' '
                                                   + CAST(Geog_Lat AS VARCHAR(20)) + ')', 4326) AS GeogLocation
                    FROM MyTable
                    WHERE ( Geog_Lat BETWEEN @Min_Lat AND @Max_Lat )
                        AND ( Geog_Long BETWEEN @Min_Long AND @Max_Long )) \
/*Using Long and Lat decimal columns we get results that are within the box that surrounds radius*/
        SELECT *
            FROM MyBoxResults
            WHERE @Point_geo.STDistance(GeogLocation) <= @Distance; 
/*This further limits results to only the radius instead of original box.*/

END

在程序中,我们采用了几种方法来加快性能。
- 使用局部变量可防止参数嗅探 Read here more about it
- 使用CTE将结果限制为边界框,可以使用Lat和Long列上的索引快速完成。 - 仅在需要时使用Geography functionsSTDistance。如果该函数用于整个数据集而不是有限的结果,则返回过程需要更长的时间。

如果您对此程序有任何疑问,请与我们联系。