|
| 1 | +# SMO vs T-SQL Usage Guide |
| 2 | + |
| 3 | +**GUIDING PRINCIPLE**: Default to using SMO (SQL Server Management Objects) first. Only use T-SQL when SMO doesn't provide the functionality or when T-SQL offers better performance or user experience. |
| 4 | + |
| 5 | +## Why SMO First |
| 6 | + |
| 7 | +- **Abstraction**: SMO provides object-oriented interface that handles version differences automatically |
| 8 | +- **Type Safety**: Strong typing reduces errors compared to dynamic T-SQL strings |
| 9 | +- **Built-in Methods**: Common operations (Create, Drop, Alter, Script) are provided out-of-the-box |
| 10 | +- **Consistency**: SMO ensures consistent behavior across SQL Server versions |
| 11 | +- **Less Code**: Often requires fewer lines than equivalent T-SQL |
| 12 | + |
| 13 | +## When to Use SMO |
| 14 | + |
| 15 | +### 1. Object Manipulation |
| 16 | +Creating, dropping, altering database objects: |
| 17 | + |
| 18 | +```powershell |
| 19 | +# PREFERRED - SMO for object manipulation |
| 20 | +$newdb = New-Object Microsoft.SqlServer.Management.Smo.Database($server, $dbName) |
| 21 | +$newdb.Collation = $Collation |
| 22 | +$newdb.RecoveryModel = $RecoveryModel |
| 23 | +$newdb.Create() |
| 24 | +
|
| 25 | +# Dropping objects |
| 26 | +$destServer.Roles[$roleName].Drop() |
| 27 | +$destServer.Roles.Refresh() |
| 28 | +``` |
| 29 | + |
| 30 | +### 2. Object Scripting |
| 31 | +Generating T-SQL from existing objects: |
| 32 | + |
| 33 | +```powershell |
| 34 | +# PREFERRED - SMO scripting with execution via Query |
| 35 | +$sql = $currentRole.Script() | Out-String |
| 36 | +Write-Message -Level Debug -Message $sql |
| 37 | +$destServer.Query($sql) |
| 38 | +
|
| 39 | +# Another example |
| 40 | +$destServer.Query($currentEndpoint.Script()) | Out-Null |
| 41 | +``` |
| 42 | + |
| 43 | +### 3. Object Enumeration |
| 44 | +Accessing collections and properties: |
| 45 | + |
| 46 | +```powershell |
| 47 | +# PREFERRED - SMO for object access |
| 48 | +$databases = $server.Databases |
| 49 | +$database = $server.Databases[$dbName] |
| 50 | +$isSystemDb = $database.IsSystemObject |
| 51 | +$members = $currentRole.EnumMemberNames() |
| 52 | +``` |
| 53 | + |
| 54 | +### 4. Object Properties |
| 55 | +Reading and setting object attributes: |
| 56 | + |
| 57 | +```powershell |
| 58 | +# PREFERRED - SMO for property access |
| 59 | +$recoveryModel = $db.RecoveryModel |
| 60 | +$owner = $db.Owner |
| 61 | +$lastBackup = $db.LastBackupDate |
| 62 | +$size = $db.Size |
| 63 | +``` |
| 64 | + |
| 65 | +## When T-SQL is Appropriate |
| 66 | + |
| 67 | +### 1. System Views and DMVs |
| 68 | +When SMO doesn't expose the data efficiently: |
| 69 | + |
| 70 | +```powershell |
| 71 | +# T-SQL for system catalog queries |
| 72 | +$sql = @" |
| 73 | +SELECT |
| 74 | + p.name AS ProcedureName, |
| 75 | + SCHEMA_NAME(p.schema_id) AS SchemaName, |
| 76 | + p.object_id, |
| 77 | + m.definition AS DllPath |
| 78 | +FROM sys.procedures p |
| 79 | +INNER JOIN sys.all_objects o ON p.object_id = o.object_id |
| 80 | +LEFT JOIN sys.sql_modules m ON p.object_id = m.object_id |
| 81 | +WHERE p.type = 'X' |
| 82 | + AND p.is_ms_shipped = 0 |
| 83 | +ORDER BY p.name |
| 84 | +"@ |
| 85 | +$sourceXPs = $sourceServer.Query($sql) |
| 86 | +``` |
| 87 | + |
| 88 | +### 2. Performance-Critical Queries |
| 89 | +When retrieving large result sets: |
| 90 | + |
| 91 | +```powershell |
| 92 | +# T-SQL for efficient data retrieval |
| 93 | +$querylastused = "SELECT dbname, max(last_read) last_read FROM sys.dm_db_index_usage_stats GROUP BY dbname" |
| 94 | +$dblastused = $server.Query($querylastused) |
| 95 | +``` |
| 96 | + |
| 97 | +### 3. Version-Specific Logic |
| 98 | +Different queries for different SQL Server versions: |
| 99 | + |
| 100 | +```powershell |
| 101 | +# T-SQL when version-specific system tables/views are needed |
| 102 | +if ($server.VersionMajor -eq 8) { |
| 103 | + # SQL Server 2000 uses system tables |
| 104 | + $backed_info = $server.Query("SELECT name, SUSER_SNAME(sid) AS [Owner] FROM master.dbo.sysdatabases") |
| 105 | +} else { |
| 106 | + # SQL Server 2005+ uses catalog views |
| 107 | + $backed_info = $server.Query("SELECT name, SUSER_SNAME(owner_sid) AS [Owner] FROM sys.databases") |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +### 4. System Stored Procedures |
| 112 | +When the operation requires a specific system proc: |
| 113 | + |
| 114 | +```powershell |
| 115 | +# T-SQL for system procedures that have no SMO equivalent |
| 116 | +$dropSql = "EXEC sp_dropextendedproc @functname = N'$xpFullName'" |
| 117 | +$null = $destServer.Query($dropSql) |
| 118 | +
|
| 119 | +$createSql = "EXEC sp_addextendedproc @functname = N'$xpFullName', @dllname = N'$destDllPath'" |
| 120 | +$null = $destServer.Query($createSql) |
| 121 | +``` |
| 122 | + |
| 123 | +### 5. User-Friendly Features |
| 124 | +When T-SQL makes the command more intuitive: |
| 125 | + |
| 126 | +```powershell |
| 127 | +# Sometimes T-SQL provides better UX than SMO |
| 128 | +# Example: Parameterized queries for filtering |
| 129 | +$splatQuery = @{ |
| 130 | + SqlInstance = $instance |
| 131 | + Query = "SELECT * FROM users WHERE Givenname = @name" |
| 132 | + SqlParameter = @{ Name = "Maria" } |
| 133 | +} |
| 134 | +$result = Invoke-DbaQuery @splatQuery |
| 135 | +``` |
| 136 | + |
| 137 | +## Hybrid Pattern (Most Common) |
| 138 | + |
| 139 | +Most dbatools commands use both SMO and T-SQL strategically: |
| 140 | + |
| 141 | +```powershell |
| 142 | +# Get SMO server object |
| 143 | +$sourceServer = Connect-DbaInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential |
| 144 | +
|
| 145 | +# Use SMO for object enumeration |
| 146 | +$sourceRoles = $sourceServer.Roles | Where-Object IsFixedRole -eq $false |
| 147 | +
|
| 148 | +# Use T-SQL for complex permission queries |
| 149 | +$splatPermissions = @{ |
| 150 | + SqlInstance = $sourceServer |
| 151 | + IncludeServerLevel = $true |
| 152 | +} |
| 153 | +$sourcePermissions = Get-DbaPermission @splatPermissions | Where-Object Grantee -eq $roleName |
| 154 | +
|
| 155 | +# Use SMO for object manipulation |
| 156 | +foreach ($currentRole in $sourceRoles) { |
| 157 | + # Script the object using SMO |
| 158 | + $sql = $currentRole.Script() | Out-String |
| 159 | +
|
| 160 | + # Execute via T-SQL |
| 161 | + $destServer.Query($sql) |
| 162 | +
|
| 163 | + # Use SMO methods for membership |
| 164 | + $members = $currentRole.EnumMemberNames() |
| 165 | + foreach ($member in $members) { |
| 166 | + $destServer.Roles[$roleName].AddMember($member) |
| 167 | + } |
| 168 | +} |
| 169 | +``` |
| 170 | + |
| 171 | +## Decision Tree |
| 172 | + |
| 173 | +1. **Does SMO expose the functionality cleanly?** |
| 174 | + - YES → Use SMO |
| 175 | + - NO → Continue to #2 |
| 176 | + |
| 177 | +2. **Is this a data retrieval operation from system views/DMVs?** |
| 178 | + - YES → Use T-SQL via `$server.Query()` |
| 179 | + - NO → Continue to #3 |
| 180 | + |
| 181 | +3. **Does the operation require a system stored procedure?** |
| 182 | + - YES → Use T-SQL via `$server.Query()` |
| 183 | + - NO → Continue to #4 |
| 184 | + |
| 185 | +4. **Would T-SQL significantly improve user experience?** |
| 186 | + - YES → Use T-SQL (document why in comments) |
| 187 | + - NO → Use SMO |
| 188 | + |
| 189 | +## Common Patterns |
| 190 | + |
| 191 | +```powershell |
| 192 | +# Pattern 1: SMO object with T-SQL execution of Script() |
| 193 | +$sql = $smoObject.Script() | Out-String |
| 194 | +$destServer.Query($sql) |
| 195 | +
|
| 196 | +# Pattern 2: T-SQL for discovery, SMO for manipulation |
| 197 | +$objects = $server.Query("SELECT name FROM sys.objects WHERE type = 'U'") |
| 198 | +foreach ($obj in $objects) { |
| 199 | + $table = $server.Databases[$dbName].Tables[$obj.name] |
| 200 | + $table.Drop() # SMO method |
| 201 | +} |
| 202 | +
|
| 203 | +# Pattern 3: SMO with T-SQL fallback |
| 204 | +try { |
| 205 | + $database = $server.Databases[$dbName] # SMO |
| 206 | +} catch { |
| 207 | + # Fallback to T-SQL if SMO fails |
| 208 | + $result = $server.Query("SELECT name FROM sys.databases WHERE name = '$dbName'") |
| 209 | +} |
| 210 | +``` |
| 211 | + |
| 212 | +## Anti-Patterns to Avoid |
| 213 | + |
| 214 | +```powershell |
| 215 | +# WRONG - Using T-SQL when SMO provides the functionality |
| 216 | +$result = $server.Query("ALTER DATABASE [$dbName] SET RECOVERY FULL") |
| 217 | +
|
| 218 | +# CORRECT - Use SMO |
| 219 | +$db = $server.Databases[$dbName] |
| 220 | +$db.RecoveryModel = "Full" |
| 221 | +$db.Alter() |
| 222 | +
|
| 223 | +# WRONG - Using T-SQL for object enumeration |
| 224 | +$databases = $server.Query("SELECT name FROM sys.databases") |
| 225 | +
|
| 226 | +# CORRECT - Use SMO |
| 227 | +$databases = $server.Databases |
| 228 | +
|
| 229 | +# WRONG - Concatenating T-SQL strings without parameters (SQL injection risk) |
| 230 | +$result = $server.Query("SELECT * FROM users WHERE name = '$userName'") |
| 231 | +
|
| 232 | +# CORRECT - Use parameterized queries |
| 233 | +$splatQuery = @{ |
| 234 | + Query = "SELECT * FROM users WHERE name = @userName" |
| 235 | + SqlParameter = @{ userName = $userName } |
| 236 | +} |
| 237 | +$result = Invoke-DbaQuery @splatQuery -SqlInstance $server |
| 238 | +``` |
| 239 | + |
| 240 | +## Summary |
| 241 | + |
| 242 | +- **Default to SMO** for object-oriented operations (Create, Drop, Alter, Script, property access) |
| 243 | +- **Use T-SQL** for system views, DMVs, complex queries, system stored procedures, and version-specific logic |
| 244 | +- **Combine both** in a hybrid approach when it provides the best balance of functionality and usability |
| 245 | +- **Always prefer parameterized queries** when using T-SQL with dynamic values |
| 246 | +- **Document your choice** when T-SQL is used instead of SMO for non-obvious reasons |
0 commit comments