在尝试检索特定半径(英里)内的项目列表时,以及根据项目的邮政编码,构建数据库和mvc应用程序以获得最快查找效果的最佳方法是什么。即使它涉及到我当前设置的更改,我仍然愿意接受建议。
我目前正在使用包含邮政编码和纬度/经度坐标的.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。
答案 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 functions和STDistance。如果该函数用于整个数据集而不是有限的结果,则返回过程需要更长的时间。
如果您对此程序有任何疑问,请与我们联系。