【问题标题】:How to optimize Entity Framework query when using spatial types and automapper?使用空间类型和自动映射器时如何优化实体框架查询?
【发布时间】:2025-11-30 07:15:01
【问题描述】:

我正在构建的并不是非常独特的东西。简而言之,我正在使用 ASP.NET MVC 4(Web Api)和 Entity Framework 5(具有空间支持)在 Azure 中创建一个类似 FourSquare 的小型服务。所以我使用的是 SQL Azure,而不是像 MongoDB 或 CouchDB 这样的 NoSQL 数据库之一。部分是因为我对 .NET 更流利/熟悉,部分是为了了解开发经验是什么(重构、部署、测试),部分是为了了解它将如何与例如。 node.js/MongoDB。

现在让我们看一些代码。

/// <summary>
/// Return the nearest locations relative from the given longitude/latitude
/// </summary>
/// <param name="longitude">Longitude</param>
/// <param name="latitude">Latitude</param>
/// <param name="maxresults">Optional maximum results, default is 2</param>
/// <param name="radius">Optional maximum radius in kilometres, default is 50 km</param>
/// <returns></returns>
public JsonEnvelope Get(string longitude, string latitude, int maxresults = 2, int radius = 50)
{
    var pointTxt = string.Format("POINT({0} {1})", longitude, latitude);
    var locations = (from s in locationEntityRepository.GetAll
                     orderby s.Coordinates.Distance(DbGeography.FromText(pointTxt))
                     where s.Coordinates.Distance(DbGeography.FromText(pointTxt)) / 1000  <= radius
                     select new Location
                     {
                         Id = s.Id,
                         Name = s.Name,
                         LocationType = s.LocationType,
                         Address = s.Address,
                         Longitude = s.Coordinates.Longitude.Value,
                         Latitude = s.Coordinates.Latitude.Value,
                         Distance = (s.Coordinates.Distance(DbGeography.FromText(pointTxt)).Value) / 1000
                      })
                    .Take(maxresults).ToList();

    // Bad bad bad. But EF/Linq doesn't let us do Includes when using subqueries... Go figure
    foreach (var location in locations)
    {
        location.Checkins = AutoMapper.
                            Mapper.
                            Map<List <Checkin>, List<LocationCheckinsJsonViewModel>>
                                (checkinRepository.GetCheckinsForLocation(location.Id).ToList());
    }

    // AutoMapper.Mapper.Map<Checkin, CheckinViewModel>(dbCheckin);
    var jsonBuilder = new JsonResponseBuilder();
    jsonBuilder.AddObject2Response("locations", locations);

    return jsonBuilder.JsonEnvelope;
}

我认为我需要澄清一些事情。 locationEntityRepository.GetAll 看起来像这样。

public IQueryable<LocationEntity> GetAll
{
    get { return _context.Locations; }
}

public IQueryable<LocationEntity> GetAllIncluding(params Expression<Func<LocationEntity, object>>[] includeProperties)
{
    IQueryable<LocationEntity> query = _context.Locations;
    foreach (var includeProperty in includeProperties) {
        query = query.Include(includeProperty);
    }

    // var tmp = query.ToList();

    return query;
}

现在代码真的闻起来很时髦。理想情况下,我希望能够使用 GetAllIncluding(c =&gt; c.Checkins) 而不是 GetAll 方法,并且能够使用 AutoMapper 在 LINQ 投影中进行映射。

我知道在使用子查询时,Include + LINQ/EF 返回 null 是设计使然。在 LINQ/EF 查询中使用 automapper 应该使用 Project().To&lt;&gt;,但在使用 .ForMember 时不起作用。

因此,挑战在于使代码更高效(在需要更改我的 JSON 结构时,更少的 SQL 和易于维护。请记住,我们在这里试图击败 node.js/MongoDB ;)我应该打扰,还是保持原样?

【问题讨论】:

  • 我很困惑问题是什么。如果您在您的locationEntityRepository.GetAll EF 之后的选择语句中添加.Include("Checkins") 将不允许它吗? (其中“Checkins”是您实体上导航属性的名称)我不得不承认我更喜欢基于方法的 Linq 语句并且没有/无法测试您上面的代码。但乍一看,这看起来像是一个语法问题。但即使没有,您也可以编写您请求的“GetAllIncluding()”方法,我只是认为这不是正确的方法。如果我误解了,请编辑您的问题以包含两个实体模型的代码。

标签: c# linq entity-framework-5 spatial automapper-2


【解决方案1】:

我使用存储库模式做了类似的事情。

    public IEnumerable<T> FindAll()
    {
        return _context.Set<T>().ToList();
    }

    public IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate)
    {
        return _context.Set<T>().Where(predicate);
    }

.Set 方法在哪里

    public IDbSet<TEntity> Set<TEntity>() where TEntity : class
    {
        return base.Set<TEntity>();
    }

这使您可以从数据库中获取某个类型的所有内容,或者只获取满足特定条件的所有类型。

这会给你留下一个对象列表,或者如果你使用 FirstOrDefault() 一个你可以随心所欲地映射的对象。

【讨论】:

    【解决方案2】:

    三件事。

    1. 包括System.Data.Entity 命名空间。 include 方法实际上是该命名空间引入的扩展。最好使用 EF 4.1 及更高版本中可用的 Lambda 重载,因此,您必须在要调用它的任何地方添加命名空间。
    2. 删除第一个查询末尾的 .ToList() ,因为它会立即执行违背包含目的的查询。

      using System.Data.Entity
      public IQueryable<LocationEntity> GetAll
      {
          get
          {
              return _context.Locations;
          }
      }
      

    这样,您可以在子查询中使用 Include

    【讨论】:

    • #1 没问题,但没有解决根本问题——它只是句法。 #2,命名空间问题将在编译时出现,与此无关,因为上面的示例已经编译。 #3,ToList() 不会违背包含的目的。它将执行查询包括子实体或集合。
    • 你是对的。更改为 DbSet 甚至会将您的实现与 Entity 框架联系起来,这在每种情况下都不理想。
    【解决方案3】:

    你为什么不使用这个:

    Context context=new Context();
    Public List<LocationEntity> GetAll()
    {
        return context.LocationEntities.Include("includeProperty").ToList();
    }
    

    上下文是这样的:

    public class Context: DbContext
        {
            public Context()
            {
                base.Configuration.LazyLoadingEnabled = false;
                base.Configuration.ProxyCreationEnabled = false;
                base.Configuration.ValidateOnSaveEnabled = false;
            }
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
                modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
            }
            public DbSet<LocationEntity> LocationEntities{ get; set; }
        }
    

    【讨论】: