WHEN: ORM code review, Prisma/TypeORM/SQLAlchemy/GORM patterns, lazy loading, transactions WHAT: Query efficiency + Lazy/eager loading + Transaction handling + N+1 prevention + Model design WHEN NOT: Raw SQL → sql-optimizer, Schema design → schema-reviewer
Install
npx skillscat add physics91/claude-vibe/orm-reviewer Install via the SkillsCat registry.
This skill reviews ORM code for query efficiency, lazy/eager loading patterns, transaction handling, and N+1 query prevention. It analyzes Prisma, TypeORM, SQLAlchemy, and GORM implementations to identify performance issues and model design problems. Use it when evaluating ORM-based code for optimization opportunities, but not for raw SQL tuning or schema design tasks.
ORM Reviewer Skill
Purpose
Reviews ORM code for query efficiency, loading strategies, transactions, and best practices.
When to Use
- ORM code review
- Prisma/TypeORM/SQLAlchemy patterns
- N+1 query detection
- Transaction handling review
- Model relationship design
Project Detection
schema.prisma(Prisma)@Entitydecorators (TypeORM)models.py(Django/SQLAlchemy)- GORM struct tags
Workflow
Step 1: Analyze ORM
**ORM**: Prisma / TypeORM / SQLAlchemy
**Database**: PostgreSQL
**Models**: 15
**Relationships**: HasMany, BelongsTo, ManyToManyStep 2: Select Review Areas
AskUserQuestion:
"Which areas to review?"
Options:
- Full ORM review (recommended)
- Query efficiency
- Loading strategies (N+1)
- Transaction handling
- Model design
multiSelect: trueDetection Rules
Prisma Patterns
N+1 Prevention
// BAD: N+1 queries
const users = await prisma.user.findMany();
for (const user of users) {
const orders = await prisma.order.findMany({
where: { userId: user.id }
});
// N+1 queries!
}
// GOOD: Include related data
const users = await prisma.user.findMany({
include: {
orders: true
}
});
// GOOD: Selective include
const users = await prisma.user.findMany({
include: {
orders: {
where: { status: 'pending' },
take: 5,
orderBy: { createdAt: 'desc' }
}
}
});Select Optimization
// BAD: Fetching all fields
const users = await prisma.user.findMany();
// GOOD: Select only needed fields
const users = await prisma.user.findMany({
select: {
id: true,
name: true,
email: true
}
});
// GOOD: Combining select with relations
const orders = await prisma.order.findMany({
select: {
id: true,
total: true,
user: {
select: {
name: true,
email: true
}
}
}
});Transactions
// BAD: No transaction for related operations
await prisma.order.create({ data: orderData });
await prisma.orderItem.createMany({ data: items });
await prisma.inventory.updateMany({ ... });
// If inventory update fails, order is orphaned!
// GOOD: Interactive transaction
const result = await prisma.$transaction(async (tx) => {
const order = await tx.order.create({ data: orderData });
await tx.orderItem.createMany({
data: items.map(item => ({ ...item, orderId: order.id }))
});
await tx.inventory.updateMany({
where: { productId: { in: items.map(i => i.productId) } },
data: { quantity: { decrement: 1 } }
});
return order;
});TypeORM Patterns
Eager vs Lazy Loading
// BAD: Lazy loading causing N+1
@Entity()
class User {
@OneToMany(() => Order, order => order.user)
orders: Order[]; // Lazy by default
}
// Triggers N+1:
const users = await userRepo.find();
for (const user of users) {
console.log(user.orders); // Each access = query!
}
// GOOD: Eager loading with relations
const users = await userRepo.find({
relations: ['orders']
});
// GOOD: QueryBuilder for complex queries
const users = await userRepo
.createQueryBuilder('user')
.leftJoinAndSelect('user.orders', 'order')
.where('order.status = :status', { status: 'pending' })
.getMany();Query Optimization
// BAD: Multiple queries
const user = await userRepo.findOne({ where: { id } });
const orders = await orderRepo.find({ where: { userId: id } });
const reviews = await reviewRepo.find({ where: { userId: id } });
// GOOD: Single query with joins
const user = await userRepo.findOne({
where: { id },
relations: ['orders', 'reviews']
});
// GOOD: Select specific columns
const users = await userRepo.find({
select: ['id', 'name', 'email'],
where: { status: 'active' }
});SQLAlchemy Patterns
Loading Strategies
# BAD: Lazy loading N+1
users = session.query(User).all()
for user in users:
print(user.orders) # N queries!
# GOOD: Eager loading
from sqlalchemy.orm import joinedload, selectinload
# For one-to-many: selectinload (2 queries)
users = session.query(User).options(
selectinload(User.orders)
).all()
# For many-to-one: joinedload (1 query)
orders = session.query(Order).options(
joinedload(Order.user)
).all()
# Combined
users = session.query(User).options(
selectinload(User.orders).selectinload(Order.items),
joinedload(User.profile)
).all()Session Management
# BAD: Long-lived session
session = Session()
# ... many operations over time
session.close() # Objects may be stale
# GOOD: Context manager
with Session() as session:
user = session.query(User).first()
# Session auto-closes
# GOOD: Scoped session in web apps
from sqlalchemy.orm import scoped_session
Session = scoped_session(sessionmaker(bind=engine))
# In request handler
def handle_request():
try:
# Use Session()
Session.commit()
except:
Session.rollback()
raise
finally:
Session.remove() # Clean up for this threadDjango ORM Patterns
N+1 Prevention
# BAD: N+1 queries
orders = Order.objects.all()
for order in orders:
print(order.user.name) # N queries!
# GOOD: select_related for ForeignKey
orders = Order.objects.select_related('user').all()
# GOOD: prefetch_related for reverse FK / M2M
users = User.objects.prefetch_related('orders').all()
# GOOD: Combined with Prefetch for filtering
from django.db.models import Prefetch
users = User.objects.prefetch_related(
Prefetch(
'orders',
queryset=Order.objects.filter(status='pending')
)
).all()Query Optimization
# BAD: .count() after .all()
count = len(Order.objects.all()) # Loads all objects!
# GOOD: Database count
count = Order.objects.count()
# BAD: Multiple queries
user = User.objects.get(id=user_id)
order_count = Order.objects.filter(user=user).count()
# GOOD: Annotation
from django.db.models import Count
user = User.objects.annotate(
order_count=Count('orders')
).get(id=user_id)
# values() for specific columns only
emails = User.objects.values_list('email', flat=True)GORM (Go) Patterns
// BAD: N+1 queries
var users []User
db.Find(&users)
for _, user := range users {
var orders []Order
db.Where("user_id = ?", user.ID).Find(&orders)
}
// GOOD: Preload
var users []User
db.Preload("Orders").Find(&users)
// GOOD: Preload with conditions
db.Preload("Orders", "status = ?", "pending").Find(&users)
// GOOD: Nested preload
db.Preload("Orders.Items").Find(&users)
// Select specific columns
var users []User
db.Select("id", "name", "email").Find(&users)Response Template
## ORM Code Review Results
**ORM**: Prisma
**Database**: PostgreSQL
**Models**: 12
### N+1 Queries
| Status | File | Issue |
|--------|------|-------|
| CRITICAL | userService.ts:45 | Loop with findMany causing N+1 |
### Loading Strategy
| Status | File | Issue |
|--------|------|-------|
| HIGH | orderController.ts:23 | Missing include for user relation |
### Transactions
| Status | File | Issue |
|--------|------|-------|
| HIGH | checkoutService.ts:67 | Related operations without transaction |
### Query Efficiency
| Status | File | Issue |
|--------|------|-------|
| MEDIUM | reportService.ts:12 | Selecting all columns |
### Recommended Fixes
```typescript
// Fix N+1 in userService.ts
const users = await prisma.user.findMany({
include: { orders: true }
});
// Add transaction in checkoutService.ts
await prisma.$transaction(async (tx) => {
// ... atomic operations
});
## Best Practices
1. **Loading**: Eager load known relations
2. **N+1**: Always include/preload in lists
3. **Select**: Only needed columns
4. **Transactions**: Wrap related operations
5. **Batching**: Use createMany, bulk operations
## Integration
- `sql-optimizer`: Raw query optimization
- `schema-reviewer`: Model design
- `perf-analyzer`: Application performance