<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Juan Gómez</title>
    <description>The latest articles on DEV Community by Juan Gómez (@jgomezdev).</description>
    <link>https://dev.to/jgomezdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1592628%2Fc54806ef-8072-4425-9b0e-edd50de74e2d.png</url>
      <title>DEV Community: Juan Gómez</title>
      <link>https://dev.to/jgomezdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jgomezdev"/>
    <language>en</language>
    <item>
      <title>Stop Squinting at .NET Stack Traces: Debug Them With Claude Inside Visual Studio</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Fri, 17 Jul 2026 13:35:22 +0000</pubDate>
      <link>https://dev.to/jgomezdev/stop-squinting-at-net-stack-traces-debug-them-with-claude-inside-visual-studio-3gc5</link>
      <guid>https://dev.to/jgomezdev/stop-squinting-at-net-stack-traces-debug-them-with-claude-inside-visual-studio-3gc5</guid>
      <description>&lt;h1&gt;
  
  
  Stop Squinting at .NET Stack Traces: Debug Them With Claude Inside Visual Studio
&lt;/h1&gt;

&lt;p&gt;An unhandled exception fires, and Visual Studio drops forty lines of stack trace into your lap. Thirty-eight of them live in &lt;code&gt;Microsoft.EntityFrameworkCore.*&lt;/code&gt; or &lt;code&gt;System.Private.CoreLib&lt;/code&gt;, one is an async state-machine frame with a name like &lt;code&gt;&amp;lt;&amp;lt;Main&amp;gt;$&amp;gt;b__0_0&lt;/code&gt;, and exactly one points at code you wrote. Your job is to find that one line, understand &lt;em&gt;why&lt;/em&gt; it blew up, and fix the actual cause — not the symptom.&lt;/p&gt;

&lt;p&gt;We've all pasted a trace into a search engine and gotten back a single Stack Overflow question from 2015, zero answers, one comment that just says &lt;em&gt;"did you ever solve this?"&lt;/em&gt;. There's a better way, and it now lives right inside Visual Studio: hand the trace to Claude, get back a ranked diagnosis tied to &lt;em&gt;your&lt;/em&gt; line, and a fix you can actually reason about.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faa9e1z9p0zpdoklahs8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faa9e1z9p0zpdoklahs8y.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why .NET Stack Traces Are Hard to Read
&lt;/h2&gt;

&lt;p&gt;Stack traces aren't hard because they're long. They're hard because modern .NET code rarely runs in a straight line, and the trace reflects the machinery, not your intent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Async unwinding.&lt;/strong&gt; &lt;code&gt;await&lt;/code&gt; compiles into a state machine. When an exception crosses an &lt;code&gt;await&lt;/code&gt;, the frames you see are &lt;code&gt;MoveNext()&lt;/code&gt; calls and &lt;code&gt;ExecutionContext.Run&lt;/code&gt;, not the tidy call chain you wrote. The method name shows up as &lt;code&gt;&amp;lt;SomethingAsync&amp;gt;d__4&lt;/code&gt;, and the line that &lt;em&gt;logically&lt;/em&gt; threw might be three awaits up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inner exceptions.&lt;/strong&gt; The message on top is often the &lt;em&gt;least&lt;/em&gt; useful one. The real cause is nested — a &lt;code&gt;DbUpdateException&lt;/code&gt; wrapping a &lt;code&gt;SqliteException&lt;/code&gt;, or a &lt;code&gt;TargetInvocationException&lt;/code&gt; wrapping the thing you actually care about. If you only read the first line, you're debugging the wrapper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AggregateException&lt;/code&gt;.&lt;/strong&gt; Anything involving &lt;code&gt;Task.WhenAll&lt;/code&gt; or parallelism can bundle several failures into one. The outer message says "One or more errors occurred" — thanks, very helpful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inlined and framework frames.&lt;/strong&gt; The JIT inlines small methods, so frames disappear. And the frames that remain are mostly library internals you can't change and don't need to read.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An LLM is genuinely good at this specific chore. It has seen these exception shapes thousands of times, it can tell the framework noise from the signal, and — this is the part that matters — when it runs &lt;em&gt;inside your IDE&lt;/em&gt;, it can correlate the trace with the actual source file and line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F40bpqexhwn4kp6errap0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F40bpqexhwn4kp6errap0.png" alt=" " width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Two Ways to Put Claude Inside Visual Studio
&lt;/h2&gt;

&lt;p&gt;You don't need to leave the IDE or paste anything into a browser. There are two solid paths, and they suit different moments.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Copilot Chat with a Claude model (in-IDE, conversational)
&lt;/h3&gt;

&lt;p&gt;Visual Studio 2022 (17.14+) ships GitHub Copilot with a &lt;strong&gt;model picker&lt;/strong&gt;. Click the Copilot badge, open the model dropdown in the chat prompt box, and pick a Claude model — Sonnet for fast everyday diagnosis, Opus when the bug is genuinely nasty. From then on, every Copilot Chat reply is generated by Claude.&lt;/p&gt;

&lt;p&gt;Two things make this path shine for debugging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When an unhandled exception pops the &lt;strong&gt;Exception Helper&lt;/strong&gt; while debugging, there's an inline &lt;strong&gt;Ask Copilot&lt;/strong&gt; link right on that dialog. With Claude selected as your model, that one-click analysis runs on Claude.&lt;/li&gt;
&lt;li&gt;In the chat box you can pull real context in with &lt;code&gt;#&lt;/code&gt; references — &lt;code&gt;#Program.cs&lt;/code&gt;, &lt;code&gt;#solution&lt;/code&gt;, or a highlighted selection — so Claude reads your &lt;em&gt;actual&lt;/em&gt; code, not a paraphrase.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd rather bring your own Anthropic key instead of using Copilot's models, the same picker has &lt;strong&gt;Manage Models&lt;/strong&gt; → add Anthropic → paste your API key. Handy if you're already paying for API access.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Claude Code in the integrated terminal (agentic)
&lt;/h3&gt;

&lt;p&gt;There's no official Claude Code extension for the full Visual Studio yet, but you don't need one — Claude Code runs in any terminal, including &lt;strong&gt;View → Terminal&lt;/strong&gt; (&lt;code&gt;Ctrl+&lt;/code&gt;&lt;code&gt;) inside VS. Run&lt;/code&gt;claude`, paste the trace, and because Claude Code can read the whole repository, it will open the offending file itself, trace the call path, and propose the fix as a diff you approve or reject.&lt;/p&gt;

&lt;p&gt;The trade-off is simple: Copilot Chat is faster for a quick "what does this mean?", while Claude Code is better when the fix touches several files or you want it to reproduce and verify. Use whichever the bug deserves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9jmxtctys4j3vmjhp45r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9jmxtctys4j3vmjhp45r.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A Real Example: The EF Core Trace That Blames the Wrong Thing
&lt;/h2&gt;

&lt;p&gt;Let's debug something concrete. Here's a .NET 10 minimal API endpoint that returns an order summary. It looks reasonable, and it compiles cleanly:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`csharp&lt;br&gt;
// Program.cs — .NET 10 minimal API&lt;br&gt;
using Microsoft.EntityFrameworkCore;&lt;/p&gt;

&lt;p&gt;var builder = WebApplication.CreateBuilder(args);&lt;br&gt;
builder.Services.AddDbContext(o =&amp;gt; o.UseSqlite("Data Source=shop.db"));&lt;/p&gt;

&lt;p&gt;var app = builder.Build();&lt;/p&gt;

&lt;p&gt;app.MapGet("/orders/{customerId:int}/summary", async (int customerId, ShopContext db) =&amp;gt;&lt;br&gt;
{&lt;br&gt;
    // Fire both queries "in parallel" to be fast — right?&lt;br&gt;
    var ordersTask = db.Orders.Where(o =&amp;gt; o.CustomerId == customerId).ToListAsync();&lt;br&gt;
    var totalTask  = db.Orders.Where(o =&amp;gt; o.CustomerId == customerId).SumAsync(o =&amp;gt; o.Total);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await Task.WhenAll(ordersTask, totalTask);

return Results.Ok(new { Orders = ordersTask.Result, Total = totalTask.Result });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;});&lt;/p&gt;

&lt;p&gt;app.Run();&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Hit the endpoint and it throws. Here's the trace Visual Studio shows (trimmed to the frames that appear):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
System.InvalidOperationException: A second operation was started on this context&lt;br&gt;
instance before a previous operation completed. This is usually caused by different&lt;br&gt;
threads concurrently using the same instance of DbContext. For more information on&lt;br&gt;
how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.&lt;br&gt;
   at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()&lt;br&gt;
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken ct)&lt;br&gt;
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken ct)&lt;br&gt;
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable&lt;/code&gt;1 source, CancellationToken ct)&lt;br&gt;
   at Program.&amp;lt;&amp;gt;c.&amp;lt;&lt;/p&gt;$&amp;gt;b__0_0(Int32 customerId, ShopContext db) in C:\src\Shop.Api\Program.cs:line 12&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;

&lt;p&gt;Four of those five frames are EF Core internals. The one that's yours — &lt;code&gt;Program.cs:line 12&lt;/code&gt; — is the &lt;code&gt;ToListAsync()&lt;/code&gt; call, which makes it &lt;em&gt;look&lt;/em&gt; like &lt;code&gt;ToListAsync&lt;/code&gt; is the villain. It isn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feeding it to Claude the right way
&lt;/h3&gt;

&lt;p&gt;The quality of the answer depends entirely on what you paste. A one-line message gets you a one-line guess. Give Claude the whole picture:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`text&lt;br&gt;
.NET 10, EF Core 10, SQLite. This endpoint throws on every request under normal load&lt;br&gt;
(not just concurrency). Full exception and the endpoint code below — what's the root&lt;br&gt;
cause, and what's the idiomatic fix?&lt;/p&gt;

&lt;p&gt;[paste the FULL exception via ex.ToString() — message + all inner exceptions + trace]&lt;/p&gt;

&lt;p&gt;[paste the MapGet handler]&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Three things make that prompt good:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The full exception, not the headline.&lt;/strong&gt; &lt;code&gt;ex.ToString()&lt;/code&gt; includes every inner exception and the complete trace. In Copilot Chat, &lt;code&gt;#Program.cs&lt;/code&gt; pulls the source in for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first frame that's &lt;em&gt;yours&lt;/em&gt;.&lt;/strong&gt; Point Claude at &lt;code&gt;Program.cs:line 12&lt;/code&gt; and the code around it. The framework frames are context; your frame is the crime scene.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The conditions.&lt;/strong&gt; "Every request" vs "only under load" vs "intermittent" changes the diagnosis completely. Say what you observed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What Claude tells you
&lt;/h3&gt;

&lt;p&gt;Instead of "line 12 failed", you get the actual mechanism: EF Core's &lt;code&gt;DbContext&lt;/code&gt; is &lt;strong&gt;not thread-safe and does not allow two operations on the same instance at once&lt;/strong&gt;. &lt;code&gt;Task.WhenAll&lt;/code&gt; starts the second query before the first finishes, both on the same injected &lt;code&gt;db&lt;/code&gt;, and EF's &lt;code&gt;ConcurrencyDetector&lt;/code&gt; throws. The line number was a red herring — the bug is the &lt;em&gt;pattern&lt;/em&gt;, not the call.&lt;/p&gt;

&lt;p&gt;And it gives you both fixes, with the trade-off:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;csharp&lt;br&gt;
// Fix A — simplest: run them sequentially. One context, one operation at a time.&lt;br&gt;
var orders = await db.Orders.Where(o =&amp;gt; o.CustomerId == customerId).ToListAsync();&lt;br&gt;
var total  = await db.Orders.Where(o =&amp;gt; o.CustomerId == customerId).SumAsync(o =&amp;gt; o.Total);&lt;br&gt;
return Results.Ok(new { Orders = orders, Total = total });&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`csharp&lt;br&gt;
// Fix B — real parallelism: give each query its OWN context via a factory.&lt;br&gt;
builder.Services.AddDbContextFactory(o =&amp;gt; o.UseSqlite("Data Source=shop.db"));&lt;/p&gt;

&lt;p&gt;app.MapGet("/orders/{customerId:int}/summary",&lt;br&gt;
    async (int customerId, IDbContextFactory factory) =&amp;gt;&lt;br&gt;
{&lt;br&gt;
    await using var ordersDb = await factory.CreateDbContextAsync();&lt;br&gt;
    await using var totalDb  = await factory.CreateDbContextAsync();&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var ordersTask = ordersDb.Orders.Where(o =&amp;gt; o.CustomerId == customerId).ToListAsync();
var totalTask  = totalDb.Orders.Where(o =&amp;gt; o.CustomerId == customerId).SumAsync(o =&amp;gt; o.Total);

await Task.WhenAll(ordersTask, totalTask);
return Results.Ok(new { Orders = await ordersTask, Total = await totalTask });
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Fix A is what you want 90% of the time — two SQLite queries are fast, and the parallelism was never worth the bug. Fix B is the answer when the queries are genuinely slow and independent, because &lt;code&gt;IDbContextFactory&amp;lt;T&amp;gt;&lt;/code&gt; hands you a fresh, short-lived context per operation. &lt;em&gt;That&lt;/em&gt; distinction — not the line number — is the thing you were actually missing, and it's the kind of judgment a good explanation gives you and a search result doesn't.&lt;/p&gt;

&lt;p&gt;A .NET async stack trace has two kinds of frames: the forty that belong to the runtime, and the one that's your fault. Claude's job is to skip the forty and explain the one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verify — Don't Trust Blindly
&lt;/h2&gt;

&lt;p&gt;Claude is a very fast senior colleague who is occasionally, confidently wrong. Treat its diagnosis as a strong hypothesis, not a verdict:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reproduce and confirm.&lt;/strong&gt; Apply the fix, re-run, and check the exception is actually gone — ideally under the same conditions that triggered it. In Visual Studio, set a breakpoint on the fixed line and watch the state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask &lt;em&gt;why&lt;/em&gt;, not just &lt;em&gt;what&lt;/em&gt;.&lt;/strong&gt; "Explain why this fixes it" surfaces a wrong root cause fast. If the reasoning doesn't hold, the fix probably doesn't either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for plausible-but-wrong causes.&lt;/strong&gt; For intermittent bugs especially, Claude may propose a cause that fits the trace but isn't yours. Make it rank candidates and tell you how to distinguish them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The debugger still owns &lt;em&gt;state&lt;/em&gt;. Claude owns &lt;em&gt;meaning&lt;/em&gt;. When you need to know the actual value of a variable at the moment of the crash, that's a watch window and a breakpoint, not a chat prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Reach for Claude — and When Not To
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reach for it when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The exception type or message is unfamiliar, or the trace is mostly framework internals.&lt;/li&gt;
&lt;li&gt;The real cause is buried in inner exceptions or an &lt;code&gt;AggregateException&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You're staring at async frames and can't tell which of your lines is the origin.&lt;/li&gt;
&lt;li&gt;You want the &lt;em&gt;idiomatic&lt;/em&gt; fix and the trade-offs, not just a patch that makes the red go away.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Don't bother when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You already know the cause — a &lt;code&gt;NullReferenceException&lt;/code&gt; on a line you just wrote doesn't need an LLM.&lt;/li&gt;
&lt;li&gt;You need the runtime value of something — use the debugger.&lt;/li&gt;
&lt;li&gt;The trace contains &lt;strong&gt;secrets or customer data&lt;/strong&gt;. Connection strings, tokens, and PII live in exception messages and payloads more often than you'd like. Scrub before you paste, especially with a cloud model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule of thumb: &lt;strong&gt;if you'd otherwise open a browser to search the exception, ask Claude instead — it's in the IDE, it can read your code, and it won't send you to a dead 2015 forum thread.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The trace shows machinery, not intent&lt;/strong&gt; — async state machines, inner exceptions, and framework frames bury the one line that's yours. Reading traces is pattern recognition, which is exactly where an LLM helps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude lives in Visual Studio two ways&lt;/strong&gt; — pick it in Copilot Chat's model picker for quick, conversational diagnosis; run Claude Code in the integrated terminal when the fix is agentic and spans files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context is the whole game&lt;/strong&gt; — paste the full &lt;code&gt;ex.ToString()&lt;/code&gt;, point at the first frame that's &lt;em&gt;yours&lt;/em&gt;, and describe when it happens. A headline message gets a headline guess.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The line number is often a red herring&lt;/strong&gt; — the EF Core example throws at &lt;code&gt;ToListAsync&lt;/code&gt;, but the bug is sharing one &lt;code&gt;DbContext&lt;/code&gt; across parallel queries. The value is the explanation, not the location.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify, then trust&lt;/strong&gt; — reproduce the fix, ask &lt;em&gt;why&lt;/em&gt; it works, and never paste secrets. The debugger owns state; Claude owns meaning.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Deja de Entrecerrar los Ojos con los Stack Traces de .NET: Depúralos con Claude en Visual Studio</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Fri, 17 Jul 2026 13:23:04 +0000</pubDate>
      <link>https://dev.to/jgomezdev/deja-de-entrecerrar-los-ojos-con-los-stack-traces-de-net-depuralos-con-claude-en-visual-studio-4cp1</link>
      <guid>https://dev.to/jgomezdev/deja-de-entrecerrar-los-ojos-con-los-stack-traces-de-net-depuralos-con-claude-en-visual-studio-4cp1</guid>
      <description>&lt;h1&gt;
  
  
  Deja de Entrecerrar los Ojos con los Stack Traces de .NET: Depúralos con Claude en Visual Studio
&lt;/h1&gt;

&lt;p&gt;Salta una excepción no controlada y Visual Studio te suelta cuarenta líneas de stack trace en el regazo. Treinta y ocho viven en &lt;code&gt;Microsoft.EntityFrameworkCore.*&lt;/code&gt; o en &lt;code&gt;System.Private.CoreLib&lt;/code&gt;, una es un frame de máquina de estados asíncrona con un nombre tipo &lt;code&gt;&amp;lt;&amp;lt;Main&amp;gt;$&amp;gt;b__0_0&lt;/code&gt;, y exactamente una apunta a código que escribiste tú. Tu trabajo es encontrar esa línea, entender &lt;em&gt;por qué&lt;/em&gt; explotó y arreglar la causa real — no el síntoma.&lt;/p&gt;

&lt;p&gt;Todos hemos pegado un trace en el buscador y recibido a cambio una sola pregunta de Stack Overflow de 2015, cero respuestas, y un único comentario que dice &lt;em&gt;"¿al final lo resolviste?"&lt;/em&gt;. Hay una forma mejor, y ahora vive dentro de Visual Studio: le pasas el trace a Claude y recibes un diagnóstico ordenado por probabilidad, atado a &lt;em&gt;tu&lt;/em&gt; línea, con un arreglo que de verdad puedes razonar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F38a75kh943zg78g4kwfa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F38a75kh943zg78g4kwfa.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Por Qué Cuesta Tanto Leer un Stack Trace de .NET
&lt;/h2&gt;

&lt;p&gt;Los stack traces no son difíciles por ser largos. Son difíciles porque el código moderno de .NET casi nunca corre en línea recta, y el trace refleja la maquinaria, no tu intención.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Desenrollado asíncrono.&lt;/strong&gt; Un &lt;code&gt;await&lt;/code&gt; compila a una máquina de estados. Cuando una excepción cruza un &lt;code&gt;await&lt;/code&gt;, los frames que ves son llamadas a &lt;code&gt;MoveNext()&lt;/code&gt; y &lt;code&gt;ExecutionContext.Run&lt;/code&gt;, no la cadena de llamadas ordenada que escribiste. El método aparece como &lt;code&gt;&amp;lt;AlgoAsync&amp;gt;d__4&lt;/code&gt;, y la línea que &lt;em&gt;lógicamente&lt;/em&gt; lanzó puede estar tres &lt;code&gt;await&lt;/code&gt; más arriba.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excepciones internas (inner exceptions).&lt;/strong&gt; El mensaje de arriba suele ser el &lt;em&gt;menos&lt;/em&gt; útil. La causa real está anidada — un &lt;code&gt;DbUpdateException&lt;/code&gt; envolviendo un &lt;code&gt;SqliteException&lt;/code&gt;, o un &lt;code&gt;TargetInvocationException&lt;/code&gt; envolviendo lo que de verdad importa. Si solo lees la primera línea, estás depurando el envoltorio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AggregateException&lt;/code&gt;.&lt;/strong&gt; Cualquier cosa con &lt;code&gt;Task.WhenAll&lt;/code&gt; o paralelismo puede empaquetar varios fallos en uno. El mensaje externo dice "Se produjeron uno o más errores" — gracias, utilísimo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frames inlineados y de framework.&lt;/strong&gt; El JIT hace inline de métodos pequeños, así que hay frames que desaparecen. Y los que quedan son en su mayoría internos de librerías que no puedes cambiar ni necesitas leer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un LLM es genuinamente bueno para esta tarea concreta. Ha visto estas formas de excepción miles de veces, sabe separar el ruido del framework de la señal y — esto es lo clave — cuando corre &lt;em&gt;dentro de tu IDE&lt;/em&gt;, puede correlacionar el trace con el archivo y la línea reales.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2wamllim3vm5mae83p2s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2wamllim3vm5mae83p2s.png" alt=" " width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Dos Formas de Meter a Claude Dentro de Visual Studio
&lt;/h2&gt;

&lt;p&gt;No necesitas salir del IDE ni pegar nada en un navegador. Hay dos caminos sólidos, y cada uno sirve para un momento distinto.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Copilot Chat con un modelo Claude (dentro del IDE, conversacional)
&lt;/h3&gt;

&lt;p&gt;Visual Studio 2022 (17.14+) trae GitHub Copilot con un &lt;strong&gt;selector de modelo&lt;/strong&gt;. Haz clic en el badge de Copilot, abre el desplegable de modelos en la caja del chat y elige un modelo Claude — Sonnet para el diagnóstico rápido del día a día, Opus cuando el bug es realmente feo. A partir de ahí, cada respuesta de Copilot Chat la genera Claude.&lt;/p&gt;

&lt;p&gt;Dos cosas hacen que este camino brille para depurar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cuando una excepción no controlada abre el &lt;strong&gt;Asistente de excepciones&lt;/strong&gt; durante el debug, hay un enlace &lt;strong&gt;Preguntar a Copilot&lt;/strong&gt; justo en ese diálogo. Con Claude seleccionado como modelo, ese análisis de un clic corre sobre Claude.&lt;/li&gt;
&lt;li&gt;En la caja del chat puedes traer contexto real con referencias &lt;code&gt;#&lt;/code&gt; — &lt;code&gt;#Program.cs&lt;/code&gt;, &lt;code&gt;#solution&lt;/code&gt;, o una selección resaltada — para que Claude lea tu código &lt;em&gt;de verdad&lt;/em&gt;, no una paráfrasis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si prefieres usar tu propia clave de Anthropic en vez de los modelos de Copilot, el mismo selector tiene &lt;strong&gt;Manage Models&lt;/strong&gt; → agregar Anthropic → pegar tu API key. Cómodo si ya pagas por acceso a la API.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Claude Code en la terminal integrada (agéntico)
&lt;/h3&gt;

&lt;p&gt;Todavía no hay una extensión oficial de Claude Code para el Visual Studio completo, pero no la necesitas — Claude Code corre en cualquier terminal, incluida &lt;strong&gt;Ver → Terminal&lt;/strong&gt; (&lt;code&gt;Ctrl+&lt;/code&gt;&lt;code&gt;) dentro de VS. Ejecuta&lt;/code&gt;claude`, pega el trace y, como Claude Code puede leer todo el repositorio, abrirá él mismo el archivo culpable, seguirá la ruta de llamadas y propondrá el arreglo como un diff que apruebas o rechazas.&lt;/p&gt;

&lt;p&gt;El intercambio es simple: Copilot Chat es más rápido para un "¿qué significa esto?", mientras que Claude Code es mejor cuando el arreglo toca varios archivos o quieres que reproduzca y verifique. Usa el que el bug se merezca.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc5kruqyb0jxm0k939zf6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc5kruqyb0jxm0k939zf6.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Un Ejemplo Real: El Trace de EF Core Que Culpa a Quien No Es
&lt;/h2&gt;

&lt;p&gt;Depuremos algo concreto. Este es un endpoint de minimal API en .NET 10 que devuelve el resumen de pedidos de un cliente. Se ve razonable, y compila sin quejas:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`csharp&lt;br&gt;
// Program.cs — minimal API en .NET 10&lt;br&gt;
using Microsoft.EntityFrameworkCore;&lt;/p&gt;

&lt;p&gt;var builder = WebApplication.CreateBuilder(args);&lt;br&gt;
builder.Services.AddDbContext(o =&amp;gt; o.UseSqlite("Data Source=shop.db"));&lt;/p&gt;

&lt;p&gt;var app = builder.Build();&lt;/p&gt;

&lt;p&gt;app.MapGet("/orders/{customerId:int}/summary", async (int customerId, ShopContext db) =&amp;gt;&lt;br&gt;
{&lt;br&gt;
    // Disparamos ambas consultas "en paralelo" para ir rápido — ¿verdad?&lt;br&gt;
    var ordersTask = db.Orders.Where(o =&amp;gt; o.CustomerId == customerId).ToListAsync();&lt;br&gt;
    var totalTask  = db.Orders.Where(o =&amp;gt; o.CustomerId == customerId).SumAsync(o =&amp;gt; o.Total);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await Task.WhenAll(ordersTask, totalTask);

return Results.Ok(new { Orders = ordersTask.Result, Total = totalTask.Result });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;});&lt;/p&gt;

&lt;p&gt;app.Run();&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Llamas al endpoint y lanza. Este es el trace que muestra Visual Studio (recortado a los frames que aparecen):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
System.InvalidOperationException: A second operation was started on this context&lt;br&gt;
instance before a previous operation completed. This is usually caused by different&lt;br&gt;
threads concurrently using the same instance of DbContext. For more information on&lt;br&gt;
how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.&lt;br&gt;
   at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()&lt;br&gt;
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken ct)&lt;br&gt;
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken ct)&lt;br&gt;
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable&lt;/code&gt;1 source, CancellationToken ct)&lt;br&gt;
   at Program.&amp;lt;&amp;gt;c.&amp;lt;&lt;/p&gt;$&amp;gt;b__0_0(Int32 customerId, ShopContext db) in C:\src\Shop.Api\Program.cs:line 12&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;

&lt;p&gt;Cuatro de esos cinco frames son internos de EF Core. El único que es tuyo — &lt;code&gt;Program.cs:line 12&lt;/code&gt; — es la llamada a &lt;code&gt;ToListAsync()&lt;/code&gt;, lo que hace &lt;em&gt;parecer&lt;/em&gt; que &lt;code&gt;ToListAsync&lt;/code&gt; es el villano. No lo es.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cómo pasárselo a Claude bien
&lt;/h3&gt;

&lt;p&gt;La calidad de la respuesta depende por completo de lo que pegues. Un mensaje de una línea te da una conjetura de una línea. Dale a Claude el panorama completo:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`text&lt;br&gt;
.NET 10, EF Core 10, SQLite. Este endpoint lanza en cada request con carga normal&lt;br&gt;
(no solo con concurrencia). Abajo van la excepción completa y el código del endpoint —&lt;br&gt;
¿cuál es la causa raíz y cuál es el arreglo idiomático?&lt;/p&gt;

&lt;p&gt;[pega la excepción COMPLETA con ex.ToString() — mensaje + todas las inner exceptions + trace]&lt;/p&gt;

&lt;p&gt;[pega el handler del MapGet]&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Tres cosas hacen que ese prompt sea bueno:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;La excepción completa, no el titular.&lt;/strong&gt; &lt;code&gt;ex.ToString()&lt;/code&gt; incluye cada inner exception y el trace entero. En Copilot Chat, &lt;code&gt;#Program.cs&lt;/code&gt; te trae el código solo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El primer frame que es &lt;em&gt;tuyo&lt;/em&gt;.&lt;/strong&gt; Apunta a Claude a &lt;code&gt;Program.cs:line 12&lt;/code&gt; y al código a su alrededor. Los frames de framework son contexto; tu frame es la escena del crimen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Las condiciones.&lt;/strong&gt; "En cada request" vs "solo bajo carga" vs "intermitente" cambia el diagnóstico por completo. Di lo que observaste.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Lo que Claude te responde
&lt;/h3&gt;

&lt;p&gt;En vez de "la línea 12 falló", obtienes el mecanismo real: el &lt;code&gt;DbContext&lt;/code&gt; de EF Core &lt;strong&gt;no es thread-safe y no permite dos operaciones sobre la misma instancia a la vez&lt;/strong&gt;. &lt;code&gt;Task.WhenAll&lt;/code&gt; arranca la segunda consulta antes de que termine la primera, ambas sobre el mismo &lt;code&gt;db&lt;/code&gt; inyectado, y el &lt;code&gt;ConcurrencyDetector&lt;/code&gt; de EF lanza. El número de línea era una pista falsa — el bug es el &lt;em&gt;patrón&lt;/em&gt;, no la llamada.&lt;/p&gt;

&lt;p&gt;Y te da los dos arreglos, con el intercambio incluido:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;csharp&lt;br&gt;
// Arreglo A — el más simple: ejecútalas en secuencia. Un contexto, una operación a la vez.&lt;br&gt;
var orders = await db.Orders.Where(o =&amp;gt; o.CustomerId == customerId).ToListAsync();&lt;br&gt;
var total  = await db.Orders.Where(o =&amp;gt; o.CustomerId == customerId).SumAsync(o =&amp;gt; o.Total);&lt;br&gt;
return Results.Ok(new { Orders = orders, Total = total });&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`csharp&lt;br&gt;
// Arreglo B — paralelismo real: dale a cada consulta su PROPIO contexto vía factory.&lt;br&gt;
builder.Services.AddDbContextFactory(o =&amp;gt; o.UseSqlite("Data Source=shop.db"));&lt;/p&gt;

&lt;p&gt;app.MapGet("/orders/{customerId:int}/summary",&lt;br&gt;
    async (int customerId, IDbContextFactory factory) =&amp;gt;&lt;br&gt;
{&lt;br&gt;
    await using var ordersDb = await factory.CreateDbContextAsync();&lt;br&gt;
    await using var totalDb  = await factory.CreateDbContextAsync();&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var ordersTask = ordersDb.Orders.Where(o =&amp;gt; o.CustomerId == customerId).ToListAsync();
var totalTask  = totalDb.Orders.Where(o =&amp;gt; o.CustomerId == customerId).SumAsync(o =&amp;gt; o.Total);

await Task.WhenAll(ordersTask, totalTask);
return Results.Ok(new { Orders = await ordersTask, Total = await totalTask });
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;El Arreglo A es lo que quieres el 90% de las veces — dos consultas a SQLite son rápidas, y el paralelismo nunca valió el bug. El Arreglo B es la respuesta cuando las consultas son genuinamente lentas e independientes, porque &lt;code&gt;IDbContextFactory&amp;lt;T&amp;gt;&lt;/code&gt; te entrega un contexto fresco y de vida corta por operación. &lt;em&gt;Esa&lt;/em&gt; distinción — no el número de línea — es lo que de verdad te faltaba, y es el tipo de criterio que te da una buena explicación y no un resultado de búsqueda.&lt;/p&gt;

&lt;p&gt;Un stack trace asíncrono de .NET tiene dos tipos de frames: los cuarenta que le pertenecen al runtime, y el que es culpa tuya. El trabajo de Claude es saltarse los cuarenta y explicarte el uno.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verifica — No Confíes a Ciegas
&lt;/h2&gt;

&lt;p&gt;Claude es un colega senior muy rápido que de vez en cuando se equivoca con toda seguridad. Trata su diagnóstico como una hipótesis fuerte, no como un veredicto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reproduce y confirma.&lt;/strong&gt; Aplica el arreglo, vuelve a correr y comprueba que la excepción de verdad desapareció — idealmente bajo las mismas condiciones que la dispararon. En Visual Studio, pon un breakpoint en la línea corregida y observa el estado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pregunta el &lt;em&gt;por qué&lt;/em&gt;, no solo el &lt;em&gt;qué&lt;/em&gt;.&lt;/strong&gt; "Explica por qué esto lo arregla" saca a la luz una causa raíz equivocada rapidísimo. Si el razonamiento no se sostiene, el arreglo probablemente tampoco.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ojo con las causas plausibles pero falsas.&lt;/strong&gt; Sobre todo con bugs intermitentes, Claude puede proponer una causa que encaja con el trace pero que no es la tuya. Haz que ordene los candidatos y te diga cómo distinguirlos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El debugger sigue siendo el dueño del &lt;em&gt;estado&lt;/em&gt;. Claude es dueño del &lt;em&gt;significado&lt;/em&gt;. Cuando necesitas saber el valor real de una variable en el momento del crash, eso es una ventana de inspección y un breakpoint, no un prompt de chat.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cuándo Recurrir a Claude — y Cuándo No
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Recurre a él cuando:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El tipo o el mensaje de la excepción te resulta desconocido, o el trace es casi todo internos de framework.&lt;/li&gt;
&lt;li&gt;La causa real está enterrada en inner exceptions o en un &lt;code&gt;AggregateException&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Estás mirando frames asíncronos y no logras saber cuál de tus líneas es el origen.&lt;/li&gt;
&lt;li&gt;Quieres el arreglo &lt;em&gt;idiomático&lt;/em&gt; y sus intercambios, no solo un parche que quite el rojo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;No te molestes cuando:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ya sabes la causa — un &lt;code&gt;NullReferenceException&lt;/code&gt; en una línea que acabas de escribir no necesita un LLM.&lt;/li&gt;
&lt;li&gt;Necesitas el valor en runtime de algo — usa el debugger.&lt;/li&gt;
&lt;li&gt;El trace contiene &lt;strong&gt;secretos o datos de clientes&lt;/strong&gt;. Las cadenas de conexión, los tokens y los datos personales aparecen en mensajes de excepción y payloads más seguido de lo que te gustaría. Límpialos antes de pegar, especialmente con un modelo en la nube.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La regla de oro: &lt;strong&gt;si de otro modo abrirías el navegador para buscar la excepción, mejor pregúntale a Claude — está en el IDE, puede leer tu código, y no te mandará a un hilo de foro muerto de 2015.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Puntos Clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;El trace muestra maquinaria, no intención&lt;/strong&gt; — máquinas de estados asíncronas, inner exceptions y frames de framework entierran la única línea que es tuya. Leer traces es reconocimiento de patrones, que es justo donde ayuda un LLM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude vive en Visual Studio de dos formas&lt;/strong&gt; — elígelo en el selector de modelo de Copilot Chat para diagnóstico rápido y conversacional; corre Claude Code en la terminal integrada cuando el arreglo es agéntico y abarca varios archivos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El contexto lo es todo&lt;/strong&gt; — pega el &lt;code&gt;ex.ToString()&lt;/code&gt; completo, apunta al primer frame que es &lt;em&gt;tuyo&lt;/em&gt; y describe cuándo pasa. Un mensaje titular recibe una conjetura titular.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El número de línea suele ser una pista falsa&lt;/strong&gt; — el ejemplo de EF Core lanza en &lt;code&gt;ToListAsync&lt;/code&gt;, pero el bug es compartir un &lt;code&gt;DbContext&lt;/code&gt; entre consultas paralelas. El valor está en la explicación, no en la ubicación.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verifica, luego confía&lt;/strong&gt; — reproduce el arreglo, pregunta &lt;em&gt;por qué&lt;/em&gt; funciona y nunca pegues secretos. El debugger es dueño del estado; Claude, del significado.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>debugging</category>
    </item>
    <item>
      <title>RAG local en .NET: Chatea con tu Documentación (sin nube, sin API keys)</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Fri, 10 Jul 2026 20:47:29 +0000</pubDate>
      <link>https://dev.to/jgomezdev/rag-local-en-net-chatea-con-tu-documentacion-sin-nube-sin-api-keys-4lgh</link>
      <guid>https://dev.to/jgomezdev/rag-local-en-net-chatea-con-tu-documentacion-sin-nube-sin-api-keys-4lgh</guid>
      <description>&lt;h1&gt;
  
  
  RAG local en .NET: Chatea con tu Documentación (sin nube, sin API keys)
&lt;/h1&gt;

&lt;p&gt;Pregúntale a un modelo de lenguaje sobre la documentación de &lt;em&gt;tu&lt;/em&gt; producto y míralo improvisar. Nunca leyó tus docs internas de la API, así que rellena los huecos con respuestas seguras, plausibles y completamente inventadas. Eso es una alucinación, y no es que el modelo sea tonto: es que le pediste algo para lo que jamás le diste el material.&lt;/p&gt;

&lt;p&gt;RAG lo arregla. En vez de rezar para que el modelo &lt;em&gt;memorizara&lt;/em&gt; tus docs, le entregas las páginas relevantes &lt;em&gt;en el momento de la pregunta&lt;/em&gt; — como dejar que un estudiante haga un examen a libro abierto. En este tutorial construimos un pequeño sistema RAG en .NET 10 que responde preguntas sobre la documentación de un producto, y lo hacemos &lt;strong&gt;100% local&lt;/strong&gt;: sin nube, sin API keys, sin factura a fin de mes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2q0owz1rkc49anrvbdgw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2q0owz1rkc49anrvbdgw.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ¿Qué es RAG, sin humo?
&lt;/h2&gt;

&lt;p&gt;RAG significa &lt;strong&gt;Retrieval-Augmented Generation&lt;/strong&gt; (Generación Aumentada por Recuperación). Quítale las siglas y quedan tres ideas simples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval (recuperar)&lt;/strong&gt; — encontrar los pedazos de tus docs que se relacionan con la pregunta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Augmented (aumentar)&lt;/strong&gt; — pegar esos pedazos al prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation (generar)&lt;/strong&gt; — dejar que el modelo escriba la respuesta usándolos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eso es todo. Lo ingenioso está en el primer paso: &lt;em&gt;¿cómo encuentras los pedazos relevantes?&lt;/em&gt; No basta con buscar palabras iguales, porque alguien puede preguntar "¿cómo inicio sesión?" mientras tu doc dice "autenticación". Palabras distintas, mismo significado.&lt;/p&gt;

&lt;p&gt;El truco son los &lt;strong&gt;embeddings&lt;/strong&gt;. Un embedding es simplemente una lista de números (un &lt;em&gt;vector&lt;/em&gt;) que captura el &lt;em&gt;significado&lt;/em&gt; de un texto. Los textos que significan cosas parecidas obtienen números parecidos — así "iniciar sesión" y "autenticación" quedan cerca, aunque no compartan ni una letra. Encuentra los vectores más cercanos al vector de la pregunta y habrás encontrado los docs relevantes. No es magia, es geometría.&lt;/p&gt;




&lt;h2&gt;
  
  
  Los tres pasos de RAG
&lt;/h2&gt;

&lt;p&gt;Este es el pipeline completo, de principio a fin:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingesta&lt;/strong&gt; (una sola vez, al inicio): parte los docs en trozos pequeños, convierte cada trozo en un embedding y guárdalos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recuperación&lt;/strong&gt; (por cada pregunta): convierte la pregunta en un embedding y busca los trozos cuyos embeddings estén más cerca.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aumento y generación&lt;/strong&gt; (por cada pregunta): mete esos trozos en el prompt y pide al modelo que responda usándolos.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Los pasos 2 y 3 corren cada vez que alguien pregunta algo. El paso 1 corre una vez y se reutiliza para siempre (o hasta que cambien tus docs).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffsygi8ao9jgpiin89gaf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffsygi8ao9jgpiin89gaf.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Lo que vamos a construir + Setup
&lt;/h2&gt;

&lt;p&gt;Armaremos una app de consola que ingiere la documentación de un producto y luego responde preguntas sobre ella desde la terminal. Tres herramientas hacen el trabajo pesado:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; corre los modelos de IA en tu máquina — uno para crear embeddings, otro para escribir respuestas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; guarda todo en un único archivo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sqlite-vec&lt;/strong&gt;, una extensión de SQLite, hace por nosotros la búsqueda de "los vectores más cercanos".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tu laptop está a punto de tener un segundo empleo como mini data center de IA. El único costo es un ventilador un poco más caliente.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Crea el proyecto&lt;/span&gt;
dotnet new console &lt;span class="nt"&gt;-n&lt;/span&gt; OrbitDocsRag
&lt;span class="nb"&gt;cd &lt;/span&gt;OrbitDocsRag
dotnet add package Microsoft.Data.Sqlite

&lt;span class="c"&gt;# 2. Instala Ollama desde https://ollama.com y descarga los dos modelos&lt;/span&gt;
ollama pull nomic-embed-text   &lt;span class="c"&gt;# convierte texto en vectores de 768 números&lt;/span&gt;
ollama pull llama3.2           &lt;span class="c"&gt;# escribe la respuesta final&lt;/span&gt;

&lt;span class="c"&gt;# 3. Coloca la librería nativa de sqlite-vec junto a tu app.&lt;/span&gt;
&lt;span class="c"&gt;#    Descarga vec0.dll (Windows) / vec0.so (Linux) / vec0.dylib (macOS)&lt;/span&gt;
&lt;span class="c"&gt;#    desde https://github.com/asg017/sqlite-vec/releases y ponla en la&lt;/span&gt;
&lt;span class="c"&gt;#    carpeta del proyecto para que aterrice en bin/ al compilar.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ollama expone una API REST sencilla en &lt;code&gt;http://localhost:11434&lt;/code&gt;. Hablaremos con ella usando un &lt;code&gt;HttpClient&lt;/code&gt; pelado — sin SDK — para que veas que no hay nada escondido detrás de un wrapper. De verdad son solo un par de &lt;code&gt;POST&lt;/code&gt; por HTTP.&lt;/p&gt;




&lt;h2&gt;
  
  
  Paso 1 — Partir los docs en trozos (chunking)
&lt;/h2&gt;

&lt;p&gt;No puedes convertir un manual de 50 páginas en un solo vector — sería un promedio borroso de todo, inútil para cualquier cosa. Así que partimos cada documento en ventanas pequeñas y solapadas. Los trozos pequeños dan coincidencias &lt;em&gt;precisas&lt;/em&gt;; el solapamiento evita que una idea que cae justo en el borde quede cortada por la mitad.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;DocChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Chunker&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DocChunk&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;[]?)&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringSplitOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveEmptyEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;maxWords&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;maxWords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DocChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada trozo recuerda su &lt;code&gt;Source&lt;/code&gt; (el archivo del que vino) para poder citarlo después. Desliza una ventana de 120 palabras avanzando de 100 en 100, y obtienes trozos solapados que conservan su contexto.&lt;/p&gt;




&lt;h2&gt;
  
  
  Paso 2 — Convertir texto en vectores (embeddings)
&lt;/h2&gt;

&lt;p&gt;Aquí Ollama se gana el sueldo. Hacemos &lt;code&gt;POST&lt;/code&gt; de un texto a &lt;code&gt;/api/embed&lt;/code&gt; y recibimos un arreglo de 768 floats — el embedding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OllamaClient&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;JsonSerializerOptions&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonSerializerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:11434"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;EmbedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nomic-embed-text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/embed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmbedResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Embeddings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;EmbedResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="n"&gt;Embeddings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esa es toda la parte de "IA" de los embeddings — una llamada HTTP. El modelo lee tu texto y devuelve su significado como 768 números. Dos trozos sobre autenticación regresarán con dos arreglos muy parecidos.&lt;/p&gt;




&lt;h2&gt;
  
  
  Paso 3 — Guardar los vectores en SQLite
&lt;/h2&gt;

&lt;p&gt;Ahora guardamos cada trozo &lt;em&gt;y&lt;/em&gt; su embedding. &lt;code&gt;sqlite-vec&lt;/code&gt; nos da una tabla virtual especial que sabe buscar vectores de forma nativa. Le decimos que los vectores son 768 floats y que queremos distancia &lt;strong&gt;coseno&lt;/strong&gt; (ya llegamos a eso). Las columnas &lt;code&gt;+source&lt;/code&gt; y &lt;code&gt;+chunk&lt;/code&gt; viajan como texto plano para poder recuperar el texto original tras una búsqueda.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.Sqlite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VectorStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;SqliteConnection&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Data Source=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"vec0"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// carga la librería nativa de sqlite-vec&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;VIRTUAL&lt;/span&gt; &lt;span class="n"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;vec_chunks&lt;/span&gt; &lt;span class="n"&gt;USING&lt;/span&gt; &lt;span class="nf"&gt;vec0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;768&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;distance_metric&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="s"&gt;""";
&lt;/span&gt;        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DocChunk&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;INSERT&lt;/span&gt; &lt;span class="n"&gt;INTO&lt;/span&gt; &lt;span class="nf"&gt;vec_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="s"&gt;""";
&lt;/span&gt;        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$embedding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$source"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$chunk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Guardamos el embedding como un string JSON tipo &lt;code&gt;[0.12, -0.04, ...]&lt;/code&gt; — sqlite-vec lo interpreta sin problema.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Cómo funciona eso de "el más cercano"?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;sqlite-vec&lt;/code&gt; hace la búsqueda en código nativo rápido, pero no hay ningún misterio adentro. "¿Qué tan parecidos son dos vectores?" se responde con &lt;strong&gt;similitud coseno&lt;/strong&gt;, que son un puñado de líneas de C# plano:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;CosineSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;magA&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;magB&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dot&lt;/span&gt;  &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;magA&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;magB&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magB&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El resultado va de &lt;code&gt;1.0&lt;/code&gt; (significado idéntico) hasta &lt;code&gt;0&lt;/code&gt; (sin relación). sqlite-vec reporta la &lt;em&gt;distancia&lt;/em&gt; — básicamente &lt;code&gt;1 - similitud&lt;/code&gt; — así que &lt;strong&gt;más pequeño es más cercano&lt;/strong&gt;. Por eso cada consulta de abajo termina en &lt;code&gt;ORDER BY distance&lt;/code&gt;. Eso es toda la "búsqueda vectorial" de la que todos hablan: un producto punto y una raíz cuadrada.&lt;/p&gt;




&lt;h2&gt;
  
  
  Paso 4 — Recuperar los trozos relevantes
&lt;/h2&gt;

&lt;p&gt;Por cada pregunta, convertimos la pregunta en embedding exactamente igual, y luego le pedimos a sqlite-vec los &lt;code&gt;k&lt;/code&gt; trozos más cercanos. La palabra clave &lt;code&gt;MATCH&lt;/code&gt; es la señal de sqlite-vec para ejecutar una búsqueda por vecinos más cercanos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;Distance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// (agrega este método a VectorStore)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;        &lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;
        &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vec_chunks&lt;/span&gt;
        &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;MATCH&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
        &lt;span class="n"&gt;ORDER&lt;/span&gt; &lt;span class="n"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;
        &lt;span class="n"&gt;LIMIT&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="s"&gt;""";
&lt;/span&gt;    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le das el vector de la pregunta y recibes los tres trozos con mayor probabilidad de contener la respuesta — ordenados del más cercano al más lejano.&lt;/p&gt;




&lt;h2&gt;
  
  
  Paso 5 — Armar el prompt y generar la respuesta
&lt;/h2&gt;

&lt;p&gt;El último paso es la recompensa. Cosemos los trozos recuperados dentro de un prompt con una instrucción estricta: &lt;strong&gt;responde solo con este contexto&lt;/strong&gt;. Luego lo enviamos al modelo de chat vía &lt;code&gt;/api/chat&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// (agrega este método a OllamaClient)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ChatAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"llama3.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// records anidados dentro de OllamaClient&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatMessage&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y el prompt que lo une todo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"""
&lt;/span&gt;    &lt;span class="n"&gt;Answer&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="n"&gt;below&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt; &lt;span class="s"&gt;"I don't know based on the docs."&lt;/span&gt;

    &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;""";
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esa única instrucción — &lt;em&gt;solo con este contexto&lt;/em&gt; — es lo que mata las alucinaciones. Sin nada en qué apoyarse, el modelo no inventará una respuesta con seguridad; admitirá que no sabe. Es la diferencia entre un estudiante que estudió y uno que improvisa frente a la clase. Y como cada trozo lleva su etiqueta &lt;code&gt;[source.md]&lt;/code&gt;, puedes mostrarle al usuario exactamente de dónde salió la respuesta.&lt;/p&gt;




&lt;h2&gt;
  
  
  El ejemplo completo
&lt;/h2&gt;

&lt;p&gt;Este es el &lt;code&gt;Program.cs&lt;/code&gt; de nivel superior. Trae tres "docs" diminutas embebidas para que puedas correrlo sin ningún archivo, pero cambia &lt;code&gt;SampleDocs.All&lt;/code&gt; por una carpeta de &lt;code&gt;.md&lt;/code&gt; reales y no cambia nada más. Mantén los tipos &lt;code&gt;OllamaClient&lt;/code&gt;, &lt;code&gt;VectorStore&lt;/code&gt;, &lt;code&gt;Chunker&lt;/code&gt;, &lt;code&gt;DocChunk&lt;/code&gt; y &lt;code&gt;SearchHit&lt;/code&gt; de los pasos anteriores en el mismo proyecto (debajo del código de nivel superior, o en sus propios archivos) y compila tal cual.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.Sqlite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OllamaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;VectorStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orbit-docs.db"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// --- Paso 1 + 2 + 3: ingesta de los docs (trocear -&amp;gt; embeber -&amp;gt; guardar) ---&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SampleDocs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Chunker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EmbedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Pregunta sobre la API de Orbit: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"¿Cómo me autentico?"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// --- Paso 4: recuperar ---&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;questionEmbedding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EmbedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;questionEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// --- Paso 5: aumentar + generar ---&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"""
&lt;/span&gt;    &lt;span class="n"&gt;Answer&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="n"&gt;below&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt; &lt;span class="s"&gt;"I don't know based on the docs."&lt;/span&gt;

    &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;""";
&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ChatAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"\n&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\n"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Fuentes: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Distinct&lt;/span&gt;&lt;span class="p"&gt;())}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SampleDocs&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;All&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"auth.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;Orbit&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;uses&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Send&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt;
            &lt;span class="n"&gt;Authorization&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Bearer&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Keys&lt;/span&gt; &lt;span class="n"&gt;never&lt;/span&gt; &lt;span class="n"&gt;expire&lt;/span&gt; &lt;span class="n"&gt;but&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt;
            &lt;span class="n"&gt;revoked&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Rate&lt;/span&gt; &lt;span class="n"&gt;limits&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;""",
&lt;/span&gt;        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"webhooks.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Orbit&lt;/span&gt; &lt;span class="n"&gt;pushes&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Register&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt;
            &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Every&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;HMAC&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;verify&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt;
            &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Orbit&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Signature&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="n"&gt;trusting&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Failed&lt;/span&gt; &lt;span class="n"&gt;deliveries&lt;/span&gt;
            &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt; &lt;span class="n"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;""",
&lt;/span&gt;        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"errors.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Orbit&lt;/span&gt; &lt;span class="n"&gt;returns&lt;/span&gt; &lt;span class="n"&gt;standard&lt;/span&gt; &lt;span class="n"&gt;HTTP&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="m"&gt;429&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt;
            &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="n"&gt;back&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="m"&gt;401&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;
            &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;invalid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="m"&gt;422&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;"""
&lt;/span&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Córrelo, pregunta &lt;em&gt;"¿cómo inicio sesión?"&lt;/em&gt; y, aunque esas palabras exactas no aparecen en ningún lado de los docs, el embedding de tu pregunta aterriza junto al trozo de autenticación — y el modelo responde con las instrucciones de la API key, citando &lt;code&gt;auth.md&lt;/code&gt;. Eso es RAG funcionando.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cuándo usar RAG — y cuándo no
&lt;/h2&gt;

&lt;p&gt;RAG es la herramienta correcta cuando tu conocimiento es &lt;strong&gt;grande, privado o cambiante&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Usa RAG&lt;/strong&gt; para docs de empresa, manuales de producto, tickets de soporte, una base de conocimiento — cualquier cosa que el modelo base nunca vio y que se actualiza seguido. ¿Docs nuevos? Solo los embebes. Sin reentrenar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sáltate RAG y pega todo en el prompt&lt;/strong&gt; cuando el conocimiento completo cabe cómodamente en la ventana de contexto. Si son dos párrafos, la recuperación es matar moscas a cañonazos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recurre a fine-tuning&lt;/strong&gt; cuando necesitas cambiar el &lt;em&gt;estilo, tono o formato&lt;/em&gt; del modelo — no sus hechos. El fine-tuning enseña comportamiento; RAG aporta conocimiento. Resuelven problemas distintos, y muchos sistemas reales usan ambos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Algunas limitaciones honestas a tener en cuenta: tus respuestas son tan buenas como tus trozos, así que un mal chunking significa mala recuperación. Las tablas y los números se embeben mal (su significado vive en la estructura, no en la prosa). Y si la respuesta simplemente no está en los docs, un sistema RAG bien instruido debería decirlo — lo cual es una virtud, no un defecto.&lt;/p&gt;

&lt;p&gt;La regla de oro: &lt;strong&gt;si tendrías que copiar y pegar un documento en el chat para obtener una buena respuesta, ese es trabajo para RAG.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Puntos clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAG son tres pasos, no magia&lt;/strong&gt; — recuperar los trozos relevantes, aumentar el prompt, generar la respuesta. Todo lo demás es detalle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Los embeddings convierten significado en geometría&lt;/strong&gt; — un texto parecido se vuelve un vector parecido, así encuentras docs relevantes aunque las palabras no coincidan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;La "búsqueda vectorial" es solo similitud coseno&lt;/strong&gt; — un producto punto y una raíz cuadrada; sqlite-vec solo lo hace rápido, no misterioso.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anclar al contexto mata las alucinaciones&lt;/strong&gt; — decirle al modelo que responda &lt;em&gt;solo&lt;/em&gt; con el contexto recuperado convierte el disparate seguro en un honesto "no sé", y te deja citar fuentes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No necesitas la nube&lt;/strong&gt; — Ollama más SQLite corren un pipeline RAG completo en tu laptop, con cero API keys y cero costo por token.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>ai</category>
      <category>csharp</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Build a Local RAG in .NET: Chat With Your Docs (No Cloud, No API Keys)</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Fri, 10 Jul 2026 20:47:26 +0000</pubDate>
      <link>https://dev.to/jgomezdev/build-a-local-rag-in-net-chat-with-your-docs-no-cloud-no-api-keys-3nki</link>
      <guid>https://dev.to/jgomezdev/build-a-local-rag-in-net-chat-with-your-docs-no-cloud-no-api-keys-3nki</guid>
      <description>&lt;h1&gt;
  
  
  Build a Local RAG in .NET: Chat With Your Docs (No Cloud, No API Keys)
&lt;/h1&gt;

&lt;p&gt;Ask a language model about &lt;em&gt;your&lt;/em&gt; product's documentation and watch it improvise. It has never read your internal API docs, so it fills the gaps with confident, plausible, completely made-up answers. That is hallucination, and it is not the model being dumb — it is the model being asked a question it was never given the material to answer.&lt;/p&gt;

&lt;p&gt;RAG fixes this. Instead of hoping the model &lt;em&gt;memorized&lt;/em&gt; your docs, you hand it the relevant pages &lt;em&gt;at question time&lt;/em&gt; — like letting a student take an open-book exam. In this tutorial we build a small RAG system in .NET 10 that answers questions about a product's documentation, and we do it &lt;strong&gt;100% locally&lt;/strong&gt;: no cloud, no API keys, no invoice at the end of the month.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpf0ku1od2y357jbws5gw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpf0ku1od2y357jbws5gw.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is RAG, Without the Hype?
&lt;/h2&gt;

&lt;p&gt;RAG stands for &lt;strong&gt;Retrieval-Augmented Generation&lt;/strong&gt;. Strip away the acronym and it is three plain-English words:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval&lt;/strong&gt; — find the pieces of your docs that relate to the question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Augmented&lt;/strong&gt; — glue those pieces onto the prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation&lt;/strong&gt; — let the model write the answer using them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. The clever part is step one: &lt;em&gt;how do you find the relevant pieces?&lt;/em&gt; You can't just search for matching keywords, because someone might ask "how do I log in?" while your docs say "authentication." Different words, same meaning.&lt;/p&gt;

&lt;p&gt;The trick is &lt;strong&gt;embeddings&lt;/strong&gt;. An embedding is just a list of numbers (a &lt;em&gt;vector&lt;/em&gt;) that captures the &lt;em&gt;meaning&lt;/em&gt; of a piece of text. Texts that mean similar things get similar numbers — so "log in" and "authentication" land close together, even though they share no letters. Find the closest vectors to the question's vector, and you've found the relevant docs. No magic, just geometry.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Steps of RAG
&lt;/h2&gt;

&lt;p&gt;Here is the entire pipeline, start to finish:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingest&lt;/strong&gt; (done once, up front): chop the docs into small chunks, turn each chunk into an embedding, and store them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieve&lt;/strong&gt; (done per question): turn the question into an embedding, then find the chunks whose embeddings are closest to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Augment &amp;amp; generate&lt;/strong&gt; (done per question): drop those chunks into the prompt and ask the model to answer using them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 2 and 3 run every time someone asks something. Step 1 runs once and gets reused forever (or until your docs change).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqmw6zqe8gnneqfmm0jg2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqmw6zqe8gnneqfmm0jg2.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Building + Setup
&lt;/h2&gt;

&lt;p&gt;We'll build a console app that ingests a product's docs and then answers questions about them from the terminal. Three tools do the heavy lifting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; runs the AI models on your machine — one to create embeddings, one to write answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; stores everything in a single file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sqlite-vec&lt;/strong&gt;, a SQLite extension, does the "find the closest vectors" search for us.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your laptop is about to moonlight as a tiny AI data center. The only cost is a slightly warmer fan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Create the project&lt;/span&gt;
dotnet new console &lt;span class="nt"&gt;-n&lt;/span&gt; OrbitDocsRag
&lt;span class="nb"&gt;cd &lt;/span&gt;OrbitDocsRag
dotnet add package Microsoft.Data.Sqlite

&lt;span class="c"&gt;# 2. Install Ollama from https://ollama.com, then pull the two models&lt;/span&gt;
ollama pull nomic-embed-text   &lt;span class="c"&gt;# turns text into 768-number vectors&lt;/span&gt;
ollama pull llama3.2           &lt;span class="c"&gt;# writes the final answer&lt;/span&gt;

&lt;span class="c"&gt;# 3. Drop the sqlite-vec native library next to your app.&lt;/span&gt;
&lt;span class="c"&gt;#    Grab vec0.dll (Windows) / vec0.so (Linux) / vec0.dylib (macOS)&lt;/span&gt;
&lt;span class="c"&gt;#    from https://github.com/asg017/sqlite-vec/releases and place it&lt;/span&gt;
&lt;span class="c"&gt;#    in the project folder so it lands in bin/ at build time.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ollama exposes a plain REST API at &lt;code&gt;http://localhost:11434&lt;/code&gt;. We'll talk to it with a bare &lt;code&gt;HttpClient&lt;/code&gt; — no SDK — so you can see there is nothing hiding behind a wrapper. It really is just a couple of HTTP &lt;code&gt;POST&lt;/code&gt;s.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Chop the Docs Into Chunks
&lt;/h2&gt;

&lt;p&gt;You can't embed a whole 50-page manual as one vector — it would be a blurry average of everything, useful for nothing. So we split each document into small, overlapping windows. Small chunks give &lt;em&gt;precise&lt;/em&gt; matches; the overlap makes sure an idea that straddles a boundary isn't cut in half.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;DocChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Chunker&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DocChunk&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;[]?)&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringSplitOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveEmptyEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;maxWords&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;maxWords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DocChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each chunk remembers its &lt;code&gt;Source&lt;/code&gt; (the file it came from) so we can cite it later. Slide a 120-word window forward 100 words at a time, and you get overlapping chunks that keep their context.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — Turn Text Into Vectors (Embeddings)
&lt;/h2&gt;

&lt;p&gt;This is where Ollama earns its keep. We &lt;code&gt;POST&lt;/code&gt; a string to &lt;code&gt;/api/embed&lt;/code&gt; and get back an array of 768 floats — the embedding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OllamaClient&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;JsonSerializerOptions&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonSerializerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:11434"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;EmbedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nomic-embed-text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/embed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmbedResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Embeddings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;EmbedResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="n"&gt;Embeddings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole "AI" part of embeddings — one HTTP call. The model reads your text and returns its meaning as 768 numbers. Two chunks about authentication will come back with two very similar arrays.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Store the Vectors in SQLite
&lt;/h2&gt;

&lt;p&gt;Now we save each chunk &lt;em&gt;and&lt;/em&gt; its embedding. &lt;code&gt;sqlite-vec&lt;/code&gt; gives us a special virtual table that can search vectors natively. We tell it the vectors are 768 floats and that we want &lt;strong&gt;cosine&lt;/strong&gt; distance (more on that in a second). The &lt;code&gt;+source&lt;/code&gt; and &lt;code&gt;+chunk&lt;/code&gt; columns ride along as plain text so we can read the original text back after a search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.Sqlite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VectorStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;SqliteConnection&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Data Source=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"vec0"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// loads the sqlite-vec native library&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;VIRTUAL&lt;/span&gt; &lt;span class="n"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;vec_chunks&lt;/span&gt; &lt;span class="n"&gt;USING&lt;/span&gt; &lt;span class="nf"&gt;vec0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;768&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;distance_metric&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="s"&gt;""";
&lt;/span&gt;        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DocChunk&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;INSERT&lt;/span&gt; &lt;span class="n"&gt;INTO&lt;/span&gt; &lt;span class="nf"&gt;vec_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="s"&gt;""";
&lt;/span&gt;        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$embedding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$source"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$chunk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We store the embedding as a JSON string like &lt;code&gt;[0.12, -0.04, ...]&lt;/code&gt; — sqlite-vec happily parses that.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does "closest" actually work?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;sqlite-vec&lt;/code&gt; does the search in fast native code, but there's no mystery inside. "How similar are two vectors?" is answered by &lt;strong&gt;cosine similarity&lt;/strong&gt;, which is a handful of lines of plain C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;CosineSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;magA&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;magB&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dot&lt;/span&gt;  &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;magA&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;magB&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magB&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result runs from &lt;code&gt;1.0&lt;/code&gt; (identical meaning) down to &lt;code&gt;0&lt;/code&gt; (unrelated). sqlite-vec reports the &lt;em&gt;distance&lt;/em&gt; — basically &lt;code&gt;1 - similarity&lt;/code&gt; — so &lt;strong&gt;smaller is closer&lt;/strong&gt;. That's why every query below ends with &lt;code&gt;ORDER BY distance&lt;/code&gt;. That's the whole "vector search" everyone talks about: a dot product and a square root.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — Retrieve the Relevant Chunks
&lt;/h2&gt;

&lt;p&gt;Per question, we embed the question the exact same way, then ask sqlite-vec for the &lt;code&gt;k&lt;/code&gt; nearest chunks. The &lt;code&gt;MATCH&lt;/code&gt; keyword is sqlite-vec's signal to run a nearest-neighbor search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;Distance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// (add this method to VectorStore)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;        &lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;
        &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vec_chunks&lt;/span&gt;
        &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;MATCH&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
        &lt;span class="n"&gt;ORDER&lt;/span&gt; &lt;span class="n"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;
        &lt;span class="n"&gt;LIMIT&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="s"&gt;""";
&lt;/span&gt;    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give it the question's vector, get back the three chunks most likely to contain the answer — ranked from closest to furthest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5 — Build the Prompt and Generate the Answer
&lt;/h2&gt;

&lt;p&gt;The final step is the payoff. We stitch the retrieved chunks into a prompt with one strict instruction: &lt;strong&gt;answer only from this context&lt;/strong&gt;. Then we send it to the chat model via &lt;code&gt;/api/chat&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// (add this method to OllamaClient)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ChatAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"llama3.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// nested records inside OllamaClient&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatMessage&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the prompt that ties it all together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"""
&lt;/span&gt;    &lt;span class="n"&gt;Answer&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="n"&gt;below&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt; &lt;span class="s"&gt;"I don't know based on the docs."&lt;/span&gt;

    &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;""";
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one instruction — &lt;em&gt;only from this context&lt;/em&gt; — is what kills hallucinations. With nothing to lean on, the model won't confidently invent an answer; it'll admit it doesn't know. It's the difference between a student who studied and one improvising in front of the class. And because each chunk carries its &lt;code&gt;[source.md]&lt;/code&gt; tag, you can show the user exactly where the answer came from.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full Example
&lt;/h2&gt;

&lt;p&gt;Here's the top-level &lt;code&gt;Program.cs&lt;/code&gt;. It ships with three tiny "docs" inline so you can run it without any files, but swap &lt;code&gt;SampleDocs.All&lt;/code&gt; for a folder of real &lt;code&gt;.md&lt;/code&gt; files and nothing else changes. Keep the &lt;code&gt;OllamaClient&lt;/code&gt;, &lt;code&gt;VectorStore&lt;/code&gt;, &lt;code&gt;Chunker&lt;/code&gt;, &lt;code&gt;DocChunk&lt;/code&gt;, and &lt;code&gt;SearchHit&lt;/code&gt; types from the steps above in the same project (below the top-level code, or in their own files) and it compiles as-is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.Sqlite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OllamaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;VectorStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orbit-docs.db"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// --- Step 1 + 2 + 3: ingest the docs (chunk -&amp;gt; embed -&amp;gt; store) ---&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SampleDocs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Chunker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EmbedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ask about the Orbit API: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"How do I authenticate?"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// --- Step 4: retrieve ---&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;questionEmbedding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EmbedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;questionEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// --- Step 5: augment + generate ---&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"""
&lt;/span&gt;    &lt;span class="n"&gt;Answer&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="n"&gt;below&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt; &lt;span class="s"&gt;"I don't know based on the docs."&lt;/span&gt;

    &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;""";
&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ChatAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"\n&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\n"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Sources: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Distinct&lt;/span&gt;&lt;span class="p"&gt;())}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SampleDocs&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;All&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"auth.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;Orbit&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;uses&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Send&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt;
            &lt;span class="n"&gt;Authorization&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Bearer&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Keys&lt;/span&gt; &lt;span class="n"&gt;never&lt;/span&gt; &lt;span class="n"&gt;expire&lt;/span&gt; &lt;span class="n"&gt;but&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt;
            &lt;span class="n"&gt;revoked&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Rate&lt;/span&gt; &lt;span class="n"&gt;limits&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;""",
&lt;/span&gt;        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"webhooks.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Orbit&lt;/span&gt; &lt;span class="n"&gt;pushes&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Register&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt;
            &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Every&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;HMAC&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;verify&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt;
            &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Orbit&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Signature&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="n"&gt;trusting&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Failed&lt;/span&gt; &lt;span class="n"&gt;deliveries&lt;/span&gt;
            &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt; &lt;span class="n"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;""",
&lt;/span&gt;        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"errors.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Orbit&lt;/span&gt; &lt;span class="n"&gt;returns&lt;/span&gt; &lt;span class="n"&gt;standard&lt;/span&gt; &lt;span class="n"&gt;HTTP&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="m"&gt;429&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt;
            &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="n"&gt;back&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="m"&gt;401&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;
            &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;invalid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="m"&gt;422&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;"""
&lt;/span&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it, ask &lt;em&gt;"how do I log in?"&lt;/em&gt;, and even though those exact words appear nowhere in the docs, the embedding for your question lands next to the authentication chunk — and the model answers with the API-key instructions, citing &lt;code&gt;auth.md&lt;/code&gt;. That's RAG working.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use RAG — and When Not To
&lt;/h2&gt;

&lt;p&gt;RAG is the right tool when your knowledge is &lt;strong&gt;large, private, or changing&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use RAG&lt;/strong&gt; for company docs, product manuals, support tickets, a knowledge base — anything the base model never saw and that updates often. New docs? Just embed them. No retraining.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skip RAG and just paste it into the prompt&lt;/strong&gt; when the whole knowledge fits comfortably in the context window. If it's two paragraphs, retrieval is overkill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reach for fine-tuning instead&lt;/strong&gt; when you need to change the model's &lt;em&gt;style, tone, or format&lt;/em&gt; — not its facts. Fine-tuning teaches behavior; RAG supplies knowledge. They solve different problems, and plenty of real systems use both.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few honest limitations to keep in mind: your answers are only as good as your chunks, so bad chunking means bad retrieval. Tables and numbers embed poorly (their meaning lives in structure, not prose). And if the answer simply isn't in the docs, a well-instructed RAG system should say so — which is a feature, not a bug.&lt;/p&gt;

&lt;p&gt;The rule of thumb: &lt;strong&gt;if you'd have to copy-paste a document into the chat to get a good answer, that's a job for RAG.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAG is three steps, not magic&lt;/strong&gt; — retrieve relevant chunks, augment the prompt, generate the answer. Everything else is detail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddings turn meaning into geometry&lt;/strong&gt; — similar text becomes similar vectors, so you can find relevant docs even when the words don't match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Vector search" is just cosine similarity&lt;/strong&gt; — a dot product and a square root; sqlite-vec only makes it fast, not mysterious.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grounding kills hallucinations&lt;/strong&gt; — telling the model to answer &lt;em&gt;only&lt;/em&gt; from the retrieved context turns confident nonsense into honest "I don't know," and lets you cite sources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You don't need the cloud&lt;/strong&gt; — Ollama plus SQLite runs a complete RAG pipeline on your laptop, with zero API keys and zero per-token cost.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>ai</category>
      <category>csharp</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Claude Code for .NET Developers: From Zero to Productive in VS Code and Visual Studio</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Mon, 15 Jun 2026 22:57:25 +0000</pubDate>
      <link>https://dev.to/jgomezdev/claude-code-for-net-developers-from-zero-to-productive-in-vs-code-and-visual-studio-3cm6</link>
      <guid>https://dev.to/jgomezdev/claude-code-for-net-developers-from-zero-to-productive-in-vs-code-and-visual-studio-3cm6</guid>
      <description>&lt;h1&gt;
  
  
  Claude Code for .NET Developers: From Zero to Productive in VS Code and Visual Studio
&lt;/h1&gt;

&lt;p&gt;Here is the pattern that plays out constantly with AI coding tools: you try one, it blows your mind. You use it for a week, and it starts feeling random. Sometimes it's brilliant; sometimes it confidently suggests code from three framework versions ago. You stop trusting it, and it fades into the "cool but not practical" category.&lt;/p&gt;

&lt;p&gt;The problem is not the model. The problem is that you are using a powerful tool without understanding how it works. Claude Code is different from a chat window, and once you understand its mechanics — how it discovers context, how you shape its behavior, how you extend it with custom commands — it becomes genuinely reliable for .NET work.&lt;/p&gt;

&lt;p&gt;This article covers everything: installation, VS Code integration, the honest story about Visual Studio 2022 and 2026, CLAUDE.md (the most important file you are not writing yet), context management, Skills, and MCP. Whether you are new to AI tooling or already use it daily, there is something here for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3ltb8z6bp2yuaocphah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3ltb8z6bp2yuaocphah.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Claude Code, Exactly?
&lt;/h2&gt;

&lt;p&gt;Claude Code is not a chatbot that happens to know about code. It is a CLI-first, agentic coding assistant built by Anthropic. The key distinction: it operates on your actual repository. It can read files, run commands, edit code, and chain multiple steps together without you narrating every move.&lt;/p&gt;

&lt;p&gt;The mental model that helps most: Claude Code is a skilled contractor who just walked into your codebase. Give them the right context, and they can work independently. Give them nothing, and they will ask questions — or worse, make assumptions.&lt;/p&gt;

&lt;p&gt;There are two surfaces you will use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The CLI&lt;/strong&gt; (&lt;code&gt;claude&lt;/code&gt;) — runs in any terminal, works with any editor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The VS Code extension&lt;/strong&gt; (&lt;code&gt;anthropic.claude-code&lt;/code&gt;) — embeds the CLI experience directly into VS Code, with file awareness, selection sharing, and inline diffs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both use the same model and the same context system. The extension is additive convenience on top of the CLI, not a separate product.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation and Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;You need Node.js 22 LTS or later. Check with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Claude Code globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @anthropic-ai/claude-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authenticate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first run opens a browser for OAuth with your Anthropic account. After that, your credentials are stored locally and the CLI works offline-first (the model calls still go to Anthropic's API, of course).&lt;/p&gt;

&lt;h3&gt;
  
  
  VS Code Integration
&lt;/h3&gt;

&lt;p&gt;Install the extension from the marketplace. The ID is &lt;code&gt;anthropic.claude-code&lt;/code&gt;. You need VS Code 1.98 or later.&lt;/p&gt;

&lt;p&gt;Once installed, a Claude icon appears in the activity bar. Open it, sign in, and you have a chat panel that is deeply aware of your open files, your current selection, and your workspace structure. You can also invoke it inline with &lt;code&gt;Ctrl+Shift+C&lt;/code&gt; (Windows/Linux) or &lt;code&gt;Cmd+Shift+C&lt;/code&gt; (Mac) to open a quick input without leaving your editor flow.&lt;/p&gt;

&lt;p&gt;The extension does something important automatically: when you highlight code and ask a question, it attaches that selection as context. You do not have to copy-paste. This alone saves significant friction on large codebases.&lt;/p&gt;

&lt;p&gt;If you use &lt;strong&gt;JetBrains Rider&lt;/strong&gt;, there is also full Claude Code integration available through the JetBrains Marketplace. The experience is comparable to VS Code. Rider users are covered — you can follow this entire article and apply it directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Studio 2022 and 2026: The Honest Story
&lt;/h3&gt;

&lt;p&gt;Visual Studio has been the king of .NET IDEs for decades — it just has not heard of Claude yet.&lt;/p&gt;

&lt;p&gt;As of June 2026, there is no official Claude Code extension for Visual Studio 2022 or 2026. GitHub Copilot has deep VS integration; Claude Code does not. This is not a temporary oversight — the extension model for Visual Studio is substantially different from VS Code, and Anthropic has focused on the VS Code surface.&lt;/p&gt;

&lt;p&gt;The workaround is simple and it works well: use the integrated terminal.&lt;/p&gt;

&lt;p&gt;Open it with &lt;code&gt;View &amp;gt; Terminal&lt;/code&gt; (or &lt;code&gt;Ctrl+\&lt;/code&gt;`). Then run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
claude&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Claude Code starts in your repo's root directory. It reads everything — your &lt;code&gt;.cs&lt;/code&gt; files, your &lt;code&gt;.csproj&lt;/code&gt;, your &lt;code&gt;appsettings.json&lt;/code&gt;, your &lt;code&gt;CLAUDE.md&lt;/code&gt;. The terminal-based experience is the same CLI you would use anywhere. You lose the inline diff rendering and selection-sharing that the VS Code extension provides, but the model's capabilities are identical.&lt;/p&gt;

&lt;p&gt;Practical tip: keep Visual Studio open for debugging, the designer, and refactoring tooling (where it is unmatched), and open VS Code or a terminal alongside it for Claude interactions. Many .NET developers already do this. It is not a compromise — it is using each tool for what it is best at.&lt;/p&gt;




&lt;h2&gt;
  
  
  CLAUDE.md — The Brain of the Operation
&lt;/h2&gt;

&lt;p&gt;This is the most important section in the article.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5gjiermmkwva1ws2wem.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5gjiermmkwva1ws2wem.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every time Claude Code starts in a directory, it performs a filesystem walk. It moves upward from the current working directory, looking for &lt;code&gt;CLAUDE.md&lt;/code&gt; files. It also lazily loads &lt;code&gt;CLAUDE.md&lt;/code&gt; files found in subdirectories as it encounters them during work. The result is a layered context system: a root &lt;code&gt;CLAUDE.md&lt;/code&gt; for the whole repo, and optionally per-project or per-module files for specific concerns.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; is not magic. It is just a Markdown file. But it is the file Claude reads before doing anything else. Think of it as your onboarding document for a contractor who starts fresh every session.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Claude Does NOT Know Without CLAUDE.md
&lt;/h3&gt;

&lt;p&gt;Without any instruction file, Claude Code will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Guess your build system from file structure&lt;/li&gt;
&lt;li&gt;Assume generic conventions&lt;/li&gt;
&lt;li&gt;Ask clarifying questions repeatedly&lt;/li&gt;
&lt;li&gt;Make mistakes that a three-minute onboarding would prevent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a good &lt;code&gt;CLAUDE.md&lt;/code&gt;, Claude knows your exact &lt;code&gt;dotnet&lt;/code&gt; commands, your test runner, your architecture decisions, your naming conventions, and any constraints specific to your project. It stops guessing and starts working.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discovery in Visual Studio: A Common Gotcha
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; lives at the root of your repository — the same directory as your &lt;code&gt;.sln&lt;/code&gt; file. Visual Studio's Solution Explorer shows files based on what is referenced in &lt;code&gt;.csproj&lt;/code&gt; or &lt;code&gt;.sln&lt;/code&gt; files. Since &lt;code&gt;CLAUDE.md&lt;/code&gt; is not referenced by either, it will not appear in Solution Explorer.&lt;/p&gt;

&lt;p&gt;This trips people up. They create the file, it does not show up in VS, they assume something is wrong. Nothing is wrong. Claude loads it perfectly. Open it directly in VS Code or a plain text editor to edit it.&lt;/p&gt;

&lt;p&gt;To verify what Claude has loaded, run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;plaintext&lt;br&gt;
/memory&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This lists all context files currently active in the session. If &lt;code&gt;CLAUDE.md&lt;/code&gt; is loaded, it appears here. This is your ground truth.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Practical CLAUDE.md for a .NET Project
&lt;/h3&gt;

&lt;p&gt;Here is an example for a realistic ASP.NET Core project:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`markdown&lt;/p&gt;

&lt;h1&gt;
  
  
  OrderService
&lt;/h1&gt;

&lt;p&gt;ASP.NET Core 10 minimal API backed by PostgreSQL via EF Core 10.&lt;br&gt;
Authentication: JWT with ASP.NET Core Identity.&lt;br&gt;
Deployed to Azure Container Apps via GitHub Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;`bash&lt;br&gt;
dotnet build OrderService.sln&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests
&lt;/h2&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Run all tests
&lt;/h1&gt;

&lt;p&gt;dotnet test OrderService.sln&lt;/p&gt;

&lt;h1&gt;
  
  
  Run only unit tests
&lt;/h1&gt;

&lt;p&gt;dotnet test tests/OrderService.UnitTests/&lt;/p&gt;

&lt;h1&gt;
  
  
  Run with coverage
&lt;/h1&gt;

&lt;p&gt;dotnet test OrderService.sln --collect:"XPlat Code Coverage"&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/OrderService.Api/&lt;/code&gt; — minimal API endpoints, no business logic here&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/OrderService.Domain/&lt;/code&gt; — domain models, value objects, interfaces&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/OrderService.Application/&lt;/code&gt; — CQRS handlers (MediatR), validators (FluentValidation)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/OrderService.Infrastructure/&lt;/code&gt; — EF Core, repositories, external HTTP clients&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/OrderService.UnitTests/&lt;/code&gt; — xUnit, Moq, domain and application layer only&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/OrderService.IntegrationTests/&lt;/code&gt; — xUnit, Testcontainers, full API stack&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conventions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;record&lt;/code&gt; types for immutable domain models and DTOs&lt;/li&gt;
&lt;li&gt;Use C# 14 &lt;code&gt;field&lt;/code&gt; keyword instead of explicit backing fields where applicable&lt;/li&gt;
&lt;li&gt;All nullable reference types enabled; annotate accordingly&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;var&lt;/code&gt; — use explicit types everywhere&lt;/li&gt;
&lt;li&gt;Async all the way down; no &lt;code&gt;.Result&lt;/code&gt; or &lt;code&gt;.Wait()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Repository interfaces in Domain layer; implementations in Infrastructure&lt;/li&gt;
&lt;li&gt;Endpoint classes in &lt;code&gt;Features/&lt;/code&gt; folders grouped by domain concept (vertical slice)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  EF Core
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Migrations in &lt;code&gt;src/OrderService.Infrastructure/Migrations/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add migration: &lt;code&gt;dotnet ef migrations add &amp;lt;Name&amp;gt; --project src/OrderService.Infrastructure --startup-project src/OrderService.Api&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Apply: &lt;code&gt;dotnet ef database update --project src/OrderService.Infrastructure --startup-project src/OrderService.Api&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do Not
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Do not add business logic to endpoint handlers&lt;/li&gt;
&lt;li&gt;Do not use &lt;code&gt;dynamic&lt;/code&gt; or &lt;code&gt;object&lt;/code&gt; as return types&lt;/li&gt;
&lt;li&gt;Do not skip FluentValidation for command/query inputs&lt;/li&gt;
&lt;li&gt;Do not add new NuGet packages without noting them here
`&lt;code&gt;&lt;/code&gt;plaintext&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This file takes about fifteen minutes to write and saves hours of back-and-forth over the lifetime of a project. The investment is obvious once you have it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating a Starter CLAUDE.md
&lt;/h3&gt;

&lt;p&gt;If you are not starting from scratch, Claude Code can inspect your repo and generate a first draft:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
/init&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;plaintext&lt;/p&gt;

&lt;p&gt;This reads your solution structure, existing documentation, and common patterns, then writes a &lt;code&gt;CLAUDE.md&lt;/code&gt; that reflects what it found. Treat the output as a starting point — review it, correct anything wrong, and add the domain knowledge Claude cannot infer (architecture decisions, team conventions, known pitfalls).&lt;/p&gt;




&lt;h2&gt;
  
  
  Giving Claude Context
&lt;/h2&gt;

&lt;p&gt;Even with a great &lt;code&gt;CLAUDE.md&lt;/code&gt;, you will often need to point Claude at specific files during a conversation. The &lt;code&gt;@&lt;/code&gt; mention syntax does this precisely.&lt;/p&gt;

&lt;h3&gt;
  
  
  File and Directory Mentions
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
@Program.cs&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;csharp&lt;/p&gt;

&lt;p&gt;Attaches the full content of &lt;code&gt;Program.cs&lt;/code&gt; to the current message.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
@src/OrderService.Application/&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;plaintext&lt;/p&gt;

&lt;p&gt;Attaches a directory listing. Claude will then read specific files from it as needed, rather than loading everything upfront.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
@appsettings.json#1-20&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;plaintext&lt;/p&gt;

&lt;p&gt;Attaches only lines 1 through 20. Useful when you care about a specific section of a large config file and do not want to burn context on the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  VS Code Selection Sharing
&lt;/h3&gt;

&lt;p&gt;When you use the VS Code extension, any code you highlight in the editor is automatically shared when you open the Claude panel. You do not need &lt;code&gt;@&lt;/code&gt; mentions for your active selection — it is already there. This makes "explain this method" or "what does this LINQ query do" workflows completely frictionless.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Long Contexts
&lt;/h3&gt;

&lt;p&gt;Long sessions accumulate context. After several hours of work, Claude's context window fills with earlier conversation turns that are no longer relevant. When responses start feeling less sharp, run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
/compact&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;plaintext&lt;/p&gt;

&lt;p&gt;This summarizes the conversation history into a compressed form, freeing up context space without losing the thread of what you have been working on. Think of it as telling the contractor to forget the small talk and remember only the decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Skills — Custom Commands for Your Workflow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi224kok5dtjqssagvfwf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi224kok5dtjqssagvfwf.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Skills are reusable, invocable commands that extend Claude Code's default behavior. They live as Markdown files and you invoke them with a &lt;code&gt;/&lt;/code&gt; prefix, just like built-in commands. There are two scopes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Personal skills&lt;/strong&gt; — stored in &lt;code&gt;~/.claude/skills/&amp;lt;skill-name&amp;gt;/SKILL.md&lt;/code&gt;. Available in every project on your machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project skills&lt;/strong&gt; — stored in &lt;code&gt;.claude/skills/&amp;lt;skill-name&amp;gt;/SKILL.md&lt;/code&gt; in your repo root. Checked into source control, shared with the team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A skill file is a Markdown document that tells Claude what to do when invoked. It can include steps, decision logic, expected outputs, and even inline prompts.&lt;/p&gt;

&lt;p&gt;Here are three practical examples for .NET development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skill: &lt;code&gt;/dotnet-test&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;.claude/skills/dotnet-test/SKILL.md&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`markdown&lt;/p&gt;

&lt;h1&gt;
  
  
  dotnet-test
&lt;/h1&gt;

&lt;p&gt;Run the test suite for this project and summarize the results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;dotnet test&lt;/code&gt; using the command from CLAUDE.md (check the "Tests" section).&lt;/li&gt;
&lt;li&gt;Parse the output for failed tests.&lt;/li&gt;
&lt;li&gt;For each failure:

&lt;ul&gt;
&lt;li&gt;Show the test name&lt;/li&gt;
&lt;li&gt;Show the failure message&lt;/li&gt;
&lt;li&gt;Show the stack trace trimmed to the first 5 lines&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If all tests pass, confirm with a short success message.&lt;/li&gt;
&lt;li&gt;If build errors prevent tests from running, show the build errors instead.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Output Format
&lt;/h2&gt;

&lt;p&gt;Group failures by project. Keep the summary concise — one paragraph max,&lt;br&gt;
followed by a numbered list of failures if any exist.&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Invoke with &lt;code&gt;/dotnet-test&lt;/code&gt;. Claude runs your test command, reads the output, and gives you a clean summary instead of a wall of terminal text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skill: &lt;code&gt;/csharp-review&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;.claude/skills/csharp-review/SKILL.md&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`markdown&lt;/p&gt;

&lt;h1&gt;
  
  
  csharp-review
&lt;/h1&gt;

&lt;p&gt;Review the provided C# code for modern idiom usage and correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Check
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Records&lt;/strong&gt;: Are mutable classes used where &lt;code&gt;record&lt;/code&gt; or &lt;code&gt;record struct&lt;/code&gt; would be more appropriate?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Init-only properties&lt;/strong&gt;: Are setters used where &lt;code&gt;init&lt;/code&gt; would suffice?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pattern matching&lt;/strong&gt;: Are &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;switch&lt;/code&gt; chains that could use &lt;code&gt;switch&lt;/code&gt; expressions or positional patterns?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nullable annotations&lt;/strong&gt;: Are reference types properly annotated? Any &lt;code&gt;!&lt;/code&gt; suppressors hiding problems?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Primary constructors&lt;/strong&gt;: Could a class use C# 12+ primary constructor syntax?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;field&lt;/code&gt; keyword&lt;/strong&gt;: Are there explicit backing fields that C# 14's &lt;code&gt;field&lt;/code&gt; keyword could eliminate?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async&lt;/strong&gt;: Any &lt;code&gt;.Result&lt;/code&gt;, &lt;code&gt;.Wait()&lt;/code&gt;, or &lt;code&gt;async void&lt;/code&gt; outside of event handlers?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LINQ&lt;/strong&gt;: Any imperative loops that would read more clearly as LINQ?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Output Format
&lt;/h2&gt;

&lt;p&gt;List each finding as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;[Category]&lt;/strong&gt; Description of the issue&lt;/li&gt;
&lt;li&gt;Suggested rewrite (code block, 10 lines max)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;End with a one-sentence overall assessment.&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Invoke with &lt;code&gt;/csharp-review&lt;/code&gt; while a file is open (or with &lt;code&gt;@filename&lt;/code&gt; if using the CLI). You get actionable, .NET-specific feedback rather than generic suggestions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skill: &lt;code&gt;/ef-migrate&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;.claude/skills/ef-migrate/SKILL.md&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`markdown&lt;/p&gt;

&lt;h1&gt;
  
  
  ef-migrate
&lt;/h1&gt;

&lt;p&gt;Guide through EF Core migration management for this project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run the &lt;code&gt;dotnet ef migrations list&lt;/code&gt; command from CLAUDE.md to show existing migrations.
Use the correct &lt;code&gt;--project&lt;/code&gt; and &lt;code&gt;--startup-project&lt;/code&gt; flags from CLAUDE.md.&lt;/li&gt;
&lt;li&gt;Show the migration list, noting which have been applied (indicated by [applied]).&lt;/li&gt;
&lt;li&gt;Ask the user: "Do you want to add a new migration, or apply pending ones?"&lt;/li&gt;
&lt;li&gt;If adding a migration:

&lt;ul&gt;
&lt;li&gt;Ask for the migration name (PascalCase, descriptive, e.g., AddOrderStatusIndex)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;dotnet ef migrations add &amp;lt;Name&amp;gt;&lt;/code&gt; with correct flags&lt;/li&gt;
&lt;li&gt;Show which files were created&lt;/li&gt;
&lt;li&gt;Ask if they want to review the generated migration before applying&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If applying pending migrations:

&lt;ul&gt;
&lt;li&gt;Confirm the number of pending migrations&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;dotnet ef database update&lt;/code&gt; with correct flags&lt;/li&gt;
&lt;li&gt;Report success or errors&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Safety Note
&lt;/h2&gt;

&lt;p&gt;Always mention that migrations modify the database schema. Remind the user to review&lt;br&gt;
generated migrations before applying to staging or production environments.&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This skill turns what is normally a multi-step, flag-heavy CLI workflow into a guided conversation. It uses the migration commands already defined in your &lt;code&gt;CLAUDE.md&lt;/code&gt;, so it adapts to each project automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  MCP — Connecting External Tools
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol (MCP) is a standard that lets Claude connect to external data sources and tools beyond your local filesystem. Where Skills extend what Claude &lt;em&gt;does&lt;/em&gt;, MCP extends what Claude can &lt;em&gt;see&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A few examples relevant to .NET teams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub MCP&lt;/strong&gt; — Claude can read PR diffs, check CI status, and leave review comments directly, without you copy-pasting GitHub URLs into the chat&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure DevOps MCP&lt;/strong&gt; — Claude can read your current sprint, query work items, and understand the backlog when you ask it to help prioritize or plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database MCP&lt;/strong&gt; — Claude can query your local or dev database schema to inform code generation, without you having to paste CREATE TABLE statements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MCP servers are configured in your Claude Code settings and are available across all sessions. For .NET teams working in Azure DevOps or GitHub, setting up the corresponding MCP server once pays dividends on every PR review and sprint planning task after that.&lt;/p&gt;




&lt;h2&gt;
  
  
  VS Code vs Visual Studio: An Honest Comparison
&lt;/h2&gt;

&lt;p&gt;Both IDEs are legitimate for .NET work. Here is where each one stands with Claude Code:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;VS Code + Claude Extension&lt;/th&gt;
&lt;th&gt;Visual Studio 2022/2026 + Terminal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code integration&lt;/td&gt;
&lt;td&gt;Native extension, inline diffs, selection sharing&lt;/td&gt;
&lt;td&gt;Terminal only, CLI-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging experience&lt;/td&gt;
&lt;td&gt;Good (but not VS-level)&lt;/td&gt;
&lt;td&gt;Best in class for .NET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLAUDE.md support&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Full (file not shown in Solution Explorer)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skills / MCP&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Full (terminal invocation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Designer tools (WinForms, WPF)&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refactoring tools&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Excellent (ReSharper-level built-in)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hot Reload / Edit and Continue&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Best in class&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extension ecosystem&lt;/td&gt;
&lt;td&gt;Vast&lt;/td&gt;
&lt;td&gt;.NET-focused, deep OS integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Community free; Pro/Enterprise paid&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pragmatic answer for most .NET developers: use both. VS Code for AI-assisted coding, exploration, and file editing; Visual Studio for debugging sessions, UI designers, and deep refactoring. They open the same project files and there is no conflict. Many developers keep both open simultaneously without issue.&lt;/p&gt;

&lt;p&gt;If you have to pick one: pure backend services (ASP.NET Core APIs, worker services, console apps) work well with VS Code as your primary. Anything with Windows Forms, WPF, or complex debugger needs warrants Visual Studio as primary with Claude Code in the terminal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CLAUDE.md is not optional&lt;/strong&gt; — it is the difference between an AI that guesses and one that knows your codebase. Write it, commit it, keep it current. Run &lt;code&gt;/init&lt;/code&gt; to generate a starting point from your existing repo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visual Studio is fully usable&lt;/strong&gt; — the lack of a native extension is a real limitation, but &lt;code&gt;View &amp;gt; Terminal&lt;/code&gt; and the &lt;code&gt;claude&lt;/code&gt; CLI give you full Claude Code functionality. The file-awareness and Skills system work identically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context is everything&lt;/strong&gt; — use &lt;code&gt;@&lt;/code&gt; mentions to target specific files, use line ranges to stay precise, and use &lt;code&gt;/compact&lt;/code&gt; when sessions get long. Precision in context leads to precision in output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Skills are multipliers&lt;/strong&gt; — a fifteen-minute investment writing a project skill pays back immediately. Codify your team's review criteria, your test workflow, your migration process. Claude Code executes them consistently every time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The model is not random — your context is&lt;/strong&gt; — when Claude gives you a bad answer, the fix is almost always better context: a clearer &lt;code&gt;CLAUDE.md&lt;/code&gt;, a more specific &lt;code&gt;@&lt;/code&gt; mention, or a well-written skill. Understanding the context system turns a frustrating tool into a reliable one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The architecture is simple once you see it: CLAUDE.md tells Claude who you are and how your project works, &lt;code&gt;@&lt;/code&gt; mentions tell Claude what to look at right now, and Skills tell Claude how to handle your recurring tasks. Stack those three things on top of a capable model and the "random magic" feeling goes away entirely. What you get instead is a coding assistant that behaves like someone who has actually read the codebase.&lt;/p&gt;

&lt;p&gt;Which, thanks to CLAUDE.md, it has.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Claude Code para desarrolladores .NET: De cero a productivo en VS Code y Visual Studio</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Mon, 15 Jun 2026 22:57:12 +0000</pubDate>
      <link>https://dev.to/jgomezdev/claude-code-para-desarrolladores-net-de-cero-a-productivo-en-vs-code-y-visual-studio-d4j</link>
      <guid>https://dev.to/jgomezdev/claude-code-para-desarrolladores-net-de-cero-a-productivo-en-vs-code-y-visual-studio-d4j</guid>
      <description>&lt;h1&gt;
  
  
  Claude Code para desarrolladores .NET: De cero a productivo en VS Code y Visual Studio
&lt;/h1&gt;

&lt;p&gt;Las herramientas de IA se sienten mágicas los primeros días. Le preguntas algo, te responde bien, y piensas: "esto va a cambiar todo". Luego pasan unas semanas, el contexto se confunde, las respuestas se vuelven genéricas, y empiezas a sentir que estás teniendo una conversación con alguien que tiene amnesia selectiva.&lt;/p&gt;

&lt;p&gt;El problema no es la IA. El problema es que nadie te explicó cómo funciona realmente.&lt;/p&gt;

&lt;p&gt;Este artículo desmitifica Claude Code para desarrolladores .NET. No es un tutorial de "mira qué cool es esto" — es una guía práctica de cómo funciona, por qué funciona así, y cómo sacarle el máximo provecho en proyectos C# reales, tanto en VS Code como en Visual Studio 2022/2026.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fey9jvse69x8fetk1p198.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fey9jvse69x8fetk1p198.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué es Claude Code (y qué no es)
&lt;/h2&gt;

&lt;p&gt;Claude Code &lt;strong&gt;no es&lt;/strong&gt; una ventana de chat glorificada. Es una CLI (interfaz de línea de comandos) que corre directamente en tu terminal y tiene acceso real a tu sistema de archivos, puede ejecutar comandos, leer y editar archivos, y mantener contexto sobre tu proyecto.&lt;/p&gt;

&lt;p&gt;La diferencia con "preguntarle a Claude en el navegador" es enorme:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;En el navegador, pegas código manualmente, pierdes contexto entre sesiones, y Claude no sabe nada de tu proyecto más allá de lo que tú le dices.&lt;/li&gt;
&lt;li&gt;Con Claude Code, Claude &lt;strong&gt;vive en tu repositorio&lt;/strong&gt;. Lee archivos, ejecuta &lt;code&gt;dotnet build&lt;/code&gt;, corre tus tests, y entiende la arquitectura de tu solución.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Además de la CLI, existe una extensión para VS Code que integra Claude Code directamente en el editor — con contexto automático de lo que tienes abierto y seleccionado. Para Visual Studio, la historia es diferente (y la contaremos honestamente más adelante).&lt;/p&gt;

&lt;h2&gt;
  
  
  Instalación y setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerrequisitos
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 22 LTS o superior&lt;/li&gt;
&lt;li&gt;Una cuenta en &lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;claude.ai&lt;/a&gt; con acceso a Claude Code&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Instalar la CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @anthropic-ai/claude-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verifica la instalación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para iniciar una sesión interactiva en tu proyecto, navega al directorio raíz y ejecuta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  VS Code — integración completa
&lt;/h3&gt;

&lt;p&gt;Instala la extensión oficial desde el Marketplace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nombre: &lt;strong&gt;Claude Code&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;ID: &lt;code&gt;anthropic.claude-code&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Requiere VS Code &lt;strong&gt;1.98 o superior&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Una vez instalada, Claude Code aparece en la barra lateral. La integración es profunda: cuando tienes un archivo abierto o código seleccionado, Claude lo recibe automáticamente como contexto. No necesitas copiar y pegar nada.&lt;/p&gt;

&lt;p&gt;JetBrains Rider también cuenta con integración completa a través de su plugin oficial — si tu equipo usa Rider, el workflow es muy similar al de VS Code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Studio 2022/2026 — la verdad sin rodeos
&lt;/h3&gt;

&lt;p&gt;Visual Studio ha sido el rey del desarrollo .NET por décadas. Simplemente aún no se ha enterado de que existe Claude.&lt;/p&gt;

&lt;p&gt;A junio de 2026, &lt;strong&gt;no existe una extensión oficial de Claude Code para Visual Studio&lt;/strong&gt;. Punto. No hay workaround mágico que cambie eso.&lt;/p&gt;

&lt;p&gt;Lo que sí funciona, y funciona bien, es usar la &lt;strong&gt;terminal integrada de Visual Studio&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ve a &lt;strong&gt;Ver &amp;gt; Terminal&lt;/strong&gt; (o &lt;code&gt;Ctrl+\&lt;/code&gt;, &lt;code&gt;Ctrl+`&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Navega al directorio raíz de tu solución (normalmente ya estás ahí)&lt;/li&gt;
&lt;li&gt;Ejecuta &lt;code&gt;claude&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Claude lee tu repositorio exactamente igual que en VS Code. La diferencia es que no tienes el contexto visual automático — tendrás que mencionar archivos explícitamente con &lt;code&gt;@&lt;/code&gt; (lo veremos en detalle más adelante). Para muchas tareas de refactoring, generación de código y revisión de arquitectura, la terminal es más que suficiente.&lt;/p&gt;

&lt;p&gt;Muchos desarrolladores .NET tienen ambos IDEs abiertos en paralelo: Visual Studio para debugging avanzado, diseñadores de formularios, y herramientas de profiling; VS Code para trabajar con Claude Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLAUDE.md — el cerebro de la operación
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50uz0mgy30s8r44te03z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50uz0mgy30s8r44te03z.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Aquí está el insight más importante del artículo, así que léelo despacio.&lt;/p&gt;

&lt;p&gt;Claude Code no funciona como un chatbot con memoria persistente. Cada vez que inicias una sesión, parte de cero. Entonces, ¿cómo "conoce" tu proyecto? A través de un archivo llamado &lt;code&gt;CLAUDE.md&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cómo Claude descubre CLAUDE.md
&lt;/h3&gt;

&lt;p&gt;Cuando ejecutas &lt;code&gt;claude&lt;/code&gt; desde un directorio, Claude realiza un &lt;strong&gt;recorrido del filesystem hacia arriba&lt;/strong&gt; desde tu directorio de trabajo actual (CWD), buscando archivos &lt;code&gt;CLAUDE.md&lt;/code&gt;. Además, carga &lt;code&gt;CLAUDE.md&lt;/code&gt; de los subdirectorios de forma lazy a medida que accede a ellos.&lt;/p&gt;

&lt;p&gt;Esto significa dos cosas importantes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No está basado en archivos de proyecto&lt;/strong&gt; — Claude no lee tu &lt;code&gt;.csproj&lt;/code&gt; para descubrir CLAUDE.md. Lo descubre por ubicación en el filesystem. Si tu &lt;code&gt;CLAUDE.md&lt;/code&gt; está en la raíz del repo y ejecutas &lt;code&gt;claude&lt;/code&gt; desde ahí, se carga. Si lo ejecutas desde un subdirectorio, también lo cargará (porque sube hacia arriba).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;En Visual Studio, CLAUDE.md no aparece en el Explorador de Soluciones&lt;/strong&gt; — porque no está referenciado en ningún &lt;code&gt;.csproj&lt;/code&gt;. Pero Claude &lt;strong&gt;sí lo carga&lt;/strong&gt;. No necesitas agregarlo al proyecto. De hecho, es mejor que no lo hagas — es un archivo de contexto para la IA, no un recurso de tu aplicación.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Verificar qué está cargado
&lt;/h3&gt;

&lt;p&gt;Puedes verificar en cualquier momento qué archivos CLAUDE.md tiene cargados Claude con el comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto muestra el contenido actual de memoria, incluyendo todos los CLAUDE.md activos en la sesión.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generar un CLAUDE.md inicial
&lt;/h3&gt;

&lt;p&gt;Si tu proyecto no tiene CLAUDE.md, Claude puede generarlo leyendo el repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto analiza tu código, estructura de carpetas, y archivos de configuración, y produce un CLAUDE.md base que puedes refinar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ejemplo práctico: CLAUDE.md para un proyecto .NET
&lt;/h3&gt;

&lt;p&gt;Aquí un ejemplo completo y realista para una API de e-commerce en .NET 10:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# ECommerceApi — Contexto para Claude Code&lt;/span&gt;

&lt;span class="gu"&gt;## Descripción del proyecto&lt;/span&gt;
API REST para gestión de catálogo y órdenes de compra.
Stack: .NET 10, C# 14, Entity Framework Core 9, PostgreSQL, MassTransit (RabbitMQ).
Arquitectura: Clean Architecture con CQRS via MediatR.

&lt;span class="gu"&gt;## Comandos esenciales&lt;/span&gt;

&lt;span class="gu"&gt;### Build y tests&lt;/span&gt;
dotnet build ECommerceApi.sln
dotnet test ECommerceApi.sln --logger "console;verbosity=normal"
dotnet test --filter "Category=Unit"
dotnet test --filter "Category=Integration"

&lt;span class="gu"&gt;### Migraciones EF Core&lt;/span&gt;
cd src/ECommerceApi.Infrastructure
dotnet ef migrations add &lt;span class="nt"&gt;&amp;lt;NombreMigracion&amp;gt;&lt;/span&gt; --startup-project ../ECommerceApi.Api
dotnet ef database update --startup-project ../ECommerceApi.Api

&lt;span class="gu"&gt;### Ejecución local&lt;/span&gt;
cd src/ECommerceApi.Api
dotnet run

&lt;span class="gu"&gt;## Estructura del proyecto&lt;/span&gt;
src/
  ECommerceApi.Api/           # Controllers, middleware, DI setup
  ECommerceApi.Application/   # Commands, queries, DTOs, validators
  ECommerceApi.Domain/        # Entities, value objects, domain events
  ECommerceApi.Infrastructure/ # EF Core, repositories, external services
tests/
  ECommerceApi.UnitTests/
  ECommerceApi.IntegrationTests/

&lt;span class="gu"&gt;## Convenciones de código&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Usa records para DTOs y value objects
&lt;span class="p"&gt;-&lt;/span&gt; Usa init-only properties en domain entities cuando sea posible
&lt;span class="p"&gt;-&lt;/span&gt; Pattern matching exhaustivo en switches sobre discriminated unions
&lt;span class="p"&gt;-&lt;/span&gt; Nullable reference types habilitados — sin supresiones con !
&lt;span class="p"&gt;-&lt;/span&gt; No uses var — tipado explícito siempre
&lt;span class="p"&gt;-&lt;/span&gt; Nombres en inglés para código, comentarios pueden ser en español

&lt;span class="gu"&gt;## Decisiones de arquitectura importantes&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Los controllers SOLO llaman a MediatR.Send() — lógica cero en controllers
&lt;span class="p"&gt;-&lt;/span&gt; Validaciones con FluentValidation en el pipeline de MediatR (no en controllers)
&lt;span class="p"&gt;-&lt;/span&gt; Los domain events se despachan DESPUÉS de SaveChanges (outbox pattern)
&lt;span class="p"&gt;-&lt;/span&gt; Los integration tests levantan PostgreSQL en Docker via Testcontainers

&lt;span class="gu"&gt;## Lo que NO hacer&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; No agregar lógica de negocio en Infrastructure
&lt;span class="p"&gt;-&lt;/span&gt; No inyectar DbContext directamente en Application — solo via repositorios
&lt;span class="p"&gt;-&lt;/span&gt; No usar AutoMapper — mappings explícitos con métodos ToDto() / ToDomain()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este archivo transforma a Claude de "asistente genérico de C#" a "colega que conoce tu proyecto". La diferencia en calidad de respuestas es notable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dando contexto a Claude
&lt;/h2&gt;

&lt;p&gt;Tener un buen CLAUDE.md es el 50% del trabajo. El otro 50% es saber cómo dar contexto durante la sesión.&lt;/p&gt;

&lt;h3&gt;
  
  
  Menciones con @
&lt;/h3&gt;

&lt;p&gt;La sintaxis &lt;code&gt;@&lt;/code&gt; te permite referenciar archivos y directorios específicos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Program.cs                    # Archivo completo
@src/Application/              # Directorio entero
@appsettings.json#1-20         # Líneas 1 a 20 del archivo
@src/Domain/Entities/Order.cs  # Archivo en ruta relativa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ejemplos de uso en conversación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Revisa @src/Application/Orders/Commands/CreateOrderCommand.cs
y dime si hay algo que mejorar con los nuevos features de C# 14.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tengo un bug en @src/Infrastructure/Repositories/OrderRepository.cs#45-80.
El método GetPendingOrders() devuelve órdenes canceladas. ¿Ves el problema?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Contexto automático en VS Code
&lt;/h3&gt;

&lt;p&gt;En VS Code con la extensión instalada, el archivo que tienes abierto en el editor activo se comparte automáticamente como contexto. Si seleccionas un bloque de código, solo ese bloque se incluye. Esto elimina mucho del trabajo manual de menciones &lt;code&gt;@&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;En Visual Studio (terminal), necesitas ser explícito con &lt;code&gt;@&lt;/code&gt; — lo cual en realidad te obliga a ser más preciso sobre qué quieres mostrarle a Claude, lo cual no es necesariamente malo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gestionar el contexto largo con /compact
&lt;/h3&gt;

&lt;p&gt;Las sesiones largas tienen un límite de tokens. Cuando el contexto se alarga demasiado, Claude puede empezar a "olvidar" cosas del inicio de la conversación.&lt;/p&gt;

&lt;p&gt;El comando &lt;code&gt;/compact&lt;/code&gt; resume el historial de la sesión actual, preservando los puntos clave pero reduciendo el tamaño del contexto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Úsalo proactivamente cuando llevas un rato en la misma sesión, antes de llegar al límite. Es como hacer un checkpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills — comandos personalizados para tu flujo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F751yhzrhzjyr1jgnl0ob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F751yhzrhzjyr1jgnl0ob.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Los Skills son la característica más poderosa de Claude Code para equipos y proyectos recurrentes. Un Skill es esencialmente un &lt;strong&gt;comando personalizado&lt;/strong&gt; que encapsula un flujo de trabajo completo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dónde viven los Skills
&lt;/h3&gt;

&lt;p&gt;Hay dos ubicaciones:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ubicación&lt;/th&gt;
&lt;th&gt;Alcance&lt;/th&gt;
&lt;th&gt;Ruta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Personal&lt;/td&gt;
&lt;td&gt;Solo tú, todos tus proyectos&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.claude/skills/&amp;lt;nombre&amp;gt;/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proyecto&lt;/td&gt;
&lt;td&gt;Todo el equipo, este repo&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.claude/skills/&amp;lt;nombre&amp;gt;/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Los Skills de proyecto se versiona junto al código — cuando alguien hace &lt;code&gt;git clone&lt;/code&gt;, obtiene los Skills también. Ideal para workflows específicos del equipo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cómo invocar un Skill
&lt;/h3&gt;

&lt;p&gt;Simplemente escribe &lt;code&gt;/nombre-del-skill&lt;/code&gt; en Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dotnet-test
/csharp-review
/ef-migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude lee el archivo &lt;code&gt;SKILL.md&lt;/code&gt; correspondiente y ejecuta el flujo definido en él.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ejemplo 1: &lt;code&gt;/dotnet-test&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Crea el archivo &lt;code&gt;.claude/skills/dotnet-test/SKILL.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# dotnet-test&lt;/span&gt;

Ejecuta la suite de tests del proyecto y presenta los resultados de forma clara.

&lt;span class="gu"&gt;## Pasos&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Ejecuta &lt;span class="sb"&gt;`dotnet test ECommerceApi.sln --logger "console;verbosity=normal"`&lt;/span&gt;
&lt;span class="p"&gt;2.&lt;/span&gt; Si hay fallos, muestra SOLO los tests fallidos con:
&lt;span class="p"&gt;   -&lt;/span&gt; Nombre completo del test
&lt;span class="p"&gt;   -&lt;/span&gt; El mensaje de error
&lt;span class="p"&gt;   -&lt;/span&gt; La línea del código donde falló
&lt;span class="p"&gt;3.&lt;/span&gt; Si todos pasan, confirma con un resumen: total de tests, tiempo de ejecución
&lt;span class="p"&gt;4.&lt;/span&gt; Si hay errores de compilación, muéstralos primero antes de hablar de tests

&lt;span class="gu"&gt;## Notas&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; No muestres los tests que pasaron a menos que el usuario lo pida
&lt;span class="p"&gt;-&lt;/span&gt; Si el build falla, no intentes interpretar qué tests fallaron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ejemplo 2: &lt;code&gt;/csharp-review&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Crea &lt;code&gt;.claude/skills/csharp-review/SKILL.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# csharp-review&lt;/span&gt;

Revisa código C# buscando oportunidades de modernización con C# 14 / .NET 10.

&lt;span class="gu"&gt;## Qué revisar&lt;/span&gt;

&lt;span class="gu"&gt;### Records y tipos de valor&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; ¿Se puede reemplazar una clase inmutable con un record?
&lt;span class="p"&gt;-&lt;/span&gt; ¿Los DTOs usan record en vez de class?
&lt;span class="p"&gt;-&lt;/span&gt; ¿Se usan init-only properties donde corresponde?

&lt;span class="gu"&gt;### Pattern matching&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; ¿Hay cadenas de if/else que se pueden reemplazar con switch expressions?
&lt;span class="p"&gt;-&lt;/span&gt; ¿Los switches son exhaustivos sobre discriminated unions?
&lt;span class="p"&gt;-&lt;/span&gt; ¿Se usa list patterns donde aplica?

&lt;span class="gu"&gt;### Nullable reference types&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; ¿Hay supresiones con ! sin justificación?
&lt;span class="p"&gt;-&lt;/span&gt; ¿Los parámetros nullable están correctamente anotados?

&lt;span class="gu"&gt;### field keyword (C# 14)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; ¿Hay propiedades con backing fields explícitos que se pueden simplificar con field?

&lt;span class="gu"&gt;### Async/await&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; ¿Hay .Result o .Wait() que deberían ser await?
&lt;span class="p"&gt;-&lt;/span&gt; ¿Se usa ConfigureAwait correctamente en librerías?

&lt;span class="gu"&gt;## Formato de respuesta&lt;/span&gt;
Para cada issue encontrado:
&lt;span class="p"&gt;-&lt;/span&gt; Código original
&lt;span class="p"&gt;-&lt;/span&gt; Código mejorado
&lt;span class="p"&gt;-&lt;/span&gt; Una línea explicando por qué es mejor

No reportes lo que ya está bien — solo oportunidades de mejora.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ejemplo 3: &lt;code&gt;/ef-migrate&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Crea &lt;code&gt;.claude/skills/ef-migrate/SKILL.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# ef-migrate&lt;/span&gt;

Guía para agregar una migración de Entity Framework Core al proyecto.

&lt;span class="gu"&gt;## Pasos&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Ejecuta &lt;span class="sb"&gt;`dotnet ef migrations list --project src/ECommerceApi.Infrastructure --startup-project src/ECommerceApi.Api`&lt;/span&gt;
   para mostrar las migraciones existentes
&lt;span class="p"&gt;
2.&lt;/span&gt; Pregunta al usuario:
&lt;span class="p"&gt;   -&lt;/span&gt; ¿Qué cambio hiciste en el modelo de dominio?
&lt;span class="p"&gt;   -&lt;/span&gt; ¿Qué nombre quieres para la migración? (sugerencia: usa PascalCase descriptivo, ej: AddOrderShippingAddress)
&lt;span class="p"&gt;
3.&lt;/span&gt; Ejecuta:
   &lt;span class="sb"&gt;`dotnet ef migrations add &amp;lt;NombreMigracion&amp;gt; --project src/ECommerceApi.Infrastructure --startup-project src/ECommerceApi.Api`&lt;/span&gt;
&lt;span class="p"&gt;
4.&lt;/span&gt; Lee el archivo de migración generado y verifica:
&lt;span class="p"&gt;   -&lt;/span&gt; Que el Up() tiene el cambio esperado
&lt;span class="p"&gt;   -&lt;/span&gt; Que el Down() revierte correctamente
&lt;span class="p"&gt;   -&lt;/span&gt; Que no hay columnas &lt;span class="sb"&gt;`nvarchar(max)`&lt;/span&gt; donde debería haber un tamaño específico
&lt;span class="p"&gt;
5.&lt;/span&gt; Si el usuario quiere aplicar la migración:
   &lt;span class="sb"&gt;`dotnet ef database update --project src/ECommerceApi.Infrastructure --startup-project src/ECommerceApi.Api`&lt;/span&gt;

&lt;span class="gu"&gt;## Notas&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Nunca ejecutes database update en producción desde aquí — solo desarrollo local
&lt;span class="p"&gt;-&lt;/span&gt; Si hay conflictos de migración, muestra el error completo antes de sugerir soluciones
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Estos tres Skills solos ya justifican el setup. Imagina tener un equipo de 10 personas donde todos invocan &lt;code&gt;/csharp-review&lt;/code&gt; y obtienen el mismo estándar de revisión.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP — conectando herramientas externas
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) es el mecanismo que permite a Claude Code conectarse con servicios externos como si fueran herramientas nativas.&lt;/p&gt;

&lt;p&gt;En términos simples: un servidor MCP expone capacidades (leer PRs, consultar tickets, buscar documentación) y Claude puede usarlas durante la sesión.&lt;/p&gt;

&lt;p&gt;Dos ejemplos relevantes para equipos .NET:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub MCP&lt;/strong&gt; — Claude puede leer los PRs abiertos del repo, ver los comentarios de revisión, y ayudarte a resolverlos sin que copies nada manualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;¿Qué cambios pendientes tengo en el PR #247?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Azure DevOps MCP&lt;/strong&gt; — Claude puede ver el contexto del sprint actual, las user stories asignadas, y entender qué estás construyendo sin que tengas que explicarlo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;¿Cuáles son los criterios de aceptación de la historia US-1089?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los servidores MCP se configuran en &lt;code&gt;~/.claude/mcp.json&lt;/code&gt; o &lt;code&gt;.claude/mcp.json&lt;/code&gt; a nivel de proyecto. La documentación oficial de Claude Code tiene la lista de servidores MCP disponibles y cómo configurarlos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cuándo usar VS Code vs Visual Studio
&lt;/h2&gt;

&lt;p&gt;La pregunta que todo desarrollador .NET acaba haciendo. La respuesta honesta:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterio&lt;/th&gt;
&lt;th&gt;VS Code + Claude Code&lt;/th&gt;
&lt;th&gt;Visual Studio 2022/2026&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Integración con Claude Code&lt;/td&gt;
&lt;td&gt;Completa — contexto automático, extensión nativa&lt;/td&gt;
&lt;td&gt;Funcional — terminal integrada, sin extensión&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging de aplicaciones .NET&lt;/td&gt;
&lt;td&gt;Bueno (con extensión C#)&lt;/td&gt;
&lt;td&gt;Excelente — el mejor de la industria&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IntelliSense y refactoring&lt;/td&gt;
&lt;td&gt;Bueno&lt;/td&gt;
&lt;td&gt;Excepcional — Roslyn completo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diseñadores visuales (WinForms, WPF, MAUI)&lt;/td&gt;
&lt;td&gt;No disponible&lt;/td&gt;
&lt;td&gt;Completos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Profiling y diagnósticos&lt;/td&gt;
&lt;td&gt;Limitado&lt;/td&gt;
&lt;td&gt;Herramientas de clase mundial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup y rendimiento del IDE&lt;/td&gt;
&lt;td&gt;Rápido&lt;/td&gt;
&lt;td&gt;Lento en proyectos grandes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desarrollo multiplataforma&lt;/td&gt;
&lt;td&gt;Ideal&lt;/td&gt;
&lt;td&gt;Limitado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trabajo con soluciones grandes (50+ proyectos)&lt;/td&gt;
&lt;td&gt;Puede ser lento&lt;/td&gt;
&lt;td&gt;Optimizado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skills y workflows de equipo&lt;/td&gt;
&lt;td&gt;Integración nativa&lt;/td&gt;
&lt;td&gt;Via terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Recomendación práctica:&lt;/strong&gt; No elijas uno. Úsalos en paralelo.&lt;/p&gt;

&lt;p&gt;Visual Studio sigue siendo insustituible para debugging pesado, profiling de memoria, y diseñadores de UI de escritorio. VS Code con Claude Code es donde vas a escribir código más rápido, refactorizar más limpio, y entender más fácil código ajeno.&lt;/p&gt;

&lt;p&gt;Muchos equipos .NET han adoptado el siguiente flujo: VS Code para el día a día con Claude Code activo, Visual Studio cuando necesitan el debugger avanzado o las herramientas de diagnóstico. Los dos conviven perfectamente apuntando al mismo proyecto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Puntos clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CLAUDE.md es tu punto de partida&lt;/strong&gt; — sin él, Claude no conoce tu proyecto. Con él, se convierte en un colaborador que entiende tu arquitectura, tus convenciones y tus decisiones técnicas. Ejecuta &lt;code&gt;/init&lt;/code&gt; para generarlo y refínalo con el tiempo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;La integración de VS Code es completa; Visual Studio usa la terminal&lt;/strong&gt; — en VS Code, la extensión &lt;code&gt;anthropic.claude-code&lt;/code&gt; (requiere 1.98+) te da contexto automático. En Visual Studio, usa &lt;code&gt;Ver &amp;gt; Terminal&lt;/code&gt; y corre &lt;code&gt;claude&lt;/code&gt; ahí. Funciona igual de bien para la mayoría de tareas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Las menciones &lt;code&gt;@&lt;/code&gt; son tu herramienta de precisión&lt;/strong&gt; — &lt;code&gt;@archivo.cs&lt;/code&gt;, &lt;code&gt;@directorio/&lt;/code&gt;, y &lt;code&gt;@archivo.cs#10-40&lt;/code&gt; te permiten dar a Claude exactamente el contexto que necesita. En VS Code, el archivo activo se comparte automáticamente; en Visual Studio, sé explícito.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Los Skills son tu ventaja de equipo&lt;/strong&gt; — crea Skills en &lt;code&gt;.claude/skills/&lt;/code&gt; y versiónalos junto al código. Un &lt;code&gt;/csharp-review&lt;/code&gt; consistente para todo el equipo es más valioso que cualquier guideline de código que nadie lee.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gestiona el contexto activamente&lt;/strong&gt; — usa &lt;code&gt;/compact&lt;/code&gt; antes de que se alargue demasiado la sesión, y usa &lt;code&gt;/memory&lt;/code&gt; para verificar qué CLAUDE.md tiene cargado. Entender cómo Claude descubre el contexto (recorrido del filesystem, no archivos de proyecto) elimina mucha confusión.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Claude Code no reemplaza tu conocimiento de .NET — lo amplifica. La diferencia entre usarlo mediocremente y usarlo bien está en esas tres cosas: un buen &lt;code&gt;CLAUDE.md&lt;/code&gt;, contexto preciso con &lt;code&gt;@&lt;/code&gt;, y Skills que encapsulan tu flujo de trabajo. Con eso, la magia deja de parecer aleatoria.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>C# 14: The `field` Keyword — Cleaner Properties, Zero Boilerplate</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Wed, 10 Jun 2026 21:20:58 +0000</pubDate>
      <link>https://dev.to/jgomezdev/c-14-the-field-keyword-cleaner-properties-zero-boilerplate-1eac</link>
      <guid>https://dev.to/jgomezdev/c-14-the-field-keyword-cleaner-properties-zero-boilerplate-1eac</guid>
      <description>&lt;h1&gt;
  
  
  C# 14: The &lt;code&gt;field&lt;/code&gt; Keyword — Cleaner Properties, Zero Boilerplate
&lt;/h1&gt;

&lt;p&gt;Every C# developer has been there. You start with a clean auto-property, then requirements change and you need to add a tiny bit of validation. Suddenly that one-liner explodes into six lines of boilerplate — a private backing field, a getter that just returns it, a setter that assigns it. The logic is two words. The ceremony is everything else.&lt;/p&gt;

&lt;p&gt;C# 14 fixes this with the &lt;code&gt;field&lt;/code&gt; keyword: a contextual keyword that refers to the compiler-synthesized backing field of a property, letting you write custom accessor logic without ever declaring an explicit field.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Boilerplate Tax on Simple Properties
&lt;/h2&gt;

&lt;p&gt;Auto-properties are one of C#'s best quality-of-life features. This is clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the moment you need to trim whitespace on assignment, that cleanness evaporates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;_username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_username&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have six lines — and four of them exist only to hold the shape of the pattern together. The backing field &lt;code&gt;_username&lt;/code&gt; is not carrying any meaningful design weight. Its only job is to be a storage slot that &lt;code&gt;Username&lt;/code&gt; uses privately. You already &lt;em&gt;know&lt;/em&gt; the compiler creates one for auto-properties. You are just forced to make it visible so you can reference it.&lt;/p&gt;

&lt;p&gt;This is the boilerplate tax. You pay it every time you add even the smallest piece of logic to a property.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why &lt;code&gt;field&lt;/code&gt; Exists
&lt;/h2&gt;

&lt;p&gt;The C# language team has discussed this friction for years. The challenge was finding syntax that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unambiguous&lt;/strong&gt; — no conflict with existing identifiers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Familiar&lt;/strong&gt; — consistent with how &lt;code&gt;value&lt;/code&gt; works in setters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoped&lt;/strong&gt; — only meaningful inside a property accessor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The solution landed in C# 14: the contextual keyword &lt;code&gt;field&lt;/code&gt;. Just like &lt;code&gt;value&lt;/code&gt; refers to the incoming assignment in a setter, &lt;code&gt;field&lt;/code&gt; refers to the hidden backing storage the compiler manages for the property.&lt;/p&gt;

&lt;p&gt;It is contextual, which means it only acts as a keyword inside a property accessor. Everywhere else in your code, &lt;code&gt;field&lt;/code&gt; is still a valid identifier name (with one gotcha covered later).&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Syntax
&lt;/h3&gt;

&lt;p&gt;Here is the same username property rewritten with &lt;code&gt;field&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;get&lt;/code&gt; accessor is still auto-implemented — it just returns &lt;code&gt;field&lt;/code&gt;. The &lt;code&gt;set&lt;/code&gt; accessor does its one job: trim the incoming value before storing it. The private backing field is gone. The compiler synthesizes it behind the scenes, and &lt;code&gt;field&lt;/code&gt; lets you reach into it.&lt;/p&gt;

&lt;p&gt;You can also go the other way — implement &lt;code&gt;get&lt;/code&gt; and auto-implement &lt;code&gt;set&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DisplayName&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUpperInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or implement both sides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Age&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Age cannot be negative."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Works with &lt;code&gt;init&lt;/code&gt; Too
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;init&lt;/code&gt;-only properties work exactly the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;init&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially useful in immutable record-style classes where you want normalization on construction but no mutation afterwards.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmghfnswxr5wdhxj6ccqz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmghfnswxr5wdhxj6ccqz.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Usage Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Input Normalization
&lt;/h3&gt;

&lt;p&gt;The most common use case. Trim strings, normalize casing, sanitize user input — all without a backing field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Blog.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;DisplayName&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three properties look almost as clean as auto-properties, and each enforces its own invariant at the point of assignment.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Range Validation
&lt;/h3&gt;

&lt;p&gt;Numeric properties that must stay within bounds are a perfect fit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Blog.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductListing&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;MaxPrice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;99_999.99m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfNegative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MaxPrice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;StockQuantity&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Quietly clamp negatives — intentional product decision&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;ArgumentOutOfRangeException.ThrowIfNegative&lt;/code&gt; and &lt;code&gt;ThrowIfGreaterThan&lt;/code&gt; — those are .NET 8+ helpers that are cleaner than manual &lt;code&gt;if&lt;/code&gt; throws. Use the modern APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Lazy Initialization
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;field&lt;/code&gt; shines for lazy-loaded properties. The classic pattern used to need a nullable backing field and a null-check dance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before C# 14 — the old dance&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;_tags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_tags&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;field&lt;/code&gt;, you write the same logic right inside the property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line. The compiler owns the backing field. You own the logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Change Notification (INotifyPropertyChanged)
&lt;/h3&gt;

&lt;p&gt;ViewModel properties in MVVM apps often look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Blog.ViewModels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;INotifyPropertyChanged&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;PropertyChangedEventHandler&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;CustomerName&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PropertyChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomerName&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PropertyChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No private fields. No name collision risks. The pattern is still verbose (that is just the nature of &lt;code&gt;INotifyPropertyChanged&lt;/code&gt;), but &lt;code&gt;field&lt;/code&gt; removes one moving part from it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The &lt;code&gt;field&lt;/code&gt; Gotcha: When Your Property Is Named &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Here is where things get interesting. Because &lt;code&gt;field&lt;/code&gt; is a &lt;em&gt;contextual&lt;/em&gt; keyword, a property actually named &lt;code&gt;Field&lt;/code&gt; (or &lt;code&gt;field&lt;/code&gt; in a case-sensitive context) can shadow the keyword. Consider this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseRecord&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This property is named "field" — uncommon, but legal&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Inside here, does "field" mean the keyword or the property itself?&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Ambiguous!&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler will warn you about the ambiguity. The fix is the verbatim identifier &lt;code&gt;@field&lt;/code&gt;, which forces C# to treat it as an identifier, not the keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseRecord&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;@field&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;@field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Explicit: this is the identifier, not the keyword&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Realistically, naming a property &lt;code&gt;field&lt;/code&gt; is unusual. But if you work with code that maps to external schemas or databases that use reserved names, now you know the escape hatch.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj05pmckv9shu56vjy8v6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj05pmckv9shu56vjy8v6.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;field&lt;/code&gt; whenever you need &lt;em&gt;some&lt;/em&gt; logic in a property accessor but the property is still the sole owner of its value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Normalization on assignment&lt;/strong&gt; — trimming, casing, formatting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation on assignment&lt;/strong&gt; — range checks, null guards, format checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lazy initialization&lt;/strong&gt; — compute-on-first-access patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple change tracking&lt;/strong&gt; — equality check before raising events&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;init&lt;/code&gt;-only normalization&lt;/strong&gt; — immutable objects that need clean input&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule of thumb: if you would previously have written a private backing field that &lt;em&gt;nothing else in the class references&lt;/em&gt;, replace it with &lt;code&gt;field&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  When NOT to Use &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;field&lt;/code&gt; is not a silver bullet. There are cases where an explicit backing field is still the right tool:&lt;/p&gt;

&lt;h3&gt;
  
  
  The constructor bypass problem
&lt;/h3&gt;

&lt;p&gt;Suppose you have a property that raises an event when assigned. You want the constructor to set the initial value &lt;em&gt;silently&lt;/em&gt;, without firing the event. With &lt;code&gt;field&lt;/code&gt;, you cannot do that — the only way into the backing storage is through the property accessor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sensor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The event fires on every set — we want to skip it in the constructor&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;Temperature&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;ReadingChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EventArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ReadingChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Sensor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;initialTemperature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// This fires ReadingChanged during construction — probably not what you want&lt;/span&gt;
        &lt;span class="n"&gt;Temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initialTemperature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need the constructor to bypass the property's logic, keep the explicit backing field and set it directly from the constructor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple properties sharing state
&lt;/h3&gt;

&lt;p&gt;If two or more properties read from or write to the same underlying value — for example a &lt;code&gt;Radius&lt;/code&gt; and a &lt;code&gt;Diameter&lt;/code&gt; that are computed from each other — you need an explicit field that both properties reference. &lt;code&gt;field&lt;/code&gt; is per-property; it cannot be shared.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complex derived state
&lt;/h3&gt;

&lt;p&gt;If the "backing storage" is not a simple value but involves multiple fields, caches, or objects, an explicit field with a descriptive name communicates intent far better than the anonymous &lt;code&gt;field&lt;/code&gt; keyword.&lt;/p&gt;

&lt;h3&gt;
  
  
  When a name is important for debugging
&lt;/h3&gt;

&lt;p&gt;Explicit backing fields appear in debuggers with their declared name. The compiler-synthesized backing field for &lt;code&gt;field&lt;/code&gt;-based properties gets a generated name (like &lt;code&gt;&amp;lt;Username&amp;gt;k__BackingField&lt;/code&gt;) that is less readable in memory dumps or diagnostic tools. For hot paths under heavy diagnostics, you might prefer the explicit field.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;field&lt;/code&gt; is a contextual keyword in C# 14 that refers to the compiler-synthesized backing field of a property — the same one auto-properties already create.&lt;/li&gt;
&lt;li&gt;It lets you add logic to one or both accessors without promoting the backing field to an explicit private member.&lt;/li&gt;
&lt;li&gt;It works in &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;set&lt;/code&gt;, and &lt;code&gt;init&lt;/code&gt; accessors, and you can mix auto-implementation with &lt;code&gt;field&lt;/code&gt;-based implementation in the same property.&lt;/li&gt;
&lt;li&gt;The gotcha to remember: if a property is named &lt;code&gt;field&lt;/code&gt;, use &lt;code&gt;@field&lt;/code&gt; to disambiguate the identifier from the keyword.&lt;/li&gt;
&lt;li&gt;Prefer an explicit backing field when: the constructor needs to bypass property logic, multiple properties share one storage slot, or the field name matters for diagnostics.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>C# 14: La keyword `field` — Propiedades más limpias, cero boilerplate</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Wed, 10 Jun 2026 21:18:31 +0000</pubDate>
      <link>https://dev.to/jgomezdev/c-14-la-keyword-field-propiedades-mas-limpias-cero-boilerplate-579i</link>
      <guid>https://dev.to/jgomezdev/c-14-la-keyword-field-propiedades-mas-limpias-cero-boilerplate-579i</guid>
      <description>&lt;h1&gt;
  
  
  C# 14: La keyword &lt;code&gt;field&lt;/code&gt; — Propiedades más limpias, cero boilerplate
&lt;/h1&gt;

&lt;p&gt;Todo desarrollador de C# lo ha vivido. Empiezas con una auto-propiedad limpia, llegan nuevos requerimientos y necesitas agregar una pequeña validación. De repente esa línea explota en seis líneas de boilerplate — un campo privado, un getter que solo lo retorna, un setter que lo asigna. La lógica son dos palabras. La ceremonia es todo lo demás.&lt;/p&gt;

&lt;p&gt;C# 14 resuelve esto con la keyword &lt;code&gt;field&lt;/code&gt;: una keyword contextual que hace referencia al backing field sintetizado por el compilador de una propiedad, permitiéndote escribir lógica personalizada en los accessors sin necesidad de declarar un campo explícito.&lt;/p&gt;




&lt;h2&gt;
  
  
  El problema: el impuesto del boilerplate
&lt;/h2&gt;

&lt;p&gt;Las auto-propiedades son una de las mejores mejoras de calidad de vida en C#. Esto es limpio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pero en el momento en que necesitas hacer un trim del espacio en blanco al asignar, esa limpieza desaparece:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;_username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_username&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora tienes seis líneas — y cuatro de ellas existen únicamente para mantener la forma del patrón. El campo privado &lt;code&gt;_username&lt;/code&gt; no aporta ningún peso de diseño significativo. Su único trabajo es ser un slot de almacenamiento que &lt;code&gt;Username&lt;/code&gt; usa de forma privada. Ya &lt;em&gt;sabes&lt;/em&gt; que el compilador crea uno para las auto-propiedades. Solo estás obligado a hacerlo visible para poder referenciarlo.&lt;/p&gt;

&lt;p&gt;Este es el impuesto del boilerplate. Lo pagas cada vez que agregas la más mínima lógica a una propiedad.&lt;/p&gt;




&lt;h2&gt;
  
  
  Por qué existe &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;El equipo de C# ha discutido esta fricción durante años. El desafío era encontrar una sintaxis que fuera:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inequívoca&lt;/strong&gt; — sin conflicto con identificadores existentes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Familiar&lt;/strong&gt; — consistente con cómo funciona &lt;code&gt;value&lt;/code&gt; en los setters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acotada&lt;/strong&gt; — con significado solo dentro de un accessor de propiedad&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La solución llegó en C# 14: la keyword contextual &lt;code&gt;field&lt;/code&gt;. Igual que &lt;code&gt;value&lt;/code&gt; hace referencia al valor entrante en un setter, &lt;code&gt;field&lt;/code&gt; hace referencia al almacenamiento interno que el compilador administra para la propiedad.&lt;/p&gt;

&lt;p&gt;Es contextual, lo que significa que solo actúa como keyword dentro de un accessor de propiedad. En el resto de tu código, &lt;code&gt;field&lt;/code&gt; sigue siendo un nombre de identificador válido (con una trampa que veremos más adelante).&lt;/p&gt;




&lt;h2&gt;
  
  
  Cómo funciona
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sintaxis básica
&lt;/h3&gt;

&lt;p&gt;La misma propiedad &lt;code&gt;Username&lt;/code&gt; reescrita con &lt;code&gt;field&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El accessor &lt;code&gt;get&lt;/code&gt; sigue siendo auto-implementado — simplemente retorna &lt;code&gt;field&lt;/code&gt;. El accessor &lt;code&gt;set&lt;/code&gt; hace su único trabajo: hacer trim al valor entrante antes de almacenarlo. El campo privado desapareció. El compilador lo sintetiza en segundo plano, y &lt;code&gt;field&lt;/code&gt; te permite acceder a él.&lt;/p&gt;

&lt;p&gt;También puedes hacerlo al revés — implementar &lt;code&gt;get&lt;/code&gt; y auto-implementar &lt;code&gt;set&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DisplayName&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUpperInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O implementar ambos lados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Age&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"La edad no puede ser negativa."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  También funciona con &lt;code&gt;init&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Las propiedades de solo inicialización funcionan exactamente igual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;init&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto es especialmente útil en clases inmutables estilo record donde quieres normalización al construir pero sin mutación posterior.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3t22hhlf1bgcr8k04yo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3t22hhlf1bgcr8k04yo.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Patrones de uso en el mundo real
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Normalización de entrada
&lt;/h3&gt;

&lt;p&gt;El caso de uso más común. Trim de strings, normalización de mayúsculas, saneamiento de input del usuario — todo sin un backing field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Blog.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;DisplayName&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Las tres propiedades se ven casi tan limpias como auto-propiedades, y cada una aplica su propio invariante en el punto de asignación.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Validación de rango
&lt;/h3&gt;

&lt;p&gt;Las propiedades numéricas que deben mantenerse dentro de límites son un caso perfecto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Blog.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductListing&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;MaxPrice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;99_999.99m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfNegative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MaxPrice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;StockQuantity&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Clamp silencioso — decisión intencional de producto&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nota &lt;code&gt;ArgumentOutOfRangeException.ThrowIfNegative&lt;/code&gt; y &lt;code&gt;ThrowIfGreaterThan&lt;/code&gt; — helpers de .NET 8+ que son más limpios que los &lt;code&gt;if&lt;/code&gt; manuales con throw. Usa las APIs modernas.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Inicialización lazy
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;field&lt;/code&gt; brilla en propiedades de carga diferida. El patrón clásico requería un backing field nullable y una danza de null-check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Antes de C# 14 — el baile de siempre&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;_tags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_tags&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con &lt;code&gt;field&lt;/code&gt;, escribes la misma lógica directamente dentro de la propiedad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una línea. El compilador administra el backing field. Tú administras la lógica.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Notificación de cambios (INotifyPropertyChanged)
&lt;/h3&gt;

&lt;p&gt;Las propiedades de ViewModel en aplicaciones MVVM suelen verse así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Blog.ViewModels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;INotifyPropertyChanged&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;PropertyChangedEventHandler&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;CustomerName&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PropertyChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomerName&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PropertyChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sin campos privados. Sin riesgo de colisión de nombres. El patrón sigue siendo verboso (esa es la naturaleza de &lt;code&gt;INotifyPropertyChanged&lt;/code&gt;), pero &lt;code&gt;field&lt;/code&gt; elimina una pieza móvil del rompecabezas.&lt;/p&gt;




&lt;h2&gt;
  
  
  La trampa de &lt;code&gt;field&lt;/code&gt;: cuando tu propiedad se llama &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Aquí las cosas se ponen interesantes. Como &lt;code&gt;field&lt;/code&gt; es una keyword &lt;em&gt;contextual&lt;/em&gt;, una propiedad que se llame efectivamente &lt;code&gt;field&lt;/code&gt; puede entrar en conflicto con la keyword. Considera esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseRecord&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Esta propiedad se llama "field" — inusual, pero legal&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Dentro de aquí, ¿"field" es la keyword o la propiedad misma?&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// ¡Ambiguo!&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El compilador te advertirá sobre la ambigüedad. La solución es el identificador verbatim &lt;code&gt;@field&lt;/code&gt;, que le indica a C# que lo trate como un identificador y no como la keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseRecord&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;@field&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;@field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Explícito: este es el identificador, no la keyword&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En la práctica, nombrar una propiedad &lt;code&gt;field&lt;/code&gt; es inusual. Pero si trabajas con código que mapea a esquemas externos o bases de datos que usan nombres reservados, ahora conoces la salida de emergencia.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp1c172epk34irufkm82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp1c172epk34irufkm82.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cuándo usar &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Usa &lt;code&gt;field&lt;/code&gt; cuando necesitas &lt;em&gt;algo&lt;/em&gt; de lógica en un accessor pero la propiedad sigue siendo la única dueña de su valor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Normalización en asignación&lt;/strong&gt; — trim, mayúsculas, formato&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validación en asignación&lt;/strong&gt; — rangos, null guards, formato&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inicialización lazy&lt;/strong&gt; — patrones de cálculo en primer acceso&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change tracking simple&lt;/strong&gt; — verificación de igualdad antes de disparar eventos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalización en &lt;code&gt;init&lt;/code&gt;&lt;/strong&gt; — objetos inmutables que necesitan entrada limpia&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La regla de oro: si antes hubieras escrito un backing field privado al que &lt;em&gt;nada más en la clase hace referencia&lt;/em&gt;, reemplázalo con &lt;code&gt;field&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cuándo NO usar &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;field&lt;/code&gt; no es una bala de plata. Hay casos donde un backing field explícito sigue siendo la herramienta correcta:&lt;/p&gt;

&lt;h3&gt;
  
  
  El problema del bypass en el constructor
&lt;/h3&gt;

&lt;p&gt;Supón que tienes una propiedad que dispara un evento al asignarse. Quieres que el constructor establezca el valor inicial &lt;em&gt;silenciosamente&lt;/em&gt;, sin disparar el evento. Con &lt;code&gt;field&lt;/code&gt; no puedes hacerlo — la única forma de acceder al almacenamiento interno es a través del accessor de la propiedad.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sensor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// El evento se dispara en cada set — queremos saltarlo en el constructor&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;Temperature&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;ReadingChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EventArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ReadingChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Sensor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;initialTemperature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Esto dispara ReadingChanged durante la construcción — probablemente no es lo que quieres&lt;/span&gt;
        &lt;span class="n"&gt;Temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initialTemperature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si el constructor necesita saltarse la lógica de la propiedad, mantén el backing field explícito y asígnalo directamente desde el constructor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Múltiples propiedades que comparten estado
&lt;/h3&gt;

&lt;p&gt;Si dos o más propiedades leen o escriben el mismo valor subyacente — por ejemplo &lt;code&gt;Radius&lt;/code&gt; y &lt;code&gt;Diameter&lt;/code&gt; calculadas una a partir de la otra — necesitas un campo explícito al que ambas propiedades hagan referencia. &lt;code&gt;field&lt;/code&gt; es por propiedad; no puede compartirse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Estado derivado complejo
&lt;/h3&gt;

&lt;p&gt;Si el "almacenamiento interno" no es un valor simple sino que involucra múltiples campos, cachés u objetos, un campo explícito con un nombre descriptivo comunica la intención mucho mejor que la keyword anónima &lt;code&gt;field&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cuando el nombre importa para el diagnóstico
&lt;/h3&gt;

&lt;p&gt;Los backing fields explícitos aparecen en los debuggers con su nombre declarado. El backing field sintetizado por el compilador para propiedades basadas en &lt;code&gt;field&lt;/code&gt; recibe un nombre generado (como &lt;code&gt;&amp;lt;Username&amp;gt;k__BackingField&lt;/code&gt;) que es menos legible en memory dumps o herramientas de diagnóstico. Para rutas críticas bajo diagnósticos intensivos, puede que prefieras el campo explícito.&lt;/p&gt;




&lt;h2&gt;
  
  
  Puntos clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;field&lt;/code&gt; es una keyword contextual de C# 14 que hace referencia al backing field sintetizado por el compilador — el mismo que las auto-propiedades ya crean.&lt;/li&gt;
&lt;li&gt;Permite agregar lógica a uno o ambos accessors sin promover el backing field a un miembro privado explícito.&lt;/li&gt;
&lt;li&gt;Funciona en accessors &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;set&lt;/code&gt; e &lt;code&gt;init&lt;/code&gt;, y puedes mezclar auto-implementación con implementación basada en &lt;code&gt;field&lt;/code&gt; en la misma propiedad.&lt;/li&gt;
&lt;li&gt;La trampa a recordar: si una propiedad se llama &lt;code&gt;field&lt;/code&gt;, usa &lt;code&gt;@field&lt;/code&gt; para distinguir el identificador de la keyword.&lt;/li&gt;
&lt;li&gt;Prefiere un backing field explícito cuando: el constructor necesita saltarse la lógica de la propiedad, múltiples propiedades comparten un slot de almacenamiento, o el nombre del campo importa para el diagnóstico.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
