[{"data":1,"prerenderedAt":1080},["ShallowReactive",2],{"blog-\u002Fblog\u002Fdotnet-ef-querying":3},{"id":4,"title":5,"body":6,"description":1065,"difficulty":1066,"extension":1067,"framework":1068,"frameworkSlug":1069,"meta":1070,"navigation":79,"order":58,"path":1071,"qaPath":1072,"seo":1073,"stem":1074,"subtopic":1075,"topic":1076,"topicSlug":1077,"updated":1078,"__hash__":1079},"blog\u002Fblog\u002Fdotnet-ef-querying.md","How EF Core Translates LINQ to SQL",{"type":7,"value":8,"toc":1050},"minimark",[9,14,18,22,33,99,112,116,124,185,196,200,207,358,362,372,463,467,470,552,555,564,568,571,638,644,648,651,727,730,734,737,803,813,817,820,863,870,885,892,907,911,917,938,941,945,948,982,985,989,992,1046],[10,11,13],"h2",{"id":12},"why-querying-knowledge-matters-in-interviews","Why querying knowledge matters in interviews",[15,16,17],"p",{},"Querying is where most EF Core performance problems originate. Interviewers focus on it\nbecause the patterns that cause N+1 queries, Cartesian explosions, and full table scans are\neasy to write accidentally and hard to spot in code review without understanding how EF\ntranslates LINQ to SQL.",[10,19,21],{"id":20},"iqueryable-vs-ienumerable-the-most-important-distinction","IQueryable vs IEnumerable — the most important distinction",[15,23,24,28,29,32],{},[25,26,27],"code",{},"IQueryable\u003CT>"," builds a SQL expression; ",[25,30,31],{},"IEnumerable\u003CT>"," loads data into memory first.",[34,35,40],"pre",{"className":36,"code":37,"language":38,"meta":39,"style":39},"language-csharp shiki shiki-themes github-light github-dark","\u002F\u002F IQueryable — WHERE runs in the database:\nIQueryable\u003COrder> query = _db.Orders\n    .Where(o => o.CustomerId == id)   \u002F\u002F added to SQL\n    .Where(o => o.Total > 100);       \u002F\u002F AND added to SQL\nvar result = await query.ToListAsync(); \u002F\u002F SELECT ... WHERE CustomerId=? AND Total>?\n\n\u002F\u002F IEnumerable — loads ALL rows, filters in C#:\nIEnumerable\u003COrder> all = _db.Orders.AsEnumerable(); \u002F\u002F SELECT * FROM Orders — full table\nvar result = all.Where(o => o.Total > 100);          \u002F\u002F filtered in memory — too late\n","csharp","",[25,41,42,50,56,62,68,74,81,87,93],{"__ignoreMap":39},[43,44,47],"span",{"class":45,"line":46},"line",1,[43,48,49],{},"\u002F\u002F IQueryable — WHERE runs in the database:\n",[43,51,53],{"class":45,"line":52},2,[43,54,55],{},"IQueryable\u003COrder> query = _db.Orders\n",[43,57,59],{"class":45,"line":58},3,[43,60,61],{},"    .Where(o => o.CustomerId == id)   \u002F\u002F added to SQL\n",[43,63,65],{"class":45,"line":64},4,[43,66,67],{},"    .Where(o => o.Total > 100);       \u002F\u002F AND added to SQL\n",[43,69,71],{"class":45,"line":70},5,[43,72,73],{},"var result = await query.ToListAsync(); \u002F\u002F SELECT ... WHERE CustomerId=? AND Total>?\n",[43,75,77],{"class":45,"line":76},6,[43,78,80],{"emptyLinePlaceholder":79},true,"\n",[43,82,84],{"class":45,"line":83},7,[43,85,86],{},"\u002F\u002F IEnumerable — loads ALL rows, filters in C#:\n",[43,88,90],{"class":45,"line":89},8,[43,91,92],{},"IEnumerable\u003COrder> all = _db.Orders.AsEnumerable(); \u002F\u002F SELECT * FROM Orders — full table\n",[43,94,96],{"class":45,"line":95},9,[43,97,98],{},"var result = all.Where(o => o.Total > 100);          \u002F\u002F filtered in memory — too late\n",[15,100,101,102,104,105,107,108,111],{},"Returning ",[25,103,27],{}," from a method lets callers compose additional filters and\nprojections before the SQL is executed. Returning ",[25,106,31],{}," or ",[25,109,110],{},"List\u003CT>"," freezes\nthe query at that point.",[10,113,115],{"id":114},"deferred-execution","Deferred execution",[15,117,118,119,123],{},"EF Core queries are ",[120,121,122],"strong",{},"descriptions",", not database calls. SQL executes only at terminal operators:",[34,125,127],{"className":36,"code":126,"language":38,"meta":39,"style":39},"\u002F\u002F No SQL yet — just building an expression tree:\nvar query = _db.Orders\n    .Where(o => o.Status == \"Pending\")\n    .OrderBy(o => o.CreatedAt)\n    .Take(50);\n\n\u002F\u002F Terminal operators — each triggers a SQL call:\nvar list  = await query.ToListAsync();\nvar first = await query.FirstOrDefaultAsync();\nvar count = await query.CountAsync();\nvar any   = await query.AnyAsync();\n",[25,128,129,134,139,144,149,154,158,163,168,173,179],{"__ignoreMap":39},[43,130,131],{"class":45,"line":46},[43,132,133],{},"\u002F\u002F No SQL yet — just building an expression tree:\n",[43,135,136],{"class":45,"line":52},[43,137,138],{},"var query = _db.Orders\n",[43,140,141],{"class":45,"line":58},[43,142,143],{},"    .Where(o => o.Status == \"Pending\")\n",[43,145,146],{"class":45,"line":64},[43,147,148],{},"    .OrderBy(o => o.CreatedAt)\n",[43,150,151],{"class":45,"line":70},[43,152,153],{},"    .Take(50);\n",[43,155,156],{"class":45,"line":76},[43,157,80],{"emptyLinePlaceholder":79},[43,159,160],{"class":45,"line":83},[43,161,162],{},"\u002F\u002F Terminal operators — each triggers a SQL call:\n",[43,164,165],{"class":45,"line":89},[43,166,167],{},"var list  = await query.ToListAsync();\n",[43,169,170],{"class":45,"line":95},[43,171,172],{},"var first = await query.FirstOrDefaultAsync();\n",[43,174,176],{"class":45,"line":175},10,[43,177,178],{},"var count = await query.CountAsync();\n",[43,180,182],{"class":45,"line":181},11,[43,183,184],{},"var any   = await query.AnyAsync();\n",[15,186,187,188,191,192,195],{},"Avoid materialising the same query twice — two ",[25,189,190],{},"ToListAsync"," calls on the same ",[25,193,194],{},"IQueryable","\nproduce two SQL round trips.",[10,197,199],{"id":198},"projections-load-only-what-you-need","Projections — load only what you need",[15,201,202,203,206],{},"Always project to a DTO for read-only paths. Projection reduces the columns in ",[25,204,205],{},"SELECT",":",[34,208,210],{"className":36,"code":209,"language":38,"meta":39,"style":39},"\u002F\u002F Loads all columns including blobs:\nvar orders = await _db.Orders.ToListAsync();\n\n\u002F\u002F Only fetches Id, Status, Total:\nvar summaries = await _db.Orders\n    .Where(o => o.CustomerId == id)\n    .Select(o => new OrderSummaryDto\n    {\n        Id     = o.Id,\n        Status = o.Status,\n        Total  = o.Total\n    })\n    .ToListAsync();\n\n\u002F\u002F Nested projection — one query with automatic JOINs:\nvar details = await _db.Orders\n    .Select(o => new OrderDetailDto\n    {\n        Id           = o.Id,\n        CustomerName = o.Customer.Name,\n        Items        = o.Items.Select(i => new ItemDto\n        {\n            ProductName = i.Product.Name,\n            Quantity    = i.Quantity\n        }).ToList()\n    })\n    .ToListAsync();\n",[25,211,212,217,222,226,231,236,241,246,251,256,261,266,272,278,283,289,295,301,306,312,318,324,330,336,342,348,353],{"__ignoreMap":39},[43,213,214],{"class":45,"line":46},[43,215,216],{},"\u002F\u002F Loads all columns including blobs:\n",[43,218,219],{"class":45,"line":52},[43,220,221],{},"var orders = await _db.Orders.ToListAsync();\n",[43,223,224],{"class":45,"line":58},[43,225,80],{"emptyLinePlaceholder":79},[43,227,228],{"class":45,"line":64},[43,229,230],{},"\u002F\u002F Only fetches Id, Status, Total:\n",[43,232,233],{"class":45,"line":70},[43,234,235],{},"var summaries = await _db.Orders\n",[43,237,238],{"class":45,"line":76},[43,239,240],{},"    .Where(o => o.CustomerId == id)\n",[43,242,243],{"class":45,"line":83},[43,244,245],{},"    .Select(o => new OrderSummaryDto\n",[43,247,248],{"class":45,"line":89},[43,249,250],{},"    {\n",[43,252,253],{"class":45,"line":95},[43,254,255],{},"        Id     = o.Id,\n",[43,257,258],{"class":45,"line":175},[43,259,260],{},"        Status = o.Status,\n",[43,262,263],{"class":45,"line":181},[43,264,265],{},"        Total  = o.Total\n",[43,267,269],{"class":45,"line":268},12,[43,270,271],{},"    })\n",[43,273,275],{"class":45,"line":274},13,[43,276,277],{},"    .ToListAsync();\n",[43,279,281],{"class":45,"line":280},14,[43,282,80],{"emptyLinePlaceholder":79},[43,284,286],{"class":45,"line":285},15,[43,287,288],{},"\u002F\u002F Nested projection — one query with automatic JOINs:\n",[43,290,292],{"class":45,"line":291},16,[43,293,294],{},"var details = await _db.Orders\n",[43,296,298],{"class":45,"line":297},17,[43,299,300],{},"    .Select(o => new OrderDetailDto\n",[43,302,304],{"class":45,"line":303},18,[43,305,250],{},[43,307,309],{"class":45,"line":308},19,[43,310,311],{},"        Id           = o.Id,\n",[43,313,315],{"class":45,"line":314},20,[43,316,317],{},"        CustomerName = o.Customer.Name,\n",[43,319,321],{"class":45,"line":320},21,[43,322,323],{},"        Items        = o.Items.Select(i => new ItemDto\n",[43,325,327],{"class":45,"line":326},22,[43,328,329],{},"        {\n",[43,331,333],{"class":45,"line":332},23,[43,334,335],{},"            ProductName = i.Product.Name,\n",[43,337,339],{"class":45,"line":338},24,[43,340,341],{},"            Quantity    = i.Quantity\n",[43,343,345],{"class":45,"line":344},25,[43,346,347],{},"        }).ToList()\n",[43,349,351],{"class":45,"line":350},26,[43,352,271],{},[43,354,356],{"class":45,"line":355},27,[43,357,277],{},[10,359,361],{"id":360},"eager-loading-with-include-and-theninclude","Eager loading with Include and ThenInclude",[15,363,364,367,368,371],{},[25,365,366],{},"Include"," adds a SQL ",[25,369,370],{},"JOIN"," for related entities. Without it, navigation properties are null.",[34,373,375],{"className":36,"code":374,"language":38,"meta":39,"style":39},"\u002F\u002F Single level:\nvar orders = await _db.Orders\n    .Include(o => o.Customer)\n    .Include(o => o.Items)\n    .ToListAsync();\n\n\u002F\u002F Deep graph with ThenInclude:\nvar orders = await _db.Orders\n    .Include(o => o.Items)\n        .ThenInclude(i => i.Product)\n    .Include(o => o.Customer)\n        .ThenInclude(c => c.Address)\n    .ToListAsync();\n\n\u002F\u002F Filtered include (EF Core 5+) — only active items:\nvar orders = await _db.Orders\n    .Include(o => o.Items.Where(i => !i.IsCancelled))\n    .AsNoTracking()\n    .ToListAsync();\n",[25,376,377,382,387,392,397,401,405,410,414,418,423,427,432,436,440,445,449,454,459],{"__ignoreMap":39},[43,378,379],{"class":45,"line":46},[43,380,381],{},"\u002F\u002F Single level:\n",[43,383,384],{"class":45,"line":52},[43,385,386],{},"var orders = await _db.Orders\n",[43,388,389],{"class":45,"line":58},[43,390,391],{},"    .Include(o => o.Customer)\n",[43,393,394],{"class":45,"line":64},[43,395,396],{},"    .Include(o => o.Items)\n",[43,398,399],{"class":45,"line":70},[43,400,277],{},[43,402,403],{"class":45,"line":76},[43,404,80],{"emptyLinePlaceholder":79},[43,406,407],{"class":45,"line":83},[43,408,409],{},"\u002F\u002F Deep graph with ThenInclude:\n",[43,411,412],{"class":45,"line":89},[43,413,386],{},[43,415,416],{"class":45,"line":95},[43,417,396],{},[43,419,420],{"class":45,"line":175},[43,421,422],{},"        .ThenInclude(i => i.Product)\n",[43,424,425],{"class":45,"line":181},[43,426,391],{},[43,428,429],{"class":45,"line":268},[43,430,431],{},"        .ThenInclude(c => c.Address)\n",[43,433,434],{"class":45,"line":274},[43,435,277],{},[43,437,438],{"class":45,"line":280},[43,439,80],{"emptyLinePlaceholder":79},[43,441,442],{"class":45,"line":285},[43,443,444],{},"\u002F\u002F Filtered include (EF Core 5+) — only active items:\n",[43,446,447],{"class":45,"line":291},[43,448,386],{},[43,450,451],{"class":45,"line":297},[43,452,453],{},"    .Include(o => o.Items.Where(i => !i.IsCancelled))\n",[43,455,456],{"class":45,"line":303},[43,457,458],{},"    .AsNoTracking()\n",[43,460,461],{"class":45,"line":308},[43,462,277],{},[10,464,466],{"id":465},"the-n1-problem","The N+1 problem",[15,468,469],{},"The N+1 problem is the most common EF Core performance issue interviewers ask about.",[34,471,473],{"className":36,"code":472,"language":38,"meta":39,"style":39},"\u002F\u002F N+1 — one query per customer:\nvar customers = await _db.Customers.ToListAsync(); \u002F\u002F Query 1\n\nforeach (var c in customers)\n    Console.WriteLine(c.Orders.Count); \u002F\u002F Query 2..N+1 (one per customer)\n\u002F\u002F 100 customers = 101 queries\n\n\u002F\u002F Fix 1 — eager load:\nvar customers = await _db.Customers\n    .Include(c => c.Orders)\n    .ToListAsync(); \u002F\u002F 1 query with JOIN\n\n\u002F\u002F Fix 2 — project the count into the query:\nvar data = await _db.Customers\n    .Select(c => new { c.Name, OrderCount = c.Orders.Count })\n    .ToListAsync(); \u002F\u002F COUNT(*) in a subquery — 1 SQL statement\n",[25,474,475,480,485,489,494,499,504,508,513,518,523,528,532,537,542,547],{"__ignoreMap":39},[43,476,477],{"class":45,"line":46},[43,478,479],{},"\u002F\u002F N+1 — one query per customer:\n",[43,481,482],{"class":45,"line":52},[43,483,484],{},"var customers = await _db.Customers.ToListAsync(); \u002F\u002F Query 1\n",[43,486,487],{"class":45,"line":58},[43,488,80],{"emptyLinePlaceholder":79},[43,490,491],{"class":45,"line":64},[43,492,493],{},"foreach (var c in customers)\n",[43,495,496],{"class":45,"line":70},[43,497,498],{},"    Console.WriteLine(c.Orders.Count); \u002F\u002F Query 2..N+1 (one per customer)\n",[43,500,501],{"class":45,"line":76},[43,502,503],{},"\u002F\u002F 100 customers = 101 queries\n",[43,505,506],{"class":45,"line":83},[43,507,80],{"emptyLinePlaceholder":79},[43,509,510],{"class":45,"line":89},[43,511,512],{},"\u002F\u002F Fix 1 — eager load:\n",[43,514,515],{"class":45,"line":95},[43,516,517],{},"var customers = await _db.Customers\n",[43,519,520],{"class":45,"line":175},[43,521,522],{},"    .Include(c => c.Orders)\n",[43,524,525],{"class":45,"line":181},[43,526,527],{},"    .ToListAsync(); \u002F\u002F 1 query with JOIN\n",[43,529,530],{"class":45,"line":268},[43,531,80],{"emptyLinePlaceholder":79},[43,533,534],{"class":45,"line":274},[43,535,536],{},"\u002F\u002F Fix 2 — project the count into the query:\n",[43,538,539],{"class":45,"line":280},[43,540,541],{},"var data = await _db.Customers\n",[43,543,544],{"class":45,"line":285},[43,545,546],{},"    .Select(c => new { c.Name, OrderCount = c.Orders.Count })\n",[43,548,549],{"class":45,"line":291},[43,550,551],{},"    .ToListAsync(); \u002F\u002F COUNT(*) in a subquery — 1 SQL statement\n",[15,553,554],{},"Enable SQL logging in development to detect N+1 before it reaches production:",[34,556,558],{"className":36,"code":557,"language":38,"meta":39,"style":39},"options.LogTo(Console.WriteLine, LogLevel.Information);\n",[25,559,560],{"__ignoreMap":39},[43,561,562],{"class":45,"line":46},[43,563,557],{},[10,565,567],{"id":566},"lazy-loading-why-its-dangerous-in-web-apis","Lazy loading — why it's dangerous in web APIs",[15,569,570],{},"Lazy loading automatically fires a query when a navigation property is first accessed.\nIn web APIs this means N+1 queries per endpoint call, invisible in the code:",[34,572,574],{"className":36,"code":573,"language":38,"meta":39,"style":39},"\u002F\u002F Enable:\noptions.UseLazyLoadingProxies();\n\u002F\u002F Navigations must be virtual.\n\n\u002F\u002F Silent N+1 — one query per order:\nvar orders = await _db.Orders.ToListAsync();\nforeach (var o in orders)\n    Console.WriteLine(o.Customer.Name); \u002F\u002F +1 query per order\n\n\u002F\u002F Disposed context exception:\nOrder GetOrder() { using var db = ...; return db.Orders.Find(1); }\nvar o = GetOrder();\nConsole.WriteLine(o.Customer.Name); \u002F\u002F ObjectDisposedException\n",[25,575,576,581,586,591,595,600,604,609,614,618,623,628,633],{"__ignoreMap":39},[43,577,578],{"class":45,"line":46},[43,579,580],{},"\u002F\u002F Enable:\n",[43,582,583],{"class":45,"line":52},[43,584,585],{},"options.UseLazyLoadingProxies();\n",[43,587,588],{"class":45,"line":58},[43,589,590],{},"\u002F\u002F Navigations must be virtual.\n",[43,592,593],{"class":45,"line":64},[43,594,80],{"emptyLinePlaceholder":79},[43,596,597],{"class":45,"line":70},[43,598,599],{},"\u002F\u002F Silent N+1 — one query per order:\n",[43,601,602],{"class":45,"line":76},[43,603,221],{},[43,605,606],{"class":45,"line":83},[43,607,608],{},"foreach (var o in orders)\n",[43,610,611],{"class":45,"line":89},[43,612,613],{},"    Console.WriteLine(o.Customer.Name); \u002F\u002F +1 query per order\n",[43,615,616],{"class":45,"line":95},[43,617,80],{"emptyLinePlaceholder":79},[43,619,620],{"class":45,"line":175},[43,621,622],{},"\u002F\u002F Disposed context exception:\n",[43,624,625],{"class":45,"line":181},[43,626,627],{},"Order GetOrder() { using var db = ...; return db.Orders.Find(1); }\n",[43,629,630],{"class":45,"line":268},[43,631,632],{},"var o = GetOrder();\n",[43,634,635],{"class":45,"line":274},[43,636,637],{},"Console.WriteLine(o.Customer.Name); \u002F\u002F ObjectDisposedException\n",[15,639,640,641,643],{},"Disable lazy loading in web APIs. Use explicit ",[25,642,366],{}," or projections so every database\ncall is visible and intentional.",[10,645,647],{"id":646},"split-queries-avoiding-cartesian-explosion","Split queries — avoiding Cartesian explosion",[15,649,650],{},"Including two collection navigations multiplies rows: Orders × Items × Tags.",[34,652,654],{"className":36,"code":653,"language":38,"meta":39,"style":39},"\u002F\u002F Cartesian explosion — 100 orders × 10 items × 5 tags = 5,000 rows:\nvar orders = await _db.Orders\n    .Include(o => o.Items)\n    .Include(o => o.Tags)\n    .ToListAsync();\n\n\u002F\u002F AsSplitQuery — 3 SQL queries, one per table, no Cartesian product:\nvar orders = await _db.Orders\n    .Include(o => o.Items)\n    .Include(o => o.Tags)\n    .AsSplitQuery()\n    .ToListAsync();\n\n\u002F\u002F Set as global default:\noptions.UseSqlServer(conn, sql =>\n    sql.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));\n",[25,655,656,661,665,669,674,678,682,687,691,695,699,704,708,712,717,722],{"__ignoreMap":39},[43,657,658],{"class":45,"line":46},[43,659,660],{},"\u002F\u002F Cartesian explosion — 100 orders × 10 items × 5 tags = 5,000 rows:\n",[43,662,663],{"class":45,"line":52},[43,664,386],{},[43,666,667],{"class":45,"line":58},[43,668,396],{},[43,670,671],{"class":45,"line":64},[43,672,673],{},"    .Include(o => o.Tags)\n",[43,675,676],{"class":45,"line":70},[43,677,277],{},[43,679,680],{"class":45,"line":76},[43,681,80],{"emptyLinePlaceholder":79},[43,683,684],{"class":45,"line":83},[43,685,686],{},"\u002F\u002F AsSplitQuery — 3 SQL queries, one per table, no Cartesian product:\n",[43,688,689],{"class":45,"line":89},[43,690,386],{},[43,692,693],{"class":45,"line":95},[43,694,396],{},[43,696,697],{"class":45,"line":175},[43,698,673],{},[43,700,701],{"class":45,"line":181},[43,702,703],{},"    .AsSplitQuery()\n",[43,705,706],{"class":45,"line":268},[43,707,277],{},[43,709,710],{"class":45,"line":274},[43,711,80],{"emptyLinePlaceholder":79},[43,713,714],{"class":45,"line":280},[43,715,716],{},"\u002F\u002F Set as global default:\n",[43,718,719],{"class":45,"line":285},[43,720,721],{},"options.UseSqlServer(conn, sql =>\n",[43,723,724],{"class":45,"line":291},[43,725,726],{},"    sql.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));\n",[15,728,729],{},"Split query trade-off: multiple round trips, no transaction between them. Use it for\nmultiple collection includes; stick to single query for reference navigations.",[10,731,733],{"id":732},"raw-sql","Raw SQL",[15,735,736],{},"Use raw SQL when LINQ can't express the operation:",[34,738,740],{"className":36,"code":739,"language":38,"meta":39,"style":39},"\u002F\u002F FromSqlInterpolated — returns entities, parameterised automatically:\nstring status = \"Pending\";\nvar orders = await _db.Orders\n    .FromSqlInterpolated($\"SELECT * FROM Orders WHERE Status = {status}\")\n    .Where(o => o.Total > 100)   \u002F\u002F LINQ composable on top\n    .ToListAsync();\n\n\u002F\u002F ExecuteSqlInterpolatedAsync — non-query statements:\nawait _db.Database.ExecuteSqlInterpolatedAsync(\n    $\"UPDATE Products SET Stock = Stock - {quantity} WHERE Id = {productId}\");\n\n\u002F\u002F Never concatenate user input:\nvar sql = $\"SELECT * FROM Orders WHERE Status = '{userInput}'\"; \u002F\u002F SQL injection\n",[25,741,742,747,752,756,761,766,770,774,779,784,789,793,798],{"__ignoreMap":39},[43,743,744],{"class":45,"line":46},[43,745,746],{},"\u002F\u002F FromSqlInterpolated — returns entities, parameterised automatically:\n",[43,748,749],{"class":45,"line":52},[43,750,751],{},"string status = \"Pending\";\n",[43,753,754],{"class":45,"line":58},[43,755,386],{},[43,757,758],{"class":45,"line":64},[43,759,760],{},"    .FromSqlInterpolated($\"SELECT * FROM Orders WHERE Status = {status}\")\n",[43,762,763],{"class":45,"line":70},[43,764,765],{},"    .Where(o => o.Total > 100)   \u002F\u002F LINQ composable on top\n",[43,767,768],{"class":45,"line":76},[43,769,277],{},[43,771,772],{"class":45,"line":83},[43,773,80],{"emptyLinePlaceholder":79},[43,775,776],{"class":45,"line":89},[43,777,778],{},"\u002F\u002F ExecuteSqlInterpolatedAsync — non-query statements:\n",[43,780,781],{"class":45,"line":95},[43,782,783],{},"await _db.Database.ExecuteSqlInterpolatedAsync(\n",[43,785,786],{"class":45,"line":175},[43,787,788],{},"    $\"UPDATE Products SET Stock = Stock - {quantity} WHERE Id = {productId}\");\n",[43,790,791],{"class":45,"line":181},[43,792,80],{"emptyLinePlaceholder":79},[43,794,795],{"class":45,"line":268},[43,796,797],{},"\u002F\u002F Never concatenate user input:\n",[43,799,800],{"class":45,"line":274},[43,801,802],{},"var sql = $\"SELECT * FROM Orders WHERE Status = '{userInput}'\"; \u002F\u002F SQL injection\n",[15,804,805,806,107,809,812],{},"Always use parameterised overloads (",[25,807,808],{},"Interpolated",[25,810,811],{},"{0}"," placeholders, not string concat).",[10,814,816],{"id":815},"async-queries","Async queries",[15,818,819],{},"Always use async EF Core methods in ASP.NET Core:",[34,821,823],{"className":36,"code":822,"language":38,"meta":39,"style":39},"\u002F\u002F Thread released to pool while DB executes:\nvar orders = await _db.Orders.ToListAsync(cancellationToken);\n\n\u002F\u002F Thread blocks the entire DB round trip:\nvar orders = _db.Orders.ToList();\n\n\u002F\u002F Deadlock risk in ASP.NET Core:\nvar orders = _db.Orders.ToListAsync().Result;\n",[25,824,825,830,835,839,844,849,853,858],{"__ignoreMap":39},[43,826,827],{"class":45,"line":46},[43,828,829],{},"\u002F\u002F Thread released to pool while DB executes:\n",[43,831,832],{"class":45,"line":52},[43,833,834],{},"var orders = await _db.Orders.ToListAsync(cancellationToken);\n",[43,836,837],{"class":45,"line":58},[43,838,80],{"emptyLinePlaceholder":79},[43,840,841],{"class":45,"line":64},[43,842,843],{},"\u002F\u002F Thread blocks the entire DB round trip:\n",[43,845,846],{"class":45,"line":70},[43,847,848],{},"var orders = _db.Orders.ToList();\n",[43,850,851],{"class":45,"line":76},[43,852,80],{"emptyLinePlaceholder":79},[43,854,855],{"class":45,"line":83},[43,856,857],{},"\u002F\u002F Deadlock risk in ASP.NET Core:\n",[43,859,860],{"class":45,"line":89},[43,861,862],{},"var orders = _db.Orders.ToListAsync().Result;\n",[15,864,865,866,869],{},"Pass ",[25,867,868],{},"CancellationToken"," from the endpoint so abandoned requests (client disconnect) release\nthe connection:",[34,871,873],{"className":36,"code":872,"language":38,"meta":39,"style":39},"public async Task\u003CList\u003COrder>> GetAsync(CancellationToken ct)\n    => await _db.Orders.AsNoTracking().ToListAsync(ct);\n",[25,874,875,880],{"__ignoreMap":39},[43,876,877],{"class":45,"line":46},[43,878,879],{},"public async Task\u003CList\u003COrder>> GetAsync(CancellationToken ct)\n",[43,881,882],{"class":45,"line":52},[43,883,884],{},"    => await _db.Orders.AsNoTracking().ToListAsync(ct);\n",[15,886,887,888,891],{},"Stream large results with ",[25,889,890],{},"AsAsyncEnumerable()"," — one row at a time, no full buffer:",[34,893,895],{"className":36,"code":894,"language":38,"meta":39,"style":39},"await foreach (var order in _db.Orders.AsAsyncEnumerable())\n    await ProcessAsync(order);\n",[25,896,897,902],{"__ignoreMap":39},[43,898,899],{"class":45,"line":46},[43,900,901],{},"await foreach (var order in _db.Orders.AsAsyncEnumerable())\n",[43,903,904],{"class":45,"line":52},[43,905,906],{},"    await ProcessAsync(order);\n",[10,908,910],{"id":909},"asnotracking-the-easiest-performance-win","AsNoTracking — the easiest performance win",[15,912,913,916],{},[25,914,915],{},"AsNoTracking()"," skips change tracker registration. It's 10–20% faster and uses less memory:",[34,918,920],{"className":36,"code":919,"language":38,"meta":39,"style":39},"var orders = await _db.Orders\n    .AsNoTracking()\n    .Where(o => o.Status == \"Pending\")\n    .ToListAsync();\n",[25,921,922,926,930,934],{"__ignoreMap":39},[43,923,924],{"class":45,"line":46},[43,925,386],{},[43,927,928],{"class":45,"line":52},[43,929,458],{},[43,931,932],{"class":45,"line":58},[43,933,143],{},[43,935,936],{"class":45,"line":64},[43,937,277],{},[15,939,940],{},"Use it on every read-only path (GET endpoints, reports, search). Only track entities you\nplan to modify and save.",[10,942,944],{"id":943},"compiled-queries-for-very-hot-paths","Compiled queries — for very hot paths",[15,946,947],{},"Pre-compile LINQ to SQL once at startup for fixed-shape queries called thousands of times\nper second:",[34,949,951],{"className":36,"code":950,"language":38,"meta":39,"style":39},"private static readonly Func\u003CAppDbContext, int, Task\u003COrder?>> GetOrderById =\n    EF.CompileAsyncQuery(\n        (AppDbContext db, int id) => db.Orders.FirstOrDefault(o => o.Id == id));\n\n\u002F\u002F Usage — no translation overhead per call:\nvar order = await GetOrderById(_db, 42);\n",[25,952,953,958,963,968,972,977],{"__ignoreMap":39},[43,954,955],{"class":45,"line":46},[43,956,957],{},"private static readonly Func\u003CAppDbContext, int, Task\u003COrder?>> GetOrderById =\n",[43,959,960],{"class":45,"line":52},[43,961,962],{},"    EF.CompileAsyncQuery(\n",[43,964,965],{"class":45,"line":58},[43,966,967],{},"        (AppDbContext db, int id) => db.Orders.FirstOrDefault(o => o.Id == id));\n",[43,969,970],{"class":45,"line":64},[43,971,80],{"emptyLinePlaceholder":79},[43,973,974],{"class":45,"line":70},[43,975,976],{},"\u002F\u002F Usage — no translation overhead per call:\n",[43,978,979],{"class":45,"line":76},[43,980,981],{},"var order = await GetOrderById(_db, 42);\n",[15,983,984],{},"EF Core caches most queries internally; compiled queries help only on very hot, fixed-shape\npaths. Measure before using.",[10,986,988],{"id":987},"recap","Recap",[15,990,991],{},"The key querying rules for EF Core interviews:",[993,994,995,1002,1009,1019,1025,1030,1040,1043],"ol",{},[996,997,998,999,1001],"li",{},"Keep queries ",[25,1000,27],{}," until the final materialisation.",[996,1003,1004,1005,1008],{},"Use projections (",[25,1006,1007],{},"Select",") for read-only paths — only load the columns you need.",[996,1010,1011,1012,1014,1015,1018],{},"Use ",[25,1013,366],{}," \u002F ",[25,1016,1017],{},"ThenInclude"," to avoid null navigation properties and N+1 queries.",[996,1020,1011,1021,1024],{},[25,1022,1023],{},"AsSplitQuery"," when including two or more collection navigations.",[996,1026,1011,1027,1029],{},[25,1028,915],{}," on every read-only query.",[996,1031,1032,1033,1036,1037,1039],{},"Always use ",[25,1034,1035],{},"*Async"," methods and pass ",[25,1038,868],{},".",[996,1041,1042],{},"Disable lazy loading in web APIs — it hides N+1 queries.",[996,1044,1045],{},"Use parameterised raw SQL overloads to prevent SQL injection.",[1047,1048,1049],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":39,"searchDepth":52,"depth":52,"links":1051},[1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064],{"id":12,"depth":52,"text":13},{"id":20,"depth":52,"text":21},{"id":114,"depth":52,"text":115},{"id":198,"depth":52,"text":199},{"id":360,"depth":52,"text":361},{"id":465,"depth":52,"text":466},{"id":566,"depth":52,"text":567},{"id":646,"depth":52,"text":647},{"id":732,"depth":52,"text":733},{"id":815,"depth":52,"text":816},{"id":909,"depth":52,"text":910},{"id":943,"depth":52,"text":944},{"id":987,"depth":52,"text":988},"How EF Core translates LINQ to SQL — deferred execution, the IQueryable vs IEnumerable distinction that causes full table scans, the N+1 trap, and the query options that actually matter for performance.","medium","md",".NET Core","dotnet",{},"\u002Fblog\u002Fdotnet-ef-querying","\u002Fdotnet\u002Fentity-framework\u002Fquerying",{"title":5,"description":1065},"blog\u002Fdotnet-ef-querying","Querying","Entity Framework Core","entity-framework","2026-06-23","ptbzij9FKw4cZbw_KhmeagkIE7gxFrxb1LyG689RK2g",1782244086311]