2013-05-22 54 views
7

我在EF中使用代碼優先。比方說,我有兩個實體:在EF代碼優先過濾導航屬性

public class Farm 
{ 
    .... 
    public virtual ICollection<Fruit> Fruits {get; set;} 
} 

public class Fruit 
{ 
    ... 

} 

我的DbContext是這樣的:

public class MyDbContext : DbSet 
{ 
    .... 
    private DbSet<Farm> FarmSet{get; set;} 

    public IQueryable<Farm> Farms 
    { 
     get 
     { 
      return (from farm in FarmSet where farm.owner == myowner select farm); 
     } 
    } 
} 

我這樣做,這樣每個用戶只能看到他的農場,和我沒有打電話給在每個查詢到db的地方。現在

,我想所有的水果從一個農場進行篩選,我想這(在農場類):

from fruit in Fruits where fruit .... select fruit 

,但生成的查詢不包括where子句,這是因爲很重要我有成千上萬的行,並且效率不高,無法將它們全部加載,並在它們是對象時進行過濾。

,我讀了延遲加載性能得到填補他們正在訪問的第一次,但他們閱讀所有的數據,沒有過濾器可以,除非你做這樣的事情可應用於:

from fruits in db.Fruits where fruit .... select fruit 

但我不能這樣做,因爲農場沒有DbContext的知識(我不認爲它應該(?)),但對我來說,它只是失去了使用導航屬性的全部目的,如果我必須處理所有的數據而不只是一個屬於我的農場。

所以,

  1. 我在做什麼錯事/作出錯誤的假設?
  2. 有沒有什麼辦法可以將過濾器應用於導航屬性,並將其生成爲真正的查詢? (我正在處理大量數據)

謝謝您的閱讀!

回答

8

不幸的是,我認爲您可能採取的任何方法都必須涉及擺弄上下文,而不僅僅是實體。正如你所看到的,你不能直接過濾導航屬性,因爲它是ICollection<T>而不是IQueryable<T>,因此在你有機會應用任何過濾器之前它會被一次加載。

有一件事你可能做的是在你的Farm實體持有過濾水果列表來創建一個映射的屬性:

public class Farm 
{ 
    .... 
    public virtual ICollection<Fruit> Fruits { get; set; } 

    [NotMapped] 
    public IList<Fruit> FilteredFruits { get; set; } 
} 

,然後在您的上下文/存儲庫,添加一個方法來加載Farm與數據實體和填充FilteredFruits你想:

public class MyDbContext : DbContext 
{ 
    ....  

    public Farm LoadFarmById(int id) 
    { 
    Farm farm = this.Farms.Where(f => f.Id == id).Single(); // or whatever 

    farm.FilteredFruits = this.Entry(farm) 
           .Collection(f => f.Fruits) 
           .Query() 
           .Where(....) 
           .ToList(); 

    return farm; 
    } 
} 

... 

var myFarm = myContext.LoadFarmById(1234); 

這應該填充myFarm.FilteredFruits只用過濾收集,所以你可以使用它,你希望之路的你的實體。但是,我自己從來沒有嘗試過這種方法,因此可能會有我沒有想到的缺陷。一個主要的缺點是,它只適用於使用該方法加載的Farm,而不適用於您在MyDbContext.Farms數據集上執行的任何常規LINQ查詢。所有這一切說,我認爲你試圖這樣做的事實可能是一個跡象,表明你將太多的業務邏輯放入實體類中,而實際上它可能屬於更好的不同層次。很多時候,最好將實體基本上視爲數據庫記錄內容的容器,並將所有過濾/處理保留到存儲庫或任何業務/顯示邏輯所在的位置。我不確定你在做什麼樣的應用程序,所以我不能真正提供任何具體的建議,但這是需要考慮的問題。

一個非常常見的方法,如果你決定了Farm實體是使用投影搬東西:

var results = (from farm in myContext.Farms 
       where .... 
       select new { 
       Farm = farm, 
       FilteredFruits = myContext.Fruits.Where(f => f.FarmId == farm.Id && ...).ToList() 
       }).ToList(); 

...,然後不管你想要做的使用產生匿名對象,而不是試圖向Farm實體本身添加額外的數據。

+0

謝謝Jeremy,我決定遵循你的建議,並在我的上下文類中留下過濾/處理責任。這是有道理的,因爲我只需要爲我的某個實體進行過濾,但如果我需要爲多個實體使用它,這會很麻煩,您不覺得嗎?上下文將填充查詢和填充實體的方法。我不認爲這打破了單一責任原則,但這聽起來很奇怪,不是嗎? – edpaez

1

剛纔發現我會爲此添加另一個解決方案,花了一些時間試圖追加DDD原則來編寫第一個模型。在搜索了一段時間之後,我找到了一個像下面這樣的解決方案,適用於我。

public class FruitFarmContext : DbContext 
{ 
    public DbSet<Farm> Farms { get; set; } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Entity<Farm>().HasMany(Farm.FruitsExpression).WithMany(); 
    } 
} 

public class Farm 
{ 
    public int Id { get; set; } 
    protected virtual ICollection<Fruit> Fruits { get; set; } 
    public static Expression<Func<Farm, ICollection<Fruit>>> FruitsExpression = x => x.Fruits; 

    public IEnumerable<Fruit> FilteredFruits 
    { 
     get 
     { 
      //Apply any filter you want here on the fruits collection 
      return Fruits.Where(x => true); 
     } 
    } 
} 

public class Fruit 
{ 
    public int Id { get; set; } 

} 

想法是,農場水果收集不是直接訪問,而是通過預過濾它的屬性暴露。 這裏的妥協是設置映射時需要能夠解決水果收集的靜態表達式。 我已經開始在一些想要控制對象子集合的訪問的項目中使用這種方法。

+0

這個解決方案是否有任何含義? – Oswin

2

Lazy loading不支持過濾;使用filtered explicit loading代替:

Farm farm = dbContext.Farms.Where(farm => farm.Owner == someOwner).Single(); 

dbContext.Entry(farm).Collection(farm => farm.Fruits).Query() 
    .Where(fruit => fruit.IsRipe).Load(); 

顯式裝載方法需要兩次往返到數據庫,一個是主,一個是細節。如果堅持單個查詢很重要,請使用投影代替:

Farm farm = (
    from farm in dbContext.Farms 
    where farm.Owner == someOwner 
    select new { 
     Farm = farm, 
     Fruit = dbContext.Fruit.Where(fruit => fruit.IsRipe) // Causes Farm.Fruit to be eager loaded 
    }).Single().Farm; 

EF始終將導航屬性綁定到其加載的實體。這意味着farm.Fruit將包含與匿名類型中的Fruit屬性相同的已過濾集合。 (只要確保你沒有加載任何應該過濾掉的Fruit實體,如Use Projections and a Repository to Fake a Filtered Eager Load所述。)