C # Entity-Framework: ฉันจะรวม. Find และ .Include บน Model Object ได้อย่างไร


145

ฉันกำลังทำแบบฝึกหัดฝึก mvcmusicstore ฉันสังเกตเห็นบางอย่างเมื่อสร้างนั่งร้านสำหรับตัวจัดการอัลบั้ม (เพิ่มการลบการแก้ไข)

ฉันต้องการเขียนโค้ดอย่างหรูหราดังนั้นฉันกำลังมองหาวิธีการเขียนที่สะอาด

FYI ฉันกำลังทำให้ร้านค้าเป็นแบบทั่วไปมากขึ้น:

อัลบั้ม = รายการ

ประเภท = หมวดหมู่

ศิลปิน = ยี่ห้อ

นี่คือวิธีดึงดัชนี (สร้างโดย MVC):

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

นี่คือวิธีการดึงรายการออก:

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

คนแรกนำกลับรายการทั้งหมดและเติมหมวดหมู่และรูปแบบแบรนด์ในรูปแบบรายการ อันที่สองไม่ได้เติมหมวดหมู่และยี่ห้อ

ฉันจะเขียนอันที่สองเพื่อค้นหาและเติมค่าอะไรเข้าไปข้างใน (โดยเฉพาะอย่างยิ่งใน 1 บรรทัด) ... ในทางทฤษฎี - สิ่งที่ชอบ:

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

หากใครต้องการทำสิ่งนี้โดยทั่วไป in.net-core ดูคำตอบของฉัน
johnny 5

คำตอบ:


162

คุณต้องใช้Include()ก่อนจากนั้นดึงวัตถุเดียวจากการสืบค้นที่เกิดขึ้น:

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

24
ฉันอยากจะแนะนำให้ใช้หลัง (SingleOrDefault) ToList จะดึงรายการทั้งหมดก่อนแล้วเลือกหนึ่งรายการ
Sander Rijken

5
สิ่งนี้จะพังลงถ้าเรามีคีย์หลักผสมและกำลังใช้การค้นหาที่เกี่ยวข้องมากเกินไป
jhappoldt

78
สิ่งนี้จะได้ผล แต่มีความแตกต่างระหว่างการใช้ "ค้นหา" และ "SingleOrDefault" เมธอด "ค้นหา" ส่งคืนออบเจกต์จากที่จัดเก็บในตัวเครื่องหากมีอยู่หลีกเลี่ยงการไปกลับฐานข้อมูลโดยใช้ "SingleOrDefault" จะบังคับให้เคียวรีไปยังฐานข้อมูลอย่างไรก็ตาม
Iravanchi

3
@Iravanchi ถูกต้อง สิ่งนี้อาจใช้งานได้สำหรับผู้ใช้ แต่การดำเนินการและผลข้างเคียงของมันไม่เทียบเท่ากับ Find เท่าที่ฉันรู้
mwilson

3
ไม่ตอบคำถาม ops เพราะไม่ได้ใช้หา
Paul Swetz

73

คำตอบของเดนนิสใช้และInclude SingleOrDefaultหลังจะปัดเศษไปยังฐานข้อมูล

อีกทางเลือกหนึ่งคือใช้Findร่วมกับLoadการโหลดเอนทิตีที่เกี่ยวข้องอย่างชัดเจน ...

ด้านล่างเป็นตัวอย่าง 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(); 
}

แน่นอนFindส่งคืนทันทีโดยไม่ต้องทำการร้องขอไปยังร้านค้าถ้านิติบุคคลนั้นโหลดโดยบริบทแล้ว


30
วิธีนี้ใช้Findดังนั้นหากมีเอนทิตีอยู่ไม่มีการไปกลับไปยังฐานข้อมูลสำหรับเอนทิตีเอง แต่คุณจะมีการเดินทางไป - กลับสำหรับความสัมพันธ์แต่ละอย่างที่คุณกำลังทำอยู่Loadในขณะที่การSingleOrDefaultรวมกันกับIncludeทุกอย่างในครั้งเดียว
Iravanchi

เมื่อฉันเปรียบเทียบ 2 ในตัวสร้างโปรไฟล์ SQL Find / Load ดีกว่าสำหรับกรณีของฉัน (ฉันมีความสัมพันธ์แบบ 1: 1) @Iravanchi: คุณหมายถึงว่าถ้าฉันมี 1: m ความสัมพันธ์มันจะเรียกว่า m คูณร้านหรือไม่ ... เพราะจะไม่ทำให้รู้สึกมาก
ผู้เรียน

3
ไม่ใช่ 1: ความสัมพันธ์ m แต่มีหลายความสัมพันธ์ ทุกครั้งที่คุณเรียกใช้Loadฟังก์ชันความสัมพันธ์ควรถูกเติมเมื่อการโทรกลับมา ดังนั้นหากคุณโทรLoadหลายครั้งเพื่อความสัมพันธ์ที่หลากหลายจะมีการเดินทางไปกลับทุกครั้ง แม้สำหรับความสัมพันธ์ที่เดียวถ้าFindวิธีการไม่พบกิจการในหน่วยความจำก็จะทำให้การเดินทางรอบสอง: หนึ่งและที่สองสำหรับFind แต่Load วิธีการเรียกนิติบุคคลและความสัมพันธ์ในหนึ่งไปไกลเท่าที่ฉันรู้ ( แต่ผมไม่แน่ใจ)IncludeSingleOrDefault
Iravanchi

1
มันคงจะดีถ้าหากสามารถติดตามการออกแบบรวมอย่างใดแทนที่จะต้องรักษาคอลเลกชันและการอ้างอิงที่แตกต่างกัน ทำให้ยากขึ้นในการสร้างส่วน GetById () ที่ใช้ตัวเลือก Expression <Func <T, object >> (เช่น _repo.GetById (id, x => x.MyCollection)
Derek Greer

4
โปรดพูดถึงการอ้างอิงโพสต์ของคุณ: msdn.microsoft.com/en-us/data/jj574232.aspx#explicit
Hossein

1

คุณต้องส่ง IQueryable ไปยัง DbSet

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

return dbSet.Find(id);


ไม่มี. Find หรือ. FindAsync ใน dbSet นี่คือ EF Core หรือไม่
ธีรี่ร์

มี ef 6 เช่นกันบน ef core
Rafael R. Souza

ฉันมีความหวังแล้ว "InvalidCastException"
ZX9

0

ไม่ได้ผลสำหรับฉัน แต่ฉันแก้ไขมันโดยทำเช่นนี้

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

ไม่ทราบว่านี่เป็นวิธีแก้ปัญหาหรือไม่ แต่เดนนิสอีกคนหนึ่งให้ข้อผิดพลาดแบบบูลมาให้ฉัน .SingleOrDefault(x => x.ItemId = id);


4
วิธีแก้ปัญหาของ Dennis ก็ต้องใช้ได้เช่นกัน คุณอาจจะมีข้อผิดพลาดนี้SingleOrDefault(x => x.ItemId = id)เพียงเพราะผิดเดียว=แทนที่จะเป็นสองเท่า==?
Slauma

6
ใช่ดูเหมือนว่าคุณใช้ = ไม่ == ความผิดพลาดทางไวยากรณ์;)
Ralph N

ฉันลองพวกเขาทั้งคู่ == และ = ยังให้ข้อผิดพลาดใน. SingleOrDefault (x => x.ItemId = id); = / ต้องเป็นอย่างอื่นในรหัสของฉันผิด แต่วิธีที่ฉันทำคือวิธีที่ไม่ดี? บางทีฉันไม่เข้าใจสิ่งที่คุณหมายถึงเดนนิสมีซิงเกิ้ล = ในรหัสของเขาเช่นกัน
Johan

0

ไม่มีวิธีง่าย ๆ ในการกรองด้วยการค้นหา แต่ฉันได้มาใกล้กับการทำซ้ำฟังก์ชั่น แต่โปรดทราบบางสิ่งสำหรับการแก้ปัญหาของฉัน

โซลูชันนี้ช่วยให้คุณสามารถกรองโดยทั่วไปโดยไม่รู้จักคีย์หลักใน. net-core

  1. การค้นหานั้นแตกต่างกันอย่างมากเนื่องจากได้รับเอนทิตีหากมีอยู่ในการติดตามก่อนทำการสืบค้นฐานข้อมูล

  2. นอกจากนี้มันสามารถกรองโดยวัตถุเพื่อให้ผู้ใช้ไม่จำเป็นต้องรู้คีย์หลัก

  3. วิธีนี้ใช้สำหรับ EntityFramework Core

  4. สิ่งนี้ต้องการการเข้าถึงบริบท

ต่อไปนี้เป็นวิธีส่วนขยายที่จะเพิ่มซึ่งจะช่วยคุณกรองตามคีย์หลัก

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

เมื่อคุณมีวิธีการขยายเหล่านี้คุณสามารถกรองเช่น:

query.FilterByPrimaryKey(this._context, id);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.