2009-02-13 28 views
74

Điều này sẽ áp dụng chủ yếu cho một ứng dụng asp.net nơi dữ liệu không được truy cập thông qua soa. Có nghĩa là bạn có quyền truy cập vào các đối tượng được tải từ khung công tác, chứ không phải là Chuyển đối tượng, mặc dù một số đề xuất vẫn được áp dụng.Thực tiễn thiết kế tốt khi làm việc với Entity Framework

Đây là bài đăng của cộng đồng, vì vậy hãy thêm vào bài đăng khi bạn thấy phù hợp.

Áp dụng cho: Entity Framework 1.0 được phân phối bằng Visual Studio 2008 sp1.

Tại sao chọn EF ngay từ đầu?

Xem xét nó là một công nghệ trẻ với rất nhiều vấn đề (xem bên dưới), có thể sẽ khó bán trên băng tần EF cho dự án của bạn. Tuy nhiên, đó là công nghệ mà Microsoft đang thúc đẩy (với chi phí của Linq2Sql, đó là một tập hợp con của EF). Ngoài ra, bạn có thể không hài lòng với NHibernate hoặc các giải pháp khác ngoài kia. Bất kể lý do gì, có những người ngoài kia (kể cả tôi) làm việc với EF và cuộc sống không tệ lắm.

EF và thừa kế

Đối tượng lớn đầu tiên là thừa kế. EF hỗ trợ ánh xạ cho các lớp kế thừa được lưu giữ theo 2 cách: bảng trên mỗi lớp và bảng phân cấp. Mô hình dễ dàng và không có vấn đề lập trình với phần đó.

(Điều sau đây áp dụng cho bảng trên mỗi mô hình lớp vì tôi không có kinh nghiệm với bảng trên hệ thống phân cấp, dù sao, bị giới hạn.) Vấn đề thực sự xảy ra khi bạn đang cố chạy truy vấn bao gồm một hoặc nhiều đối tượng đó là một phần của cây thừa kế: sql được tạo ra là vô cùng khủng khiếp, mất một thời gian dài để được phân tích bởi EF và mất nhiều thời gian để thực thi. Đây là một stopper hiển thị thực sự. Đủ rằng EF có lẽ không nên được sử dụng với thừa kế hoặc ít nhất có thể.

Đây là ví dụ về mức độ tồi tệ của nó. Mô hình EF của tôi có ~ 30 lớp, ~ 10 trong số đó là một phần của cây thừa kế. Khi chạy một truy vấn để lấy một mục từ lớp Base, một cái gì đó đơn giản như Base.Get (id), SQL được sinh ra là hơn 50.000 ký tự. Sau đó, khi bạn đang cố gắng để trả về một số Hiệp hội, nó thoái hóa nhiều hơn, đi xa như ném các ngoại lệ SQL về việc không thể truy vấn nhiều hơn 256 bảng cùng một lúc. Ok, điều này là xấu, khái niệm EF là để cho phép bạn tạo cấu trúc đối tượng của bạn mà không cần (hoặc với càng ít càng tốt) xem xét việc thực hiện cơ sở dữ liệu thực tế của bảng của bạn. Nó hoàn toàn thất bại ở đây.

Vì vậy, đề xuất? Tránh thừa kế nếu bạn có thể, hiệu suất sẽ tốt hơn rất nhiều. Sử dụng nó một cách tiết kiệm nơi bạn phải làm. Theo tôi, điều này làm cho EF trở thành một công cụ thế hệ sql được vinh danh để truy vấn, nhưng vẫn có những lợi thế để sử dụng nó. Và cách để thực hiện cơ chế tương tự như thừa kế.

Bỏ qua thừa kế với giao diện

Điều đầu tiên phải biết với cố gắng để có được một số loại thừa kế đi với EF là bạn không thể gán một lớp phi EF-mô hình một lớp cơ sở. Thậm chí không thử nó, nó sẽ bị ghi đè bởi người lập mô hình. Vậy lam gi?

Bạn có thể sử dụng giao diện để thực thi các lớp đó thực hiện một số chức năng.Ví dụ ở đây là một giao diện IEntity cho phép bạn định nghĩa các liên kết giữa các thực thể EF mà bạn không biết tại thời điểm thiết kế loại thực thể sẽ là gì.

public enum EntityTypes{ Unknown = -1, Dog = 0, Cat } 
public interface IEntity 
{ 
    int EntityID { get; } 
    string Name { get; } 
    Type EntityType { get; } 
} 
public partial class Dog : IEntity 
{ 
    // implement EntityID and Name which could actually be fields 
    // from your EF model 
    Type EntityType{ get{ return EntityTypes.Dog; } } 
} 

Sử dụng IEntity này, sau đó bạn có thể làm việc với các hiệp hội không xác định trong các lớp khác

// lets take a class that you defined in your model. 
// that class has a mapping to the columns: PetID, PetType 
public partial class Person 
{ 
    public IEntity GetPet() 
    { 
     return IEntityController.Get(PetID,PetType); 
    } 
} 

mà làm cho sử dụng một số chức năng mở rộng:

public class IEntityController 
{ 
    static public IEntity Get(int id, EntityTypes type) 
    { 
     switch (type) 
     { 
      case EntityTypes.Dog: return Dog.Get(id); 
      case EntityTypes.Cat: return Cat.Get(id); 
      default: throw new Exception("Invalid EntityType"); 
     } 
    } 
} 

Không như gọn gàng là có thừa kế đơn giản , đặc biệt là xem xét bạn phải lưu trữ PetType trong một trường cơ sở dữ liệu bổ sung, nhưng xem xét hiệu suất đạt được, tôi sẽ không nhìn lại.

Nó cũng không thể mô hình mối quan hệ nhiều - nhiều, nhưng với việc sử dụng sáng tạo 'Liên minh', nó có thể được thực hiện để hoạt động. Cuối cùng, nó tạo ra các effet bên của tải dữ liệu trong một tài sản/chức năng của đối tượng, mà bạn cần phải cẩn thận về. Việc sử dụng quy ước đặt tên rõ ràng như GetXYZ() sẽ giúp ích cho điều đó.

Biên soạn Queries

hiệu suất Entity Framework là không tốt như truy cập cơ sở dữ liệu trực tiếp với ADO (rõ ràng) hoặc Linq2SQL. Tuy nhiên, có nhiều cách để cải thiện nó, một trong số đó là biên dịch các truy vấn của bạn. Hiệu năng của một truy vấn được biên dịch tương tự như Linq2Sql.

Truy vấn được biên dịch là gì? Nó chỉ đơn giản là một truy vấn mà bạn nói cho khuôn khổ để giữ cho cây được phân tích cú pháp trong bộ nhớ để nó không cần phải được tái tạo vào lần sau khi bạn chạy nó. Vì vậy, lần chạy tiếp theo, bạn sẽ tiết kiệm thời gian cần để phân tích cú pháp cây. Không giảm giá vì nó là một hoạt động rất tốn kém thậm chí còn tồi tệ hơn với các truy vấn phức tạp hơn.

Có 2 cách để biên dịch một truy vấn: tạo một ObjectQuery với EntitySQL và sử dụng hàm CompiledQuery.Compile(). (Lưu ý rằng bằng cách sử dụng EntityDataSource trong trang của bạn, thực tế bạn sẽ sử dụng ObjectQuery với EntitySQL, để được biên dịch và lưu trữ).

Ở một bên tại đây trong trường hợp bạn không biết EntitySQL là gì. Nó là một cách dựa trên chuỗi viết các truy vấn đối với EF. Đây là một ví dụ: "chọn con chó có giá trị từ Entities.DogSet làm con chó nơi dog.ID = @ID". Cú pháp khá giống với cú pháp SQL. Bạn cũng có thể thực hiện thao tác đối tượng khá phức tạp, được giải thích rõ ràng ở đây [1].

Ok, vì vậy đây là làm thế nào để làm điều đó bằng ObjectQuery <>

 string query = "select value dog " + 
         "from Entities.DogSet as dog " + 
         "where dog.ID = @ID"; 

     ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance)); 
     oQuery.Parameters.Add(new ObjectParameter("ID", id)); 
     oQuery.EnablePlanCaching = true; 
     return oQuery.FirstOrDefault(); 

Lần đầu tiên bạn chạy truy vấn này, khung sẽ tạo ra cây biểu hiện và giữ nó trong bộ nhớ. Vì vậy, lần sau nó được thực hiện, bạn sẽ tiết kiệm được một bước tốn kém. Trong ví dụ đó, EnablePlanCaching = true, không cần thiết vì đó là tùy chọn mặc định.

Cách khác để biên dịch truy vấn để sử dụng sau này là phương thức CompiledQuery.Compile.Này sử dụng một đại biểu:

static readonly Func<Entities, int, Dog> query_GetDog = 
     CompiledQuery.Compile<Entities, int, Dog>((ctx, id) => 
      ctx.DogSet.FirstOrDefault(it => it.ID == id)); 

hoặc sử dụng LINQ

static readonly Func<Entities, int, Dog> query_GetDog = 
     CompiledQuery.Compile<Entities, int, Dog>((ctx, id) => 
      (from dog in ctx.DogSet where dog.ID == id select dog).FirstOrDefault()); 

gọi truy vấn:

query_GetDog.Invoke(YourContext, id); 

Ưu điểm của CompiledQuery là cú pháp của truy vấn của bạn sẽ được kiểm tra tại thời gian biên dịch , với EntitySQL thì không. Tuy nhiên, có cân nhắc khác ...

Bao gồm

phép nói rằng bạn muốn có dữ liệu cho chủ sở hữu con chó được trả về bởi các truy vấn để tránh đưa ra 2 cuộc gọi đến các cơ sở dữ liệu. Dễ làm, đúng không?

EntitySQL

 string query = "select value dog " + 
         "from Entities.DogSet as dog " + 
         "where dog.ID = @ID"; 
     ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance)).Include("Owner"); 
     oQuery.Parameters.Add(new ObjectParameter("ID", id)); 
     oQuery.EnablePlanCaching = true; 
     return oQuery.FirstOrDefault(); 

CompiledQuery

static readonly Func<Entities, int, Dog> query_GetDog = 
     CompiledQuery.Compile<Entities, int, Dog>((ctx, id) => 
      (from dog in ctx.DogSet.Include("Owner") where dog.ID == id select dog).FirstOrDefault()); 

Bây giờ, nếu bạn muốn có Bao gồm parametrized? Ý tôi là bạn muốn có một hàm Get() duy nhất được gọi từ các trang khác nhau quan tâm đến các mối quan hệ khác nhau cho chú chó. Một người quan tâm đến Chủ sở hữu, một điều khác về FavotireToy của anh ấy và một thứ khác về FavotireToy của anh ấy và vân vân. Về cơ bản, bạn muốn nói truy vấn mà các liên kết tải.

Nó rất dễ dàng để làm với EntitySQL

public Dog Get(int id, string include) 
{ 
     string query = "select value dog " + 
         "from Entities.DogSet as dog " + 
         "where dog.ID = @ID"; 

     ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance)) 
    .IncludeMany(include); 
     oQuery.Parameters.Add(new ObjectParameter("ID", id)); 
     oQuery.EnablePlanCaching = true; 
     return oQuery.FirstOrDefault(); 
} 

Các bao gồm đơn giản là sử dụng các chuỗi thông qua. Vừa đủ dễ. Lưu ý rằng có thể cải thiện hàm Include (string) (chỉ chấp nhận một đường dẫn duy nhất) với một chuỗi IncludeMany (chuỗi) cho phép bạn chuyển một chuỗi các liên kết được phân tách bằng dấu phẩy để tải. Xem thêm trong phần mở rộng cho chức năng này.

Nếu chúng ta cố gắng làm điều đó với CompiledQuery Tuy nhiên, chúng tôi chạy vào nhiều vấn đề:

Rõ ràng

static readonly Func<Entities, int, string, Dog> query_GetDog = 
     CompiledQuery.Compile<Entities, int, string, Dog>((ctx, id, include) => 
      (from dog in ctx.DogSet.Include(include) where dog.ID == id select dog).FirstOrDefault()); 

sẽ nghẹt thở khi được gọi với:

query_GetDog.Invoke(YourContext, id, "Owner,FavoriteFood"); 

Bởi vì, như mentionned ở trên, Include() chỉ muốn nhìn thấy một đường dẫn duy nhất trong chuỗi và ở đây chúng tôi đang cho nó 2: "Chủ sở hữu" và "FavoriteFood" (mà không được nhầm lẫn với "Owner.FavoriteFood"!).

Sau đó, hãy sử dụng IncludeMany(), mà là một chức năng mở rộng

static readonly Func<Entities, int, string, Dog> query_GetDog = 
     CompiledQuery.Compile<Entities, int, string, Dog>((ctx, id, include) => 
      (from dog in ctx.DogSet.IncludeMany(include) where dog.ID == id select dog).FirstOrDefault()); 

sai một lần nữa, lần này nó là do EF không thể phân tích IncludeMany vì nó không phải là một phần của chức năng đó là nhận: nó là một phần mở rộng.

Ok, vì vậy bạn muốn chuyển một số đường dẫn tùy ý đến hàm của bạn và lệnh Bao gồm() chỉ mất một con đường duy nhất. Phải làm gì? Bạn có thể quyết định rằng bạn sẽ không bao giờ cần nhiều hơn, bao gồm 20 Bao gồm, và vượt qua từng chuỗi riêng biệt trong một cấu trúc để CompiledQuery.Nhưng giờ đây, truy vấn trông giống như sau:

from dog in ctx.DogSet.Include(include1).Include(include2).Include(include3) 
.Include(include4).Include(include5).Include(include6) 
.[...].Include(include19).Include(include20) where dog.ID == id select dog 

cũng thật khủng khiếp. Ok, sau đó, nhưng chờ một chút. Chúng ta không thể trả về một ObjectQuery <> với CompiledQuery? Sau đó thiết lập các bao gồm trên đó? Vâng, đó là những gì tôi đã có thể nghĩ như vậy là tốt:

static readonly Func<Entities, int, ObjectQuery<Dog>> query_GetDog = 
     CompiledQuery.Compile<Entities, int, string, ObjectQuery<Dog>>((ctx, id) => 
      (ObjectQuery<Dog>)(from dog in ctx.DogSet where dog.ID == id select dog)); 
public Dog GetDog(int id, string include) 
{ 
    ObjectQuery<Dog> oQuery = query_GetDog(id); 
    oQuery = oQuery.IncludeMany(include); 
    return oQuery.FirstOrDefault; 
} 

Đó nên đã làm việc, ngoại trừ việc khi bạn gọi IncludeMany (hoặc Bao gồm, ở đâu, OrderBy ...), bạn làm mất hiệu lực truy vấn biên soạn cache vì nó là một cái mới hoàn toàn ngay bây giờ! Vì vậy, cây biểu hiện cần phải được reparsed và bạn nhận được rằng hiệu suất hit một lần nữa.

Vì vậy, giải pháp là gì? Bạn chỉ đơn giản là không thể sử dụng CompiledQueries với parametrized Bao gồm. Sử dụng EntitySQL để thay thế. Điều này không có nghĩa là không có sử dụng cho CompiledQueries. Nó là tuyệt vời cho các truy vấn được bản địa hóa mà sẽ luôn được gọi trong cùng một ngữ cảnh. Lý tưởng nhất CompiledQuery nên luôn luôn được sử dụng bởi vì cú pháp được kiểm tra tại thời gian biên dịch, nhưng do giới hạn, đó là không thể.

Ví dụ sử dụng sẽ là: bạn có thể muốn có một trang truy vấn hai con chó có cùng một món ăn yêu thích, hơi hẹp cho chức năng BusinessLayer, vì vậy bạn hãy đặt nó vào trang của bạn và biết chính xác loại bao gồm là bắt buộc.

Đi qua hơn 3 tham số cho một CompiledQuery

Func được giới hạn trong 5 thông số, trong đó người cuối cùng là kiểu trả về và một trong những đầu tiên là thực thể của bạn phản đối từ các mô hình. Vì vậy, điều đó khiến bạn có 3 tham số. Một sự thương hại, nhưng nó có thể được cải thiện rất dễ dàng.

public struct MyParams 
{ 
    public string param1; 
    public int param2; 
    public DateTime param3; 
} 

    static readonly Func<Entities, MyParams, IEnumerable<Dog>> query_GetDog = 
     CompiledQuery.Compile<Entities, MyParams, IEnumerable<Dog>>((ctx, myParams) => 
      from dog in ctx.DogSet where dog.Age == myParams.param2 && dog.Name == myParams.param1 and dog.BirthDate > myParams.param3 select dog); 

public List<Dog> GetSomeDogs(int age, string Name, DateTime birthDate) 
{ 
    MyParams myParams = new MyParams(); 
    myParams.param1 = name; 
    myParams.param2 = age; 
    myParams.param3 = birthDate; 
    return query_GetDog(YourContext,myParams).ToList(); 
} 

loại Return (điều này không áp dụng đối với các truy vấn EntitySQL như họ không được biên dịch cùng một lúc trong khi thực hiện như phương pháp CompiledQuery)

Làm việc với LINQ, bạn thường không buộc việc thực hiện truy vấn cho đến thời điểm cuối cùng, trong trường hợp một số chức năng khác ở hạ lưu muốn thay đổi truy vấn theo một cách nào đó:

static readonly Func<Entities, int, string, IEnumerable<Dog>> query_GetDog = 
     CompiledQuery.Compile<Entities, int, string, IEnumerable<Dog>>((ctx, age, name) => 
      from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog); 

public IEnumerable<Dog> GetSomeDogs(int age, string name) 
{ 
    return query_GetDog(YourContext,age,name); 
} 
public void DataBindStuff() 
{ 
    IEnumerable<Dog> dogs = GetSomeDogs(4,"Bud"); 
    // but I want the dogs ordered by BirthDate 
    gridView.DataSource = dogs.OrderBy(it => it.BirthDate); 

} 

Điều gì sẽ xảy ra ở đây? Bằng cách vẫn chơi với ObjectQuery gốc (đó là kiểu trả về thực tế của câu lệnh Linq, thực hiện IEnumerable), nó sẽ vô hiệu hóa truy vấn được biên dịch và có hiệu lực để phân tích lại. Vì vậy, quy tắc chung là trả lại một Danh sách <> của các đối tượng thay thế.

static readonly Func<Entities, int, string, IEnumerable<Dog>> query_GetDog = 
     CompiledQuery.Compile<Entities, int, string, IEnumerable<Dog>>((ctx, age, name) => 
      from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog); 

public List<Dog> GetSomeDogs(int age, string name) 
{ 
    return query_GetDog(YourContext,age,name).ToList(); //<== change here 
} 
public void DataBindStuff() 
{ 
    List<Dog> dogs = GetSomeDogs(4,"Bud"); 
    // but I want the dogs ordered by BirthDate 
    gridView.DataSource = dogs.OrderBy(it => it.BirthDate); 

} 

Khi bạn gọi ToList(), truy vấn được thực hiện theo truy vấn được biên dịch và sau đó, OrderBy được thực thi dựa trên các đối tượng trong bộ nhớ. Nó có thể chậm hơn một chút, nhưng tôi thậm chí không chắc chắn. Một điều chắc chắn là bạn không phải lo lắng về việc xử lý sai ObjectQuery và làm mất hiệu lực kế hoạch truy vấn được biên dịch.

Một lần nữa, đó không phải là tuyên bố về chăn. ToList() là một thủ thuật lập trình phòng thủ, nhưng nếu bạn có một lý do hợp lệ không sử dụng ToList(), hãy tiếp tục. Có rất nhiều trường hợp bạn muốn tinh chỉnh truy vấn trước khi thực hiện nó.

Performance

tác động thực hiện biên soạn một truy vấn là gì? Nó thực sự có thể khá lớn.Quy tắc chung là biên dịch và lưu vào bộ nhớ cache truy vấn để sử dụng lại mất ít nhất gấp đôi thời gian thực hiện đơn giản mà không cần lưu vào bộ nhớ cache. Đối với các truy vấn phức tạp (đọc inherirante), tôi đã thấy lên tới 10 giây.

Vì vậy, lần đầu tiên truy vấn được biên dịch trước được gọi, bạn sẽ nhận được một lần truy cập hiệu suất. Sau lần truy cập đầu tiên đó, hiệu suất đáng chú ý hơn so với truy vấn không được biên dịch trước đó. Thực tế giống như Linq2Sql

Khi bạn tải trang có truy vấn được biên dịch trước, lần đầu tiên bạn sẽ nhận được lần truy cập. Nó sẽ tải trong có thể 5-15 giây (rõ ràng là nhiều hơn một truy vấn được biên dịch trước sẽ kết thúc được gọi), trong khi tải tiếp theo sẽ mất ít hơn 300ms. Sự khác biệt đáng kể và bạn có thể quyết định xem người dùng đầu tiên của mình có đạt được thành công hay không hoặc bạn muốn tập lệnh gọi các trang của mình để buộc phải biên soạn các truy vấn.

Truy vấn này có thể được lưu trong bộ nhớ cache không?

{ 
    Dog dog = from dog in YourContext.DogSet where dog.ID == id select dog; 
} 

Không, truy vấn LINQ ad-hoc không lưu trữ và bạn sẽ phải chịu chi phí tạo ra các cây mỗi lần bạn gọi nó.

parametrized Queries

Hầu hết các khả năng tìm kiếm liên quan đến truy vấn nhiều parametrized. Thậm chí còn có các thư viện có sẵn cho phép bạn xây dựng một truy vấn parametrized trong các biểu thức lamba. Vấn đề là bạn không thể sử dụng các truy vấn được biên dịch trước với các truy vấn đó. Một con đường xung quanh đó là để vạch ra tất cả các tiêu chí có thể có trong truy vấn và cờ mà một trong những bạn muốn sử dụng:

public struct MyParams 
{ 
    public string name; 
public bool checkName; 
    public int age; 
public bool checkAge; 
} 

    static readonly Func<Entities, MyParams, IEnumerable<Dog>> query_GetDog = 
     CompiledQuery.Compile<Entities, MyParams, IEnumerable<Dog>>((ctx, myParams) => 
      from dog in ctx.DogSet 
    where (myParams.checkAge == true && dog.Age == myParams.age) 
     && (myParams.checkName == true && dog.Name == myParams.name) 
    select dog); 

protected List<Dog> GetSomeDogs() 
{ 
    MyParams myParams = new MyParams(); 
    myParams.name = "Bud"; 
    myParams.checkName = true; 
    myParams.age = 0; 
    myParams.checkAge = false; 
    return query_GetDog(YourContext,myParams).ToList(); 
} 

Ưu điểm ở đây là bạn sẽ có được tất cả các lợi ích hơn của một quert tiền biên dịch. Nhược điểm là bạn có thể sẽ kết thúc với mệnh đề where khá khó bảo trì, bạn sẽ phải chịu một hình phạt lớn hơn cho việc biên dịch truy vấn và mỗi truy vấn bạn chạy không hiệu quả như nó có thể (đặc biệt là với các kết nối được ném vào).

Một cách khác là tạo một mẫu truy vấn EntitySQL theo từng phần, giống như tất cả chúng ta đã làm với SQL.

protected List<Dod> GetSomeDogs(string name, int age) 
{ 
string query = "select value dog from Entities.DogSet where 1 = 1 "; 
    if(!String.IsNullOrEmpty(name)) 
     query = query + " and dog.Name == @Name "; 
if(age > 0) 
    query = query + " and dog.Age == @Age "; 

    ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, YourContext); 
    if(!String.IsNullOrEmpty(name)) 
     oQuery.Parameters.Add(new ObjectParameter("Name", name)); 
if(age > 0) 
     oQuery.Parameters.Add(new ObjectParameter("Age", age)); 

return oQuery.ToList(); 
} 

Ở đây, vấn đề là: - không có cú pháp kiểm tra trong quá trình biên soạn - mỗi sự kết hợp khác nhau của các thông số tạo ra một truy vấn khác nhau mà sẽ cần phải được biên soạn trước khi nó chạy đầu tiên. Trong trường hợp này, chỉ có 4 truy vấn có thể khác nhau (không có thông số, chỉ tuổi, chỉ tên và cả hai thông số), nhưng bạn có thể thấy rằng có thể có nhiều cách hơn với tìm kiếm thông thường trên thế giới. - Không ai thích ghép các chuỗi!

Tùy chọn khác là truy vấn một tập con lớn dữ liệu và sau đó thu hẹp nó xuống trong bộ nhớ. Điều này đặc biệt hữu ích nếu bạn đang làm việc với một tập con dữ liệu xác định, giống như tất cả các chú chó trong thành phố. Bạn biết có rất nhiều nhưng bạn cũng biết không có nhiều ... vì vậy trang tìm kiếm CityDog của bạn có thể tải tất cả các con chó cho thành phố trong bộ nhớ, đó là một truy vấn được biên dịch trước và sau đó tinh chỉnh kết quả

protected List<Dod> GetSomeDogs(string name, int age, string city) 
{ 
string query = "select value dog from Entities.DogSet where dog.Owner.Address.City == @City "; 
    ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, YourContext); 
    oQuery.Parameters.Add(new ObjectParameter("City", city)); 

List<Dog> dogs = oQuery.ToList(); 

if(!String.IsNullOrEmpty(name)) 
     dogs = dogs.Where(it => it.Name == name); 
if(age > 0) 
     dogs = dogs.Where(it => it.Age == age); 

return dogs; 
} 

Điều này đặc biệt hữu ích khi bạn bắt đầu hiển thị tất cả dữ liệu, sau đó cho phép lọc.

Sự cố: - Có thể dẫn đến chuyển dữ liệu nghiêm trọng nếu bạn không cẩn thận về tập con của mình. - Bạn chỉ có thể lọc trên dữ liệu bạn đã trả lại.Nó có nghĩa là nếu bạn không trả lại liên kết Dog.Owner, bạn sẽ không thể lọc trên Dog.Owner.Name Vì vậy, giải pháp tốt nhất là gì? Không có gì cả. Bạn cần chọn giải pháp phù hợp nhất với mình và vấn đề của mình: - Sử dụng tòa nhà truy vấn dựa trên lambda khi bạn không quan tâm đến việc biên dịch trước các truy vấn của mình. - Sử dụng truy vấn LINQ được biên dịch đầy đủ được xác định trước khi cấu trúc đối tượng của bạn không quá phức tạp. - Sử dụng ghép nối EntitySQL/chuỗi khi cấu trúc có thể phức tạp và khi số lượng truy vấn kết quả khác nhau có thể nhỏ (có nghĩa là ít lần truy cập biên dịch trước). - Sử dụng tính năng lọc trong bộ nhớ khi bạn đang làm việc với một tập hợp con nhỏ của dữ liệu hoặc khi bạn phải lấy tất cả dữ liệu trên dữ liệu lúc đầu (nếu hiệu suất tốt với tất cả dữ liệu, thì lọc trong bộ nhớ sẽ không gây ra bất kỳ thời gian để được chi tiêu trong db).

Singleton truy cập

Cách tốt nhất để đối phó với bối cảnh và các tổ chức accross tất cả các trang của bạn là sử dụng mô hình singleton:

public sealed class YourContext 
{ 
    private const string instanceKey = "On3GoModelKey"; 

    YourContext(){} 

    public static YourEntities Instance 
    { 
     get 
     { 
      HttpContext context = HttpContext.Current; 
      if(context == null) 
       return Nested.instance; 

      if (context.Items[instanceKey] == null) 
      { 
       On3GoEntities entity = new On3GoEntities(); 
       context.Items[instanceKey] = entity; 
      } 
      return (YourEntities)context.Items[instanceKey]; 
     } 
    } 

    class Nested 
    { 
     // Explicit static constructor to tell C# compiler 
     // not to mark type as beforefieldinit 
     static Nested() 
     { 
     } 

     internal static readonly YourEntities instance = new YourEntities(); 
    } 
} 

NoTracking, nó có giá trị nó?

Khi thực hiện truy vấn, bạn có thể cho khung làm việc để theo dõi các đối tượng mà nó sẽ trả lại hay không. Nó có nghĩa là gì? Với việc kích hoạt theo dõi (tùy chọn mặc định), khung công tác sẽ theo dõi những gì đang diễn ra với đối tượng (đã được sửa đổi? Đã tạo? Đã xóa?) Và cũng sẽ liên kết các đối tượng với nhau, khi các truy vấn thêm được thực hiện từ cơ sở dữ liệu. được quan tâm ở đây.

Ví dụ, cho phép giả định rằng Dog với ID == 2 có một chủ sở hữu mà ID == 10.

Dog dog = (from dog in YourContext.DogSet where dog.ID == 2 select dog).FirstOrDefault(); 
    //dog.OwnerReference.IsLoaded == false; 
    Person owner = (from o in YourContext.PersonSet where o.ID == 10 select dog).FirstOrDefault(); 
    //dog.OwnerReference.IsLoaded == true; 

Nếu chúng ta làm như vậy không có theo dõi, kết quả sẽ khác nhau.

ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>) 
    (from dog in YourContext.DogSet where dog.ID == 2 select dog); 
oDogQuery.MergeOption = MergeOption.NoTracking; 
Dog dog = oDogQuery.FirstOrDefault(); 
    //dog.OwnerReference.IsLoaded == false; 
ObjectQuery<Person> oPersonQuery = (ObjectQuery<Person>) 
    (from o in YourContext.PersonSet where o.ID == 10 select o); 
oPersonQuery.MergeOption = MergeOption.NoTracking; 
    Owner owner = oPersonQuery.FirstOrDefault(); 
    //dog.OwnerReference.IsLoaded == false; 

Theo dõi rất hữu ích và trong một thế giới hoàn hảo không có vấn đề về hiệu suất, nó luôn được bật. Nhưng trong thế giới này, có một mức giá cho nó, về hiệu suất. Vì vậy, bạn nên sử dụng NoTracking để tăng tốc mọi thứ? Nó phụ thuộc vào những gì bạn đang có kế hoạch để sử dụng dữ liệu cho.

Có khả năng dữ liệu truy vấn của bạn với NoTracking có thể được sử dụng để cập nhật/chèn/xóa trong cơ sở dữ liệu không? Nếu vậy, không sử dụng NoTracking vì các liên kết không được theo dõi và sẽ khiến các ngoại lệ bị ném.

Trong một trang mà có Absolutly không cập nhật cơ sở dữ liệu, bạn có thể sử dụng NoTracking.

Trộn theo dõi và NoTracking là có thể, nhưng nó đòi hỏi bạn phải cẩn thận với bản cập nhật/chèn/xóa. Vấn đề là nếu bạn trộn sau đó bạn có nguy cơ có khuôn khổ cố gắng để đính kèm() một đối tượng NoTracking đến bối cảnh nơi một bản sao của cùng một đối tượng tồn tại với theo dõi trên. Về cơ bản, điều tôi đang nói là:

Dog dog1 = (from dog in YourContext.DogSet where dog.ID == 2).FirstOrDefault(); 

ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>) 
    (from dog in YourContext.DogSet where dog.ID == 2 select dog); 
oDogQuery.MergeOption = MergeOption.NoTracking; 
Dog dog2 = oDogQuery.FirstOrDefault(); 

dog1 và dog2 là 2 đối tượng khác nhau, một đối tượng được theo dõi và một không. Sử dụng các đối tượng tách ra trong một bản cập nhật/chèn sẽ buộc một Đính kèm() mà sẽ nói "Đợi một chút, tôi đã có một đối tượng ở đây với phím cơ sở dữ liệu tương tự. Fail". Và khi bạn đính kèm() một đối tượng, tất cả các hệ thống phân cấp của nó cũng được đính kèm, gây ra các vấn đề ở khắp mọi nơi. Hãy cẩn thận hơn.

Làm thế nào nhanh hơn nhiều là nó với NoTracking

Nó phụ thuộc vào các truy vấn. Một số có nhiều khả năng theo dõi hơn so với các loại khác. Tôi không có quy tắc dễ dàng cho nó, nhưng nó giúp ích.

Vì vậy, tôi nên sử dụng NoTracking ở mọi nơi?

Không chính xác. Có một số lợi thế để theo dõi đối tượng. Đầu tiên là đối tượng được lưu trữ, vì vậy cuộc gọi tiếp theo cho đối tượng đó sẽ không nhấn vào cơ sở dữ liệu. Cache đó chỉ có hiệu lực trong suốt vòng đời của đối tượng YourEntities, nếu bạn sử dụng mã singleton ở trên, giống như thời gian tồn tại của trang. Một trang yêu cầu == một đối tượng YourEntity. Vì vậy, đối với nhiều cuộc gọi cho cùng một đối tượng, nó sẽ chỉ tải một lần cho mỗi yêu cầu trang. (Cơ chế bộ nhớ đệm khác có thể mở rộng cơ chế đó).

Điều gì xảy ra khi bạn đang sử dụng NoTracking và cố tải cùng một đối tượng nhiều lần? Cơ sở dữ liệu sẽ được truy vấn mỗi lần, do đó, có một tác động ở đó. Bao lâu bạn nên gọi cho cùng một đối tượng trong một yêu cầu trang đơn? Ít nhất có thể tất nhiên, nhưng nó xảy ra.

Bạn còn nhớ đoạn trên về việc các liên kết có được kết nối tự động cho bạn không? Bạn không cần phải điều đó với NoTracking, vì vậy nếu bạn tải dữ liệu của bạn trong nhiều đợt, bạn sẽ không có một liên kết đến giữa chúng:

ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>)(from dog in YourContext.DogSet select dog); 
oDogQuery.MergeOption = MergeOption.NoTracking; 
List<Dog> dogs = oDogQuery.ToList(); 

ObjectQuery<Person> oPersonQuery = (ObjectQuery<Person>)(from o in YourContext.PersonSet select o); 
oPersonQuery.MergeOption = MergeOption.NoTracking; 
    List<Person> owners = oPersonQuery.ToList(); 

Trong trường hợp này, không có con chó sẽ được gán thuộc tính .Owner của nó.

Một số điều cần lưu ý khi bạn đang cố gắng tối ưu hóa hiệu suất.

Không tải chậm, tôi phải làm gì?

Điều này có thể được xem như là một phước lành trong ngụy trang. Tất nhiên nó là gây phiền nhiễu để tải tất cả mọi thứ bằng tay. Tuy nhiên, nó giảm số lượng cuộc gọi đến db và buộc bạn phải suy nghĩ về thời điểm bạn nên tải dữ liệu. Bạn càng có thể tải trong một cơ sở dữ liệu gọi là tốt hơn. Điều đó luôn đúng, nhưng nó được thực thi ngay bây giờ với 'tính năng' này của EF.

Tất nhiên, bạn có thể gọi nếu (! ObjectReference.IsLoaded) ObjectReference.Load(); nếu bạn muốn, nhưng thực hành tốt hơn là buộc khuôn khổ tải các đối tượng bạn biết bạn sẽ cần trong một lần chụp. Đây là nơi mà các cuộc thảo luận về parametrized Bao gồm bắt đầu có ý nghĩa.

phép nói rằng bạn có bạn Dog đối tượng

public class Dog 
{ 
    public Dog Get(int id) 
    { 
     return YourContext.DogSet.FirstOrDefault(it => it.ID == id); 
    } 
} 

Đây là loại chức năng bạn làm việc với tất cả các thời gian. Nó được gọi từ khắp nơi và một khi bạn có đối tượng Dog đó, bạn sẽ làm những việc rất khác với nó trong các chức năng khác nhau. Đầu tiên, nó phải được biên dịch trước, bởi vì bạn sẽ gọi nó rất thường xuyên. Thứ hai, mỗi trang khác nhau sẽ muốn có quyền truy cập vào một tập hợp con khác của dữ liệu Dog. Một số sẽ muốn Chủ sở hữu, một số FavoriteToy, v.v.

Tất nhiên, bạn có thể gọi Load() cho mỗi tham chiếu bạn cần bất cứ lúc nào bạn cần. Nhưng điều đó sẽ tạo ra một cuộc gọi đến cơ sở dữ liệu mỗi lần. Ý tưởng tồi.Vì vậy, thay vào đó, mỗi trang sẽ yêu cầu các dữ liệu mà nó muốn nhìn thấy khi nó yêu cầu đầu tiên cho đối tượng Dog:

static public Dog Get(int id) { return GetDog(entity,"");} 
    static public Dog Get(int id, string includePath) 
{ 
     string query = "select value o " + 
      " from YourEntities.DogSet as o " + 
+15

Nghiêm túc, bạn không có blog hoặc trang web của riêng bạn, nơi bạn có thể đã đăng bài luận này? – AnthonyWJones

+7

@AnthonyWJones, tốt, trong khi đó không phải là bài đăng bình thường trên SO; nếu bạn nghe SO Podcast, Jeff luôn sử dụng câu thần chú rằng SO là nơi dành cho các lập trình viên chia sẻ kiến ​​thức không có blog. Đây không phải là xúc phạm. Và dựa trên câu thần chú của Jeff, không nên bị đóng trong quan điểm của tôi. – BobbyShaftoe

+0

Chỉ cần thêm, tôi nghĩ rằng nó cũng là thú vị mà bài đăng này có lẽ sẽ được upvoted nhiều lần nếu nó là một câu trả lời cho câu hỏi; câu hỏi có thể chỉ là tiêu đề; nhưng mọi người đang đánh dấu nó là xúc phạm ... kỳ quặc. – BobbyShaftoe

Trả lời

1

Trong khi nhiều thông tin tôi nghĩ rằng nó có thể hữu ích hơn để chia sẻ như thế nào tất cả điều này phù hợp với một kiến ​​trúc giải pháp hoàn chỉnh . Ví dụ- Có một giải pháp cho thấy nơi bạn sử dụng cả thừa kế EF và phương án thay thế của bạn để nó thể hiện sự khác biệt về hiệu suất của chúng.

3

Vui lòng không sử dụng tất cả các thông tin trên như "Truy cập Singleton". Bạn hoàn toàn 100% không nên lưu trữ bối cảnh này để được tái sử dụng vì nó không phải là chủ đề an toàn.

+0

+1 nó chắc chắn chống lại phương pháp tiếp cận đơn vị công việc. Nó không hỗ trợ hủy chỉnh sửa và khi lưu ngữ cảnh, bạn sẽ gặp khó khăn khi biết những gì đã thực sự thay đổi kể từ lần lưu cuối cùng. – surfen

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