Reputation: 4870
I'm trying to load System.Data.SqlClient
dynamically.
The System.Data.SqlClient
Nuget is installed but there is no reference to it in the project.
And I don't know the right path to the nuget directory.
Is there a way to do this dynamically?
Here is my code
internal static Type GetFastType(this string typeName, string assembly)
{
if (string.IsNullOrEmpty(assembly))
throw new Exception("AssemblyName cannot be empty");
if (!assembly.ToLower().EndsWith(".dll"))
assembly += ".dll";
var key = typeName + assembly;
if (CachedStringTypes.ContainsKey(key))
return CachedStringTypes.Get(key);
// Assembly.LoadFrom(assembly) // throw exception as the dll is not found
if (!CachedAssembly.ContainsKey(assembly))
CachedAssembly.Add(assembly, Assembly.LoadFrom(assembly));
return CachedStringTypes.GetOrAdd(key, CachedAssembly.Get(assembly).GetType(typeName, true, true));
}
And here is how I run it
var type ="System.Data.SqlClient.SqlConnection".GetFastType("System.Data.SqlClient");
Upvotes: 2
Views: 417
Reputation: 4870
For anyone that may have hade the same problem, i found the solution
Here is how you could load the right type in the right way
var type = Type.GetType($"{typeName}, {assembly}");
eg.
var type =Type.GetType("System.Data.SqlClient.SqlConnection, System.Data.SqlClient");
This way, it should load the dll dynamicly.
Upvotes: 0
Reputation: 155145
Read this MSDN article: Best Practices for Assembly Loading
It looks like you're assuming the System.Data.SqlClient.SqlConnection
class always exists inside System.Data.SqlClient.dll
.
This is an incorrect assumption:
mscorlib.dll
and System.Data.dll
).Your code cannot assume that a specific class is located in a specific assembly file - this breaks .NET's notion of backwards-compatibility through type-forwarding.
In your case, your code assumes System.Data.SqlClient.SqlConnection
exists inside an assembly file named System.Data.SqlClient
. This assumption is false in many cases, but true in some cases.
Here is the top-level directory structure of the System.Data.SqlClient
NuGet package:
Observe how inside the package there are subdirectories for each supported target (in this case, MonoAndroid10, MonoTouch10, net46, net451, net461, netcoreapp2.1, netstandard1.2, etc). For each of these targets the package provides different assemblies:
When targeting .NET Framework 4.5.1, .NET Framework 4.6 or .NET Framework 4.6.1 the files from the net451
, net46
and net461
directories (respectively) will be used. These folders contain a single file named System.Data.SqlClient.dll
which does not contain any classes. This is because when you target the .NET Framework 4.x, the System.Data.SqlClient
(namespace) types are already provided by the Base Class Library inside System.Data.dll
, so there is no need for any additional types. (So if you're building only for .NET Framework 4.x then you don't need the System.Data.SqlClient
NuGet package at all.
Here's a screenshot of the insides of that assembly file using the .NET Reflector tool (a tool which lets you see inside and decompile .NET assemblies) if you don't believe me:
When targeting other platforms via .NET Standard (i.e. where System.Data.dll
isn't included by default, or when System.Data.dll
does not include SqlClient
) then the NuGet package will use the netstandard1.2
, netstandard1.3
, netstandard2.0
directories, which does contain a System.Data.SqlClient.dll
that does contain the System.Data.SqlClient
namespace with the types that you're after. Here's a screenshot of that assembly:
And other platforms like MonoAndroid
, MonoTouch
, xamarinios
, xamarintvos
, etc also have their own specific version of the assembly file (or files!).
But even if you know your program will only run on a single specific platform where a specific NuGet package contains an assembly DLL that contains a specific type - it's still "wrong" because of type-forwarding: https://learn.microsoft.com/en-us/dotnet/framework/app-domains/type-forwarding-in-the-common-language-runtime
While Type-Forwarding means that most programs that reference types in certain assemblies will continue to work fine, it does not apply to reflection-based assembly-loading and type-loading, which is what your code does. Consider this scenario:
System.Data.SqlClient
NuGet package comes out that now has two assemblies:
System.Data.SqlClient.dll
(which is the same as before, except SqlConnection
is removed but has a [TypeForwardedTo]
attribute set that cites System.Data.SqlClient.SqlConnection.dll
).System.Data.SqlClient.SqlConnection.dll
(the SqlConnection
class now lives in this assembly).System.Data.SqlClient.dll
and not System.Data.SqlClient.SqlConnection.dll
and enumerates those types.Now, assuming you're prepared to disregard all of that advice and still write programs that assume a specific type exists in a specific assembly, then the process is straightforward:
// Persistent state:
Dictionary<String,Assembly> loadedAssemblies = new Dictionary<String,Assembly>();
Dictionary<(String assembly, String typeName),Type> typesByAssemblyAndName = new Dictionary<(String assembly, String typeName),Type>();
// The function:
static Type GetExpectedTypeFromAssemblyFile( String assemblyFileName, String typeName )
{
var t = ( assemblyFileName, typeName );
if( !typesByName.TryGetValue( t, out Type type ) )
{
if( !loadedAssemblies.TryGetValue( assemblyFileName, out Assembly assembly ) )
{
assembly = Assembly.LoadFrom( assemblyFileName );
loadedAssemblies[ assemblyFileName ] = assembly;
}
type = assembly.GetType( typeName ); // throws if the type doesn't exist
typesByName[ t ] = type;
}
return type;
}
// Usage:
static IDbConnection CreateSqlConnection()
{
const String typeName = "System.Data.SqlClient.SqlConnection";
const String assemblyFileName = "System.Data.SqlClient.dll";
Type sqlConnectionType = GetExpectedTypeFromAssemblyFile( assemblyFileName, typeName );
Object sqlConnectionInstance = Activator.CreateInstance( sqlConnectionType ); // Creates an instance of the specified type using that type's default constructor.
return (IDbConnection)sqlConnectionInstance;
}
Upvotes: 3
Reputation: 33252
I think you have to provide the full path to LoadFrom(...)
. You should be aware of the probing path of the application, so just concat that path to the name of the assembly. I don't think is straighforward to load from a path that is not in the probing path unless doing some thricks with the app domain.
Upvotes: -1