【问题标题】:How can I query hierarchies with LinqToSQL?如何使用 LinqToSQL 查询层次结构?
【发布时间】:2009-01-20 21:37:28
【问题描述】:

我想用 LinqToSql 查询一个层次结构:

国家 -> 地区 -> 城市 -> 邮政编码

每个实体都包含对其父级的引用(例如 Region.Country)和其子级的集合(例如 Region.Cities)。

我想急切加载每个实体的父级以及国家和地区,但延迟加载城市和邮政编码。

为了使事情复杂化,每个实体在被投影到模型中之前都要进行本地化。所以 Country.Name 会根据语言而变化。

这是我目前所拥有的一些 sn-ps:

public IQueryable<Country> ListCountries()
{
  return ProjectCountry(dataContext.GetTable<ec_Country>());
}

private IQueryable<Country> ProjectCountry(IQueryable<ec_Country> query)
{
  var result = from country in query
  join localized in dataContext.GetTable<ec_CountryLocalization>() on country.CountryID equals localized.CountryID
  let regions = GetRegions(country.CountryID)
  where localized.StatusID == 4 && localized.WebSiteID == this.webSiteID
  select new Country(country.CountryID) {
    CreatedDate = country.CreatedDate,
    IsDeleted = country.IsDeleted,
    IsoCode = country.IsoCode,
    Name = country.Name,
    Regions = new LazyList<Region>(regions),
    Text = localized.Text,
    Title = localized.Title,
    UrlKey = country.UrlKey
  };

  return result;
}

private IQueryable<Region> GetRegions(Int32 countryID)
{
  var query = from r in dataContext.GetTable<ec_Region>()
  where r.CountryID == countryID
  orderby r.Name
  select r;

  return ProjectRegion(query);
}

private IQueryable<Region> ProjectRegion(IQueryable<ec_Region> query)
{
  var result = from region in query
  join localized in dataContext.GetTable<ec_RegionLocalization>() on region.RegionID equals localized.RegionID
  join country in ListCountries() on region.CountryID equals country.CountryID
  let cities = GetCities(region.RegionID)
  select new Region(region.RegionID) {
    Cities = new LazyList<City>(cities),
    Country = country,
    CountryID = region.CountryID,
    CreatedDate = region.CreatedDate,
    IsDeleted = region.IsDeleted,
    IsoCode = region.IsoCode,
    Name = region.Name,
    Text = localized.Text,
    Title = localized.Title,
    UrlKey = region.UrlKey
  };

  return result;
}

...等等

[TestMethod]
public void DataProvider_Correctly_Projects_Country_Spike()
{
  // Act
  Country country = dataProvider.GetCountry(1);

  // Assert
  Assert.IsNotNull(country);
  Assert.IsFalse(String.IsNullOrEmpty(country.Description));
  Assert.IsTrue(country.Regions.Count > 0);
}

测试失败:

System.NotSupportedException:方法 'System.Linq.IQueryable`1[Beeline.EducationCompass.Model.Region] GetRegions(Int32)' 不支持对 SQL 的转换。

你会建议我怎么做?如果层次结构的每个级别都在同一个表中而不是单独的表中,会更简单(或可能)吗?

【问题讨论】:

    标签: c# linq-to-sql hierarchy


    【解决方案1】:

    您将希望使用 linq 设计器来设置对象之间的关系。通过创建属性,这使您无需在 join after join 后编写 join。

    • 一个国家和它的地区之间
    • 一个地区和它的城市之间
    • 在国家和地区之间
    • 区域与其本地化之间

    您将需要使用ToList 来分隔您打算转换为 SQL 的那些操作,以及您打算在本地代码中完成的那些操作。如果您不这样做,您将继续看到那些“无法将您的方法转换为 SQL”的异常。

    在某些情况下,您还想使用DataLoadOptions 急切地加载这些属性。这是我的尝试。

    DataLoadOptions dlo = new DataLoadOptions();
    //bring in the Regions for each Country
    dlo.LoadWith<ec_Country>(c => c.Regions);
    //bring in the localizations
    dlo.AssociateWith<ec_Country>(c => c.Localizations
      .Where(loc => loc.StatusID == 4 && loc.WebSiteID == this.webSiteID)
    );
    dlo.AssociateWith<ec_Region>(r => r.Localizations);
    
    //set up the dataloadoptions to eagerly load the above.
    dataContext.DataLoadOptions = dlo;
    
    //Pull countries and all eagerly loaded data into memory.
    List<ec_Country> queryResult = query.ToList();
    
    //further map these data types to business types
    List<Country> result = queryResult
      .Select(c => ToCountry(c))
      .ToList();
    

    public Country ToCountry(ec_Country c)
    {
      return new Country()
      {
        Name = c.Name,
        Text = c.Localizations.Single().Text,
        Regions = c.Regions().Select(r => ToRegion(r)).ToList()
      }
    }
    
    public Region ToRegion(ec_Region r)
    {
      return new Region()
      {
        Name = r.Name,
        Text = r.Localizations.Single().Text,
        Cities = r.Cities.Select(city => ToCity(city)).ToLazyList();
      }
    }
    

    【讨论】:

    • 谢谢!这看起来很有希望。我明天试试看。
    • 这种方法的变体有效。为了避免让 LinqToSql 每个区域发出一个查询来获取城市,我必须同时创建一个 ToRegion(ec_Region dto) 和一个 ToRegionShallow(ec_Region dto)。浅层版本创建模型但不填充子集合(在 Region 的情况下为 Cities)。
    • 我认为如果我的数据库使用 Joe Celko 的嵌套集方法,则可能可以在单个查询中执行此操作。从理论上讲,只需要通过一次结果集就可以实例化层次结构。其他人对此有什么想法吗?
    【解决方案2】:

    这是一段粘性代码,如果其他人有相关技能,我不会回答这个问题,但既然你没有回应......

    我可以告诉你错误消息的含义。这意味着函数 GetRegions 不能被 linq to sql 提供程序转换为 sql。一些内置函数可以,因为提供者理解它们,这里是一个list。否则你可以提供翻译见here。

    在您的情况下,您需要“内联”此查询的逻辑,该逻辑不会跨越函数调用的边界,因为您正在处理表达式树,sql server 无法回调到您的GetRegions 方法。

    至于具体的做法,你得去试一试,我现在没时间答应你。 (除非其他人有时间和技能?)

    祝你好运。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-02
      • 2020-04-17
      • 2015-10-15
      • 1970-01-01
      • 1970-01-01
      • 2015-12-18
      • 2016-01-06
      • 2012-08-03
      相关资源
      最近更新 更多