如何使用Linq To SQL查询层次结构?

时间:2009-01-20 21:37:28

标签: c# linq-to-sql hierarchy

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

国家/地区 - >地区 - >城市 - >邮编

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

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

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

以下是我到目前为止的一些片段:

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:Method'System.Linq.IQueryable`1 [Beeline.EducationCompass.Model.Region] GetRegions(Int32)'没有支持的SQL转换。

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

2 个答案:

答案 0 :(得分:2)

您将要使用linq设计器来设置对象之间的关系。通过创建属性,这可以让您在加入后加入后无法编写连接。

    国家与地区之间的
  • 区域与城市之间的
  • 国家及其本地化之间的
  • 区域及其本地化之间的

您将要使用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();
  }
}

答案 1 :(得分:1)

这是一段棘手的代码,如果其他人没有相关技能,我就不会回答这个问题,但是因为你没有回复......

我可以告诉你错误信息的含义。这意味着函数GetRegions无法通过linq to sql provider转换为sql。一些内置函数可以是,因为提供者理解它们,这里是list。否则,您可以提供翻译,请参阅here

在你的情况下你需要'内联'这个查询的逻辑,逻辑不会跨越函数调用的边界,因为你正在处理一个表达式树,sql server无法回调你的GetRegions方法。

至于确切的方法,你必须要去,我现在没有时间强迫你。 (除非别人有时间和技巧?)

祝你好运。