3

We have a class library dealing with all DB-related operations. (SQLite and MSSQL) I tried to get rid of all the providers and factories (with this approach) in the executable project, since the class library already has the DB-Configuration in the App.config.

 <entityFramework>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
      <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
      <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
    </providers>
  </entityFramework>

  <system.data>
    <DbProviderFactories>
      <remove invariant="System.Data.SQLite" />
      <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
      <remove invariant="System.Data.SQLite.EF6" />
      <add name="SQLite Data Provider (Entity Framework 6)" invariant="System.Data.SQLite.EF6" description=".NET Framework Data Provider for SQLite (Entity Framework 6)" type="System.Data.SQLite.EF6.SQLiteProviderFactory, System.Data.SQLite.EF6" />
    </DbProviderFactories>
  </system.data>

However, while MSSQL works fine so far, SQLite throws an exception:

enter image description here

It does work if I add the provider to the App.config in the executable. But this is what I try to avoid. Is there any solution to this problem?

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
Bin4ry
  • 652
  • 9
  • 34

1 Answers1

2

In order to support EF6 database provider w/o app.config, you'd normally need DbConfiguration derived class which registers the appropriate DbProviderFactory and DbProviderServices using respectively SetProviderFactory and SetProviderServices methods.

However that's not enough for SQLite provider because it doesn't implement IProviderInvariantName service which causes the runtime exception in question. So you need to add implementation of that service through custom IDbDependencyResolver registered via AddDependencyResolver method.

Assuming your class library project have references to System.Data.SQLite and System.Data.SQLite.EF6 assemblies, and connection string uses "System.Data.SQLite.EF6" as providerName, add the following implementation classes to the class library project:

using System.Data.Entity.Infrastructure;
using System.Data.Entity.Infrastructure.DependencyResolution;
using System.Data.SQLite.EF6;
using System.Data.SQLite;

class SQLiteProviderInvariantName : IProviderInvariantName
{
    public static readonly SQLiteProviderInvariantName Instance = new SQLiteProviderInvariantName();
    private SQLiteProviderInvariantName() { }
    public const string ProviderName = "System.Data.SQLite.EF6";
    public string Name { get { return ProviderName; } }
}

class SQLiteDbDependencyResolver : IDbDependencyResolver
{
    public object GetService(Type type, object key)
    {
        if (type == typeof(IProviderInvariantName))
        {
            if (key is SQLiteProviderFactory || key is SQLiteFactory)
                return SQLiteProviderInvariantName.Instance;
        }
        return null;
    }

    public IEnumerable<object> GetServices(Type type, object key)
    {
        var service = GetService(type, key);
        if (service != null) yield return service;
    }
}

Then add the following configuration class (or update the existing if you already have one) again to the class library project containing your context class:

using System.Data.Entity;
using System.Data.Entity.Core.Common;
using System.Data.SQLite.EF6;

class MyDbConfiguration : DbConfiguration
{
    public MyDbConfiguration()
    {
        SetProviderFactory(SQLiteProviderInvariantName.ProviderName, SQLiteProviderFactory.Instance);
        SetProviderServices(SQLiteProviderInvariantName.ProviderName, (DbProviderServices)SQLiteProviderFactory.Instance.GetService(typeof(DbProviderServices)));
        AddDependencyResolver(new SQLiteDbDependencyResolver());
    }
}

And that's all. You can remove app.config from class library project and will be able to use connection strings like this from the executable app.config:

<add name="MyDb_SQLite" providerName="System.Data.SQLite.EF6" connectionString="Data Source =|DataDirectory|MyDb.sqlite" />

For more info, see Code-based configuration and related topics of the EF6 documentation. Please note that class libraries are not applications, so application config file really means executable config file. There is no way to force EF6 using your class library config file, so the code based configuration and executable config file are the only options.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • But this is exactly what I dont want to do. I want to get rid of the config file in the executable. – Bin4ry Sep 02 '18 at 22:30
  • You don't need any config file - the above code configuration will do that for you. But you have named connection strings (in order to specify the provider name) somewhere, right? Where are they located currently? Can't be in the library project I guess. – Ivan Stoev Sep 02 '18 at 22:32
  • That is true. They are in the executable. However, the executable calls the DAL from the library with the connection string. Thus, no provider info should be required in the executable. Furthermore, the connection string is stored in an untypical way. (Own XML structure) – Bin4ry Sep 02 '18 at 22:38
  • So we are good then. No provider info, no factory info. No ``, no `` in executable app.config, only ``. I already have similar working solution here [EF6, SQLite won't work without App.config](https://stackoverflow.com/questions/43615926/ef6-sqlite-wont-work-without-app-confg/43688403#43688403), just it was for SQLite only, while the above will work for both SQLite and SqlServer. – Ivan Stoev Sep 02 '18 at 22:42
  • The "magic" is provided by the `MyDbConfiguration` class. EF6 searches the assembly containing the db context (class library project in your case) for class(es) deriving from `DbConfiguration` and automatically instantiates them, thus the constructor call registers the necessary information at runtime, thus effectively eliminating the need of config file for that. I was thinking this is clear from the explanation of the solution (otherwise it wouldn't make sense). Just add the above 3 classes to your class library and you'll see. – Ivan Stoev Sep 02 '18 at 22:53
  • Had no idea that it will automatically search and instantiate those classes. However, lets assume for a moment I would want to force the app.config approach. Besides your already provided solution (adding classes), is there anything else that could be done? – Bin4ry Sep 02 '18 at 23:06
  • If you mean forcing the usage of the app.config of the class library, AFAIK this is not possible because class libraries app.config are not in effect. That's why IMHO you should either use app.config-less configuration (as suggested here) or be stuck with executable app.config. – Ivan Stoev Sep 02 '18 at 23:24
  • understood. Thanks – Bin4ry Sep 03 '18 at 00:34
  • This to me indicates that the custom dependency resolver is not registered. If you put breakpoint at the beginning of the `GetService` method of the `SQLiteDbDependencyResolver` class, is it called at all? – Ivan Stoev Sep 04 '18 at 11:09
  • No, it doesn't hit. I dug through the MSDN documentation and came across another problem. The DAL library acts as a "man in the middle" and basically only provides wrappers for SQLite and MSSQL (So the application doesn't know the DB behind it). The LinqToEntity/DML Statements are implemented in other libraries. Thus, I have to register the resolver in each individual library. In our case (10+ libs)... I think we have to deal with the app.config in the .exe – Bin4ry Sep 04 '18 at 11:16
  • Actually the 3 classes must be in the project containing your db context (as mentioned in the Code-based configuration link - *"Place your DbConfiguration class in the same assembly as your DbContext class"*). But using executable app.config could be a wise (proved to work) decision :) – Ivan Stoev Sep 04 '18 at 11:46
  • Yes, indeed. But all 10+ libs have their own code first DbContext classes. Thus, every lib has to add the dependency. – Bin4ry Sep 04 '18 at 13:13