2013-05-22 42 views
7

Tôi đang sử dụng Mã đầu tiên trong EF. Hãy nói rằng tôi có hai thực thể:Lọc thuộc tính điều hướng trong Mã EF Đầu tiên

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

public class Fruit 
{ 
    ... 

} 

My DbContext là một cái gì đó như thế này:

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); 
     } 
    } 
} 

tôi làm điều này để mỗi người dùng chỉ có thể xem các trang trại của mình, và tôi không cần phải gọi Trường hợp trên mỗi truy vấn đến db.

Bây giờ, tôi muốn lọc tất cả các loại trái cây từ một trang trại, tôi đã cố gắng này (trong lớp trang trại):

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

nhưng truy vấn tạo không bao gồm các khoản đâu, mà là rất quan trọng vì Tôi có hàng chục ngàn hàng và không hiệu quả để tải chúng và lọc chúng khi chúng là Đối tượng.

Tôi đọc mà tính lười nạp được điền lần đầu tiên chúng được truy vấn nhưng họ đọc tất cả các dữ liệu, không có bộ lọc có thể được áp dụng trừ khi bạn làm điều gì đó như thế này:

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

Nhưng tôi không thể làm điều đó, bởi vì Farm không có kiến ​​thức về DbContext (tôi không nghĩ nó nên (?)) mà còn với tôi nó chỉ mất toàn bộ mục đích sử dụng các thuộc tính điều hướng nếu tôi phải làm việc với tất cả dữ liệu chứ không phải chỉ thuộc về trang trại của tôi.

Vì vậy,

  1. tôi làm điều gì sai trái/làm giả định sai?
  2. Có cách nào để tôi có thể áp dụng bộ lọc cho thuộc tính điều hướng được tạo cho truy vấn thực không? (Tôi đang làm việc với nhiều dữ liệu)

Cảm ơn bạn đã đọc!

Trả lời

8

Thật không may, tôi nghĩ rằng bất kỳ cách tiếp cận nào bạn có thể thực hiện đều phải liên quan đến bối cảnh, chứ không phải chỉ thực thể. Như bạn đã thấy, bạn không thể lọc trực tiếp thuộc tính điều hướng vì đây là ICollection<T> và không phải là IQueryable<T>, vì vậy nó được tải cùng một lúc trước khi bạn có cơ hội áp dụng bất kỳ bộ lọc nào.

Một điều bạn có thể có thể làm là để tạo ra một tài sản unmapped trong Farm thực thể của bạn để giữ danh sách quả lọc:

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

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

Và sau đó, trong bối cảnh của bạn/kho, thêm một phương pháp để tải một Farm tổ chức và cư FilteredFruits với dữ liệu bạn muốn:

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); 

Điều đó sẽ cư myFarm.FilteredFruits chỉ với những bộ sưu tập được lọc, vì vậy bạn có thể sử dụng nó theo cách bạn muốn trong thực thể của bạn. Tuy nhiên, tôi chưa bao giờ thử phương pháp này, vì vậy có thể có những cạm bẫy mà tôi không nghĩ đến. Một nhược điểm chính là nó sẽ chỉ làm việc với Farm s bạn tải bằng cách sử dụng phương thức đó, và không phải với bất kỳ truy vấn LINQ chung nào bạn thực hiện trên bộ dữ liệu MyDbContext.Farms.Tất cả những gì đã nói, tôi nghĩ thực tế là bạn đang cố gắng làm điều này có thể là dấu hiệu cho thấy bạn đang đặt quá nhiều logic nghiệp vụ vào lớp thực thể của bạn, khi thực sự nó có thể thuộc về một lớp khác tốt hơn. Rất nhiều thời gian, tốt hơn là xử lý các thực thể về cơ bản như là các vật chứa cho các nội dung của một bản ghi cơ sở dữ liệu và để tất cả việc lọc/xử lý cho kho lưu trữ hoặc bất cứ nơi nào mà logic nghiệp vụ/hiển thị của bạn tồn tại. Tôi không chắc bạn đang sử dụng loại ứng dụng nào, vì vậy tôi không thể đưa ra lời khuyên cụ thể nào, nhưng đó là điều cần suy nghĩ.

Một cách tiếp cận rất phổ biến nếu bạn quyết định chuyển những điều trên thực thể Farm là sử dụng chiếu:

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

... và sau đó sử dụng các đối tượng ẩn danh tạo ra cho bất cứ điều gì bạn muốn làm, chứ không phải là cố gắng để thêm dữ liệu bổ sung vào chính các tổ chức Farm.

+0

Cảm ơn Jeremy, tôi quyết định làm theo lời khuyên của bạn và để lại trách nhiệm lọc/xử lý trong lớp ngữ cảnh của tôi . Nó có ý nghĩa bởi vì tôi chỉ cần lọc cho một trong những thực thể của tôi, nhưng nó sẽ là cồng kềnh nếu tôi cần nó cho một số thực thể, bạn có nghĩ vậy không? Ngữ cảnh sẽ được điền bằng các phương thức để truy vấn và điền các thực thể. Tôi không nghĩ rằng nó phá vỡ nguyên tắc trách nhiệm duy nhất nhưng nó nghe có vẻ kỳ lạ, đúng không? – edpaez

1

Chỉ cần hình dung tôi sẽ thêm một giải pháp khác vào việc này đã dành chút thời gian cố gắng gắn thêm các nguyên tắc DDD để mã hóa các mô hình đầu tiên. Sau khi tìm kiếm xung quanh một thời gian, tôi tìm thấy một giải pháp giống như giải pháp dưới đây hoạt động cho tôi.

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; } 

} 

Ý tưởng là bộ sưu tập trái cây của trang trại không thể truy cập trực tiếp mà thay vào đó được tiếp xúc thông qua thuộc tính lọc trước. Sự thỏa hiệp ở đây là biểu thức tĩnh được yêu cầu để có thể giải quyết việc thu thập trái cây khi thiết lập ánh xạ. Tôi đã bắt đầu sử dụng phương pháp này trên một số dự án mà tôi muốn kiểm soát quyền truy cập vào một bộ sưu tập các đối tượng con.

+0

Có bất kỳ ý nghĩa nào xảy ra với giải pháp này không? – Oswin

2

Lazy loading không hỗ trợ lọc; sử dụng filtered explicit loading thay thế:

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

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

Phương pháp tải rõ ràng yêu cầu hai chuyến đi vòng đến cơ sở dữ liệu, một cho chủ và một cho chi tiết. Nếu điều quan trọng là phải tuân theo một truy vấn, hãy sử dụng phép chiếu thay thế:

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 luôn gắn kết các thuộc tính điều hướng với thực thể được tải. Điều này có nghĩa là farm.Fruit sẽ chứa cùng một bộ sưu tập được lọc là thuộc tính Fruit trong loại ẩn danh. (Chỉ cần đảm bảo rằng bạn chưa tải vào ngữ cảnh bất kỳ thực thể Trái cây nào cần được lọc ra, như được mô tả trong Use Projections and a Repository to Fake a Filtered Eager Load.)

Các vấn đề liên quan