This module provides utilities for reading embedded resources (e.g., SQL scripts, configuration files) from a .NET assembly’s manifest resources. It exists to abstract away the boilerplate of locating and extracting embedded resources by name, particularly supporting scenarios where database initialization scripts are bundled as embedded resources within the assembly. It enables both stream-based and string-based access to such resources, with overloads that allow specifying a custom assembly or defaulting to the assembly containing the EmbeddedResource class itself.
2. Public Interface
static StreamReader GetStream(Assembly assembly, string name)
Searches the manifest resource names of the given assembly for a resource whose name ends with the provided name (case-sensitive substring match), and returns a StreamReader for the first matching resource. Returns null if no match is found. Note: The caller is responsible for disposing the returned StreamReader.
static string GetString(Assembly assembly, string name)
Calls GetStream(assembly, name), reads the entire contents of the stream into a string using ReadToEnd(), closes the stream, and returns the resulting string. Returns null if GetStream returns null. Note: This method disposes the StreamReader internally via sr.Close().
static string GetString(string name)
Overload of GetString that uses the assembly containing the EmbeddedResource class itself (typeof(EmbeddedResource).Assembly) as the target assembly. Behaves identically to GetString(assembly, name) for that assembly.
3. Invariants
Substring matching semantics: Resource lookup uses EndsWith(name), meaning the full manifest resource name must end with the provided name string. For example, GetString("init.sql") would match "MyApp.Migrations.init.sql" but not "init.sql.bak".
First-match behavior: If multiple resources end with the same name, only the first one (as returned by GetManifestResourceNames()) is used. The order is not guaranteed by the .NET specification and may vary across builds or environments.
No validation of resource existence beyond null: GetStream returns null if no matching resource is found; callers must handle this case explicitly.
Resource names are case-sensitive: EndsWith performs case-sensitive comparison by default in .NET.
4. Dependencies
Depends on:
System.IO (for StreamReader)
System.Reflection.Assembly (for GetManifestResourceNames() and GetManifestResourceStream())
Depended on by:
Not inferable from this file alone. However, given the namespace (DatabaseInitializationScripts), it is highly likely used by database migration or setup logic elsewhere in the codebase (e.g., to load .sql files during schema initialization).
The default GetString(string) overload implies a design assumption that the assembly containing EmbeddedResource is the one housing the embedded resources—likely the same assembly where this class resides.
5. Gotchas
Resource name ambiguity: Because matching is based on EndsWith, two resources named "script1.sql" and "subfolder/script1.sql" would both match a lookup for "script1.sql", and only the first (arbitrary) one would be returned. This can lead to unpredictable behavior if resource naming is not strictly controlled.
Resource stream not disposed by caller: GetStream returns an open StreamReader; callers must ensure it is disposed (e.g., via using), otherwise resource leaks may occur. The GetString(Assembly, string) overload avoids this by closing the stream internally, but the stream-returning overload does not.
Encoding assumption: StreamReader.ReadToEnd() uses the default encoding of the StreamReader, which is UTF-8 without BOM by default in modern .NET. If the embedded resource is encoded differently (e.g., UTF-8 with BOM, UTF-16), the string may be misinterpreted.
No support for nested paths: The API does not distinguish between resource names with directory-like separators (e.g., "Scripts/init.sql" vs "init.sql"), increasing the risk of collisions.
null return without exception: GetStream returns null on failure, which may lead to NullReferenceException if callers do not check for it before use.
sr.Close() is not Dispose(): While Close() and Dispose() are functionally equivalent for StreamReader, using Close() instead of using or Dispose() is outdated style and may obscure resource management intent.