C # Entity-Framework: Làm cách nào tôi có thể kết hợp một .indind và .Innce trên Model Object?


145

Tôi đang làm hướng dẫn thực hành mvcmusicstore. Tôi nhận thấy một cái gì đó khi tạo giàn giáo cho trình quản lý album (thêm xóa chỉnh sửa).

Tôi muốn viết mã một cách thanh lịch, vì vậy tôi đang tìm cách viết sạch này.

FYI tôi đang làm cho cửa hàng chung chung hơn:

Album = Mục

Thể loại = Thể loại

Nghệ sĩ = Thương hiệu

Đây là cách lấy chỉ mục (được tạo bởi MVC):

var items = db.Items.Include(i => i.Category).Include(i => i.Brand);

Đây là cách lấy mục để xóa:

Item item = db.Items.Find(id);

Cái đầu tiên mang lại tất cả các mặt hàng và điền vào các mô hình thể loại và thương hiệu bên trong mô hình vật phẩm. Cái thứ hai, không điền vào danh mục và thương hiệu.

Làm thế nào tôi có thể viết cái thứ hai để thực hiện tìm VÀ điền những gì bên trong (tốt nhất là trong 1 dòng) ... về mặt lý thuyết - đại loại như:

Item item = db.Items.Find(id).Include(i => i.Category).Include(i => i.Brand);

Nếu bất cứ ai cần làm điều này một cách khái quát in.net-core, hãy xem câu trả lời của tôi
johnny 5

Câu trả lời:


162

Bạn cần sử dụng Include()trước, sau đó truy xuất một đối tượng từ truy vấn kết quả:

Item item = db.Items
              .Include(i => i.Category)
              .Include(i => i.Brand)
              .SingleOrDefault(x => x.ItemId == id);

24
Tôi thực sự khuyên bạn nên sử dụng cái sau (SingleOrDefault), ToList sẽ truy xuất tất cả các mục nhập trước và sau đó chọn một mục
Sander Rijken

5
Điều này bị hỏng nếu chúng ta có khóa chính tổng hợp và đang sử dụng quá tải tìm thấy có liên quan.
jhappoldt

78
Điều này sẽ hoạt động, nhưng có một sự khác biệt giữa việc sử dụng "Tìm" và "SingleOrDefault". Phương thức "Tìm" trả về đối tượng từ cửa hàng được theo dõi cục bộ nếu nó tồn tại, tránh một chuyến đi khứ hồi tới cơ sở dữ liệu, trong đó sử dụng "SingleOrDefault" sẽ buộc truy vấn vào cơ sở dữ liệu.
Margarvanchi

3
@Iravanchi đúng. Điều này có thể đã làm việc cho người dùng, nhưng hoạt động và tác dụng phụ của nó không tương đương với Tìm kiếm, theo như tôi biết.
mwilson

3
Không thực sự trả lời câu hỏi ops vì nó không sử dụng .indind
Paul Swetz

73

Câu trả lời của Dennis đang sử dụng IncludeSingleOrDefault. Cái sau đi vòng quanh cơ sở dữ liệu.

Một cách khác, là sử dụng Find, kết hợp với Load, để tải rõ ràng các thực thể liên quan ...

Bên dưới một ví dụ MSDN :

using (var context = new BloggingContext()) 
{ 
  var post = context.Posts.Find(2); 

  // Load the blog related to a given post 
  context.Entry(post).Reference(p => p.Blog).Load(); 

  // Load the blog related to a given post using a string  
  context.Entry(post).Reference("Blog").Load(); 

  var blog = context.Blogs.Find(1); 

  // Load the posts related to a given blog 
  context.Entry(blog).Collection(p => p.Posts).Load(); 

  // Load the posts related to a given blog  
  // using a string to specify the relationship 
  context.Entry(blog).Collection("Posts").Load(); 
}

Tất nhiên, Findtrả về ngay lập tức mà không yêu cầu cửa hàng, nếu thực thể đó đã được tải bởi bối cảnh.


30
Phương thức này sử dụng Findvì vậy nếu có thực thể, không có chuyến đi khứ hồi tới DB cho chính thực thể đó. NHƯNG, bạn sẽ có một chuyến đi khứ hồi cho mỗi mối quan hệ bạn đang thực Loadhiện, trong khi SingleOrDefaultkết hợp với Includetải mọi thứ trong một lần.
Margarvanchi

Khi tôi so sánh 2 trong trình lược tả SQL, Tìm / Tải tốt hơn cho trường hợp của tôi (tôi có quan hệ 1: 1). @Iravanchi: bạn có nghĩa là nếu tôi có quan hệ 1: m thì nó sẽ được gọi là m lần cửa hàng không? ... bởi vì nó sẽ không có ý nghĩa nhiều lắm.
Học viên

3
Không phải quan hệ 1: m, mà là nhiều mối quan hệ. Mỗi lần bạn gọi Loadhàm, mối quan hệ sẽ được xác định khi cuộc gọi trở lại. Vì vậy, nếu bạn gọi Loadnhiều lần cho nhiều mối quan hệ, mỗi lần sẽ có một chuyến đi khứ hồi. Ngay cả đối với một mối quan hệ duy nhất, nếu Findphương thức không tìm thấy thực thể trong bộ nhớ, nó thực hiện hai chuyến đi khứ hồi: một cho Findvà thứ hai cho Load. Nhưng Include. SingleOrDefaultCách tiếp cận tìm nạp thực thể và mối quan hệ trong một lần đi xa như tôi biết (nhưng tôi không chắc chắn)
Iravanchi

1
Sẽ tốt hơn nếu có thể theo thiết kế Bao gồm bằng cách nào đó thay vì phải đối xử với các bộ sưu tập và tài liệu tham khảo khác nhau. Điều đó khiến việc tạo mặt tiền GetById () trở nên khó khăn hơn khi chỉ cần một bộ sưu tập Biểu thức <Func <T, object >> (ví dụ _repo.GetById (id, x => x.MyCollection))
Derek Greer

4
Hãy nhớ đề cập đến tài liệu tham khảo của bài đăng của bạn: msdn.microsoft.com/en-us/data/jj574232.aspx#explicit
Hossein

1

Bạn phải truyền IQueryable cho Dbset

var dbSet = (DbSet<Item>) db.Set<Item>().Include("");

return dbSet.Find(id);


Không có .indind hoặc.FindAsync trong dbset. Đây có phải là lõi của EF không?
Thierry

cũng có ef 6 trên lõi ef
Rafael R. Souza

Tôi đã hy vọng và sau đó "UnlimitedCastException"
ZX9

0

Không làm việc cho tôi. Nhưng tôi đã giải quyết nó bằng cách làm như thế này.

var item = db.Items
             .Include(i => i.Category)
             .Include(i => i.Brand)
             .Where(x => x.ItemId == id)
             .First();

Không biết nếu đó là một giải pháp ok. Nhưng một cái khác mà Dennis đã cho tôi một lỗi bool trong .SingleOrDefault(x => x.ItemId = id);


4
Giải pháp của Dennis cũng phải hoạt động. Có lẽ bạn có lỗi này SingleOrDefault(x => x.ItemId = id)chỉ vì sai đơn =thay vì gấp đôi ==?
Slauma

6
vâng, có vẻ như bạn đã sử dụng = không ==. Lỗi cú pháp;)
Ralph N

Tôi đã thử cả hai == và = vẫn gặp lỗi trong .SingleOrDefault (x => x.ItemId = id); = / Phải là một cái gì đó khác trong mã của tôi đó là sai. Nhưng cách tôi đã làm là một cách xấu? Có lẽ tôi không hiểu ý bạn là gì Dennis có một singel = trong mã của anh ấy.
Johan

0

Không có cách thực sự dễ dàng để lọc với một tìm kiếm. Nhưng tôi đã đưa ra một cách gần để tái tạo chức năng nhưng xin lưu ý một vài điều cho giải pháp của tôi.

Giải pháp này cho phép bạn lọc tổng quát mà không cần biết khóa chính trong .net-core

  1. Tìm về cơ bản là khác nhau vì nó có được thực thể nếu nó có trong theo dõi trước khi Truy vấn cơ sở dữ liệu.

  2. Ngoài ra, nó có thể lọc theo một Đối tượng để người dùng không phải biết khóa chính.

  3. Giải pháp này dành cho EntityFramework Core.

  4. Điều này đòi hỏi truy cập vào bối cảnh

Dưới đây là một số phương thức mở rộng để thêm sẽ giúp bạn lọc theo khóa chính để

    public static IReadOnlyList<IProperty> GetPrimaryKeyProperties<T>(this DbContext dbContext)
    {
        return dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties;
    }

    //TODO Precompile expression so this doesn't happen everytime
    public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
    {
        var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var body = keyProperties
            // e => e.PK[i] == id[i]
            .Select((p, i) => Expression.Equal(
                Expression.Property(parameter, p.Name),
                Expression.Convert(
                    Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
                    p.ClrType)))
            .Aggregate(Expression.AndAlso);
        return Expression.Lambda<Func<T, bool>>(body, parameter);
    }

    public static Expression<Func<T, object[]>> GetPrimaryKeyExpression<T>(this DbContext context)
    {
        var keyProperties = context.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var keyPropertyAccessExpression = keyProperties.Select((p, i) => Expression.Convert(Expression.Property(parameter, p.Name), typeof(object))).ToArray();
        var selectPrimaryKeyExpressionBody = Expression.NewArrayInit(typeof(object), keyPropertyAccessExpression);

        return Expression.Lambda<Func<T, object[]>>(selectPrimaryKeyExpressionBody, parameter);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this DbSet<TEntity> dbSet, DbContext context, object[] id)
        where TEntity : class
    {
        return FilterByPrimaryKey(dbSet.AsQueryable(), context, id);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this IQueryable<TEntity> queryable, DbContext context, object[] id)
        where TEntity : class
    {
        return queryable.Where(context.FilterByPrimaryKeyPredicate<TEntity>(id));
    }

Khi bạn có các phương thức mở rộng này, bạn có thể lọc như vậy:

query.FilterByPrimaryKey(this._context, id);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.