Every way to store data
Whole files, streams, CSV, JSON, binary, EF Core on a relational database, raw SQL, Dapper and MongoDB. Where to keep your data, and how to read it back.
Ask how much data there is, who else reads it, and how you'll look things up.
- A small file your program owns (settings, a saved game, a
to-do list) → JSON with
System.Text.Json, read and written whole withFile.ReadAllTextandFile.WriteAllText. - A big file, or one you process line by line (a log, an
import) → a stream with a
StreamReaderorStreamWriter, so it never has to fit in memory. - A spreadsheet will open it, or one sends it to you → CSV, with quoting done properly, or a library such as CsvHelper once the format gets messy.
- A format defined in bytes (an image header, a checksum) → binary reading and writing.
- Many records, related to each other, changed by many users (customers, orders, stock) → a relational database through EF Core. SQLite for a single-file database, a server database for a shared one.
- A report or read-heavy query where you want full control of the SQL → EF Core's raw SQL, or Dapper. Always with parameters.
- Each record is read whole by its id and records vary in shape (a product catalogue) → a document database such as MongoDB.
Write down the three queries your app runs most before choosing a database. If they cut across entities, stay relational.
Files
Section
Reading and writing whole files
Path.Combine, File.ReadAllText, File.WriteAllLines and friends, for files that fit in memory.
Section
Streams, readers and writers
A line at a time, for files of any size.
Section
CSV files
Commas and quotes inside fields, and when to reach for CsvHelper.
Section
JSON with System.Text.Json
Serialize an object to JSON and back, and handle names and types that don't match.
Section
Binary data
Bytes, encodings, BinaryWriter and checksums, and why JSON is usually the better first choice.
Relational databases
Section
Tables, rows, keys and SQL
What EF Core is writing for you underneath.
Classes map to tables, and the DbContext is your session with the database.
One model, four providers, and where the connection string comes from.
Section
Raw SQL and Dapper
The choosing paragraph for EF Core, EF Core raw SQL and Dapper, all parameterized.
Formal. A value concatenated into SQL text becomes part of the
command and can change its meaning (SQL injection). A parameterized
query sends the command text and the values separately; the database
treats each value as data of its declared type, never as SQL. EF Core
LINQ queries send captured variables as parameters; a constant written
inside the lambda (p => p.Price > 5m) is embedded as a literal, which
is safe because it is part of your code, not input. For raw SQL, FromSql/
SqlQuery/ExecuteSql take an interpolated string and turn each
{value} into a parameter, while FromSqlRaw with string concatenation
does not. Dapper parameterizes @name placeholders from an object.
In plain English. If you paste what a user typed into a database command, a crafted input can rewrite the command, for example to delete a table or read other people's data. Sending the values separately, as parameters, makes the database treat them only as values. EF Core does this for you as long as you use LINQ or its interpolated raw-SQL methods.
Example. db.Products.FromSql($"SELECT * FROM Products WHERE Name = {name}") sends @p0 with the name as a parameter. FromSqlRaw("... WHERE Name = '" + name + "'") lets x' OR '1'='1 return every row.
Why this rule exists. Keeping code and data apart is the only reliable defence; escaping by hand is easy to get wrong.
User input goes into SQL as a parameter, never pasted into the query text.
Documents
Section
Document databases and MongoDB
Embed what's read together, reference what's shared, and when documents fit at all.