Massive
Massive is a simple, dynamic .NET data access library. It achieves ultimate lightweight design with single-file implementation and zero dependencies, making it optimized for prototyping and small projects. With dynamic typing and simple API, it's suitable for rapid development and REPL environment usage.
GitHub Overview
FransBouma/Massive
A small, happy, dynamic MicroORM for .NET that will love you forever.
Topics
Star History
Library
Massive
Overview
Massive is a simple, dynamic .NET data access library. It achieves ultimate lightweight design with single-file implementation and zero dependencies, making it optimized for prototyping and small projects. With dynamic typing and simple API, it's suitable for rapid development and REPL environment usage.
Details
Massive 2025 edition continues to be used in projects prioritizing simplicity. It is valued for prototyping and REPL environment development, maintaining the spirit of Rob Conery's original Massive while being maintained by Frans Bouma. Supporting SQL Server, PostgreSQL, MySQL, and SQLite, its dynamic ExpandoObject-based approach allows flexible adaptation to schema changes.
Key Features
- Single File Implementation: Complete in just one C# file
- Zero Dependencies: No external libraries required
- Dynamic Typing: Flexible data access through ExpandoObject
- Simple API: Intuitive with minimal learning cost
- Multi-database: Support for SQL Server, PostgreSQL, MySQL, SQLite
- Lightweight: Minimal memory footprint
Pros and Cons
Pros
- Extremely easy to implement (just copy the file)
- Nearly flat learning curve
- Perfect for prototyping
- Flexible adaptation to schema changes
- Suitable for REPL environment usage
- Minimal overhead for small projects
Cons
- Lack of type safety
- Limited IntelliSense support
- Not suitable for large projects
- Few performance optimization options
- No advanced ORM features (relationship management, etc.)
- Lack of enterprise features
References
Examples
Basic Setup
// Just add Massive.cs file to your project
// No NuGet package required
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using Massive;
// Define table classes
public class Users : DynamicModel
{
public Users() : base("BlogConnection", "Users", "Id") { }
}
public class Posts : DynamicModel
{
public Posts() : base("BlogConnection", "Posts", "Id") { }
}
// app.config / appsettings.json
// <connectionStrings>
// <add name="BlogConnection"
// connectionString="Server=localhost;Database=BlogDB;..." />
// </connectionStrings>
Basic CRUD Operations
public class BasicOperations
{
public static void DemoCRUD()
{
var users = new Users();
// CREATE - Create new record
// Create with dynamic object
var newUser = new ExpandoObject() as dynamic;
newUser.Name = "John Doe";
newUser.Email = "[email protected]";
newUser.Age = 30;
newUser.CreatedAt = DateTime.Now;
var userId = users.Insert(newUser);
Console.WriteLine($"Created user with ID: {userId}");
// Can also create with anonymous object
var anotherUser = users.Insert(new {
Name = "Jane Smith",
Email = "[email protected]",
Age = 25,
CreatedAt = DateTime.Now
});
// READ - Read records
// Get single record
dynamic user = users.Single(userId);
Console.WriteLine($"User: {user.Name}, Email: {user.Email}");
// Get all records
var allUsers = users.All();
foreach (dynamic u in allUsers)
{
Console.WriteLine($"{u.Name} ({u.Age})");
}
// Conditional search
var adults = users.All(where: "Age >= @0", args: 18);
var searchResults = users.All(
where: "Name LIKE @0 OR Email LIKE @0",
args: "%John%"
);
// UPDATE - Update record
user.Age = 31;
user.UpdatedAt = DateTime.Now;
users.Update(user, userId);
// Partial update
users.Update(new { Age = 32 }, userId);
// DELETE - Delete record
users.Delete(userId);
// Conditional delete
users.Delete(where: "CreatedAt < @0", args: DateTime.Now.AddYears(-1));
}
}
Advanced Query Operations
public class AdvancedQueries
{
private readonly Users _users = new Users();
private readonly Posts _posts = new Posts();
// Pagination
public IEnumerable<dynamic> GetPagedUsers(int page, int pageSize)
{
var offset = (page - 1) * pageSize;
// SQL Server
var sql = @"
SELECT * FROM Users
ORDER BY Name
OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY";
return _users.Query(sql, offset, pageSize);
}
// Aggregate queries
public dynamic GetUserStatistics()
{
var sql = @"
SELECT
COUNT(*) as TotalUsers,
AVG(CAST(Age as float)) as AverageAge,
MAX(Age) as MaxAge,
MIN(Age) as MinAge
FROM Users
WHERE Age IS NOT NULL";
return _users.Query(sql).FirstOrDefault();
}
// JOIN operations
public IEnumerable<dynamic> GetPostsWithAuthors()
{
var sql = @"
SELECT
p.Id,
p.Title,
p.Content,
p.CreatedAt,
u.Name as AuthorName,
u.Email as AuthorEmail
FROM Posts p
INNER JOIN Users u ON p.AuthorId = u.Id
ORDER BY p.CreatedAt DESC";
return _posts.Query(sql);
}
// Grouping
public IEnumerable<dynamic> GetPostCountByUser()
{
var sql = @"
SELECT
u.Id,
u.Name,
COUNT(p.Id) as PostCount
FROM Users u
LEFT JOIN Posts p ON u.Id = p.AuthorId
GROUP BY u.Id, u.Name
ORDER BY PostCount DESC";
return _users.Query(sql);
}
// Dynamic condition building
public IEnumerable<dynamic> SearchUsers(
string name = null,
int? minAge = null,
int? maxAge = null)
{
var conditions = new List<string>();
var parameters = new List<object>();
if (!string.IsNullOrEmpty(name))
{
conditions.Add("Name LIKE @" + parameters.Count);
parameters.Add($"%{name}%");
}
if (minAge.HasValue)
{
conditions.Add("Age >= @" + parameters.Count);
parameters.Add(minAge.Value);
}
if (maxAge.HasValue)
{
conditions.Add("Age <= @" + parameters.Count);
parameters.Add(maxAge.Value);
}
var where = conditions.Any()
? "WHERE " + string.Join(" AND ", conditions)
: "";
var sql = $"SELECT * FROM Users {where} ORDER BY Name";
return _users.Query(sql, parameters.ToArray());
}
// Batch operations
public void BatchInsertUsers(List<dynamic> users)
{
// Batch insert within transaction
using (var conn = _users.OpenConnection())
using (var trans = conn.BeginTransaction())
{
try
{
foreach (var user in users)
{
_users.Insert(user, conn, trans);
}
trans.Commit();
}
catch
{
trans.Rollback();
throw;
}
}
}
// Custom SQL execution
public int ExecuteCustomCommand(string sql, params object[] args)
{
return _users.Execute(sql, args);
}
}
Practical Usage Example
// RESTful API usage example
using Microsoft.AspNetCore.Mvc;
using System.Dynamic;
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly Users _users = new Users();
[HttpGet]
public IActionResult GetUsers(
[FromQuery] string search,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10)
{
try
{
var offset = (page - 1) * pageSize;
var where = string.IsNullOrEmpty(search)
? ""
: "WHERE Name LIKE @2 OR Email LIKE @2";
var sql = $@"
SELECT * FROM Users {where}
ORDER BY Name
OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY";
var users = _users.Query(sql, offset, pageSize, $"%{search}%");
// Get total count
var countSql = $"SELECT COUNT(*) as Count FROM Users {where}";
var totalCount = string.IsNullOrEmpty(search)
? _users.Scalar(countSql)
: _users.Scalar(countSql, $"%{search}%");
Response.Headers.Add("X-Total-Count", totalCount.ToString());
return Ok(users);
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
}
}
[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
try
{
var user = _users.Single(id);
if (user == null)
return NotFound();
return Ok(user);
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
}
}
[HttpPost]
public IActionResult CreateUser([FromBody] CreateUserDto dto)
{
try
{
dynamic user = new ExpandoObject();
user.Name = dto.Name;
user.Email = dto.Email;
user.Age = dto.Age;
user.CreatedAt = DateTime.UtcNow;
var id = _users.Insert(user);
user.Id = id;
return CreatedAtAction(nameof(GetUser), new { id }, user);
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
}
}
[HttpPut("{id}")]
public IActionResult UpdateUser(int id, [FromBody] UpdateUserDto dto)
{
try
{
var existing = _users.Single(id);
if (existing == null)
return NotFound();
_users.Update(new
{
Name = dto.Name,
Email = dto.Email,
Age = dto.Age,
UpdatedAt = DateTime.UtcNow
}, id);
return NoContent();
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
}
}
[HttpDelete("{id}")]
public IActionResult DeleteUser(int id)
{
try
{
var result = _users.Delete(id);
if (result == 0)
return NotFound();
return NoContent();
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
}
}
}
// DTO classes
public class CreateUserDto
{
public string Name { get; set; }
public string Email { get; set; }
public int? Age { get; set; }
}
public class UpdateUserDto
{
public string Name { get; set; }
public string Email { get; set; }
public int? Age { get; set; }
}
// Helper class
public static class MassiveExtensions
{
// Convert dynamic object to typed object
public static T ToTyped<T>(this dynamic obj) where T : new()
{
var typed = new T();
var dict = obj as IDictionary<string, object>;
if (dict != null)
{
var properties = typeof(T).GetProperties();
foreach (var prop in properties)
{
if (dict.ContainsKey(prop.Name) && dict[prop.Name] != null)
{
prop.SetValue(typed, dict[prop.Name]);
}
}
}
return typed;
}
// Convert dynamic object list to typed list
public static List<T> ToTypedList<T>(this IEnumerable<dynamic> list)
where T : new()
{
return list.Select(item => ToTyped<T>(item)).ToList();
}
}
// Service class usage example
public class BlogService
{
private readonly Users _users = new Users();
private readonly Posts _posts = new Posts();
public async Task<dynamic> CreateBlogPost(
string title,
string content,
int authorId)
{
// Verify user exists
var author = _users.Single(authorId);
if (author == null)
throw new InvalidOperationException("Author not found");
// Create post
var post = new ExpandoObject() as dynamic;
post.Title = title;
post.Content = content;
post.AuthorId = authorId;
post.CreatedAt = DateTime.UtcNow;
post.ViewCount = 0;
var postId = _posts.Insert(post);
// Return created post
return _posts.Query(@"
SELECT
p.*,
u.Name as AuthorName
FROM Posts p
INNER JOIN Users u ON p.AuthorId = u.Id
WHERE p.Id = @0", postId).FirstOrDefault();
}
public IEnumerable<dynamic> GetPopularPosts(int limit = 10)
{
return _posts.Query(@"
SELECT TOP(@0)
p.Id,
p.Title,
p.ViewCount,
p.CreatedAt,
u.Name as AuthorName,
(SELECT COUNT(*) FROM Comments WHERE PostId = p.Id) as CommentCount
FROM Posts p
INNER JOIN Users u ON p.AuthorId = u.Id
ORDER BY p.ViewCount DESC, p.CreatedAt DESC", limit);
}
public void IncrementViewCount(int postId)
{
_posts.Execute(
"UPDATE Posts SET ViewCount = ViewCount + 1 WHERE Id = @0",
postId);
}
}