9

I'm working on programmatically establishing a connection to PostgresSQL using Entity Framework 6. I have this class:

public class ClearspanDatabaseContext : DbContext

with this constructor:

public ClearspanDatabaseContext()
    : base(buildConnectionString())
{
}

Here's the static method that makes the connection string programmatically:

private static string buildConnectionString()
{
    RegisterDbProvider("Npgsql", ".Net Framework Data Provider for Postgresql", "Npgsql Data Provider", "Npgsql.NpgsqlFactory, Npgsql");
    EntityConnectionStringBuilder entityConnectionStringBuilder = new EntityConnectionStringBuilder();
    entityConnectionStringBuilder.Provider = "Npgsql";
    entityConnectionStringBuilder.ProviderConnectionString = "host=192.168.168.140;Port=5432;username=ClearspanDevLogin;password=*******;database=ClearspanWebServerDev";
    return entityConnectionStringBuilder.ToString();
}

And here's the method that registers Npgsql as a database provider, taken from this source:

public static bool RegisterDbProvider(string invariant, string description, string name, string type)
{
    try
    {
        DataSet ds = ConfigurationManager.GetSection("system.data") as DataSet;
        foreach (DataRow row in ds.Tables[0].Rows)
        {
            if (row["InvariantName"].ToString() == invariant)
            {
                return true;
            }
        }
        ds.Tables[0].Rows.Add(name, description, invariant, type);
        return true;
    }
    catch
    {
    }
    return false;
}

This generates a string like this:

"provider=Npgsql;provider connection string=\"host=192.168.168.140;Port=5432;username=ClearspanDevLogin;password=********;database=ClearspanWebServerDev\""

But I get an ArgumentException:

Keyword not supported: 'provider'.

I think I am close to the programmatic connection, but am missing something small. What can I do to resolve this exception and properly setup this connection programmatically? No app.config answers, I'm working in a class library, which ignores app.config (see the comments of the accepted answer to this question). This program must remain this way because it is used as a plugin - it does not (nor should it) run on its own. Thanks in advance.

Community
  • 1
  • 1
Nick Gilbert
  • 4,159
  • 8
  • 43
  • 90
  • Have you tried `new DbContext(new System.Data.Entity.Core.EntityClient.EntityConnection(buildConnectionString()), true)`? That DbContext constructor may not support specifying a provider. – jjj Sep 11 '15 at 23:17

2 Answers2

11

Ok, here is working example for you which I verified is working. Using dummy code-first EF 6 model + custom DbConfiguration class:

public class Enrollment {
    public int EnrollmentID { get; set; }
    public int CourseID { get; set; }
    public int StudentID { get; set; }
}

[DbConfigurationType(typeof (NpgsqlConfiguration))]
public class SchoolContext : DbContext {
    public SchoolContext(string cs) : base(cs) {
    }

    public DbSet<Enrollment> Enrollments { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder) {

    }
}

class NpgsqlConfiguration : System.Data.Entity.DbConfiguration
{
    public NpgsqlConfiguration()
    {
        SetProviderServices("Npgsql", Npgsql.NpgsqlServices.Instance);
        SetProviderFactory("Npgsql", Npgsql.NpgsqlFactory.Instance);
        SetDefaultConnectionFactory(new Npgsql.NpgsqlConnectionFactory());
    }
}

Then, instead of your buildConnectionString(), just pass postgre connection string in constructor:

using (var ctx = new SchoolContext("host=192.168.168.40;port=5432;...")) {                
            Console.WriteLine(ctx.Enrollments.ToArray());
        } 

And that is all. Config file is completely empty during that, and it works.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • Is this appropriate since I'm using Code First? I'm not sure where the .csdl and the .ssdl files I would use are. I think they are artifacts of Database First. – Nick Gilbert Sep 14 '15 at 13:58
  • Not really. With code-first, metadata will be generated on first connetion (dynamically), so all those .csdl are still there (though transparent for you). But that is not directly related to your question. In your case - try to use provider connection string directly (not EF connection string), so raw postgre connection string. Your error is because EF tries to parse connection string you pass to it. It sees there is no "metadata" parameter, so it's not EF string. So it's raw postgre connection string. Then it sees "provider" option and does not know what it is. – Evk Sep 14 '15 at 14:06
  • Ok, so I'm not sure how you're suggesting I modify my code then, if the metadata is not necessary. – Nick Gilbert Sep 14 '15 at 14:25
  • @NickG Updated my answer with suggestion. – Evk Sep 14 '15 at 14:38
  • Thanks, but I'm getting an error of "An exception of type 'System.ArgumentException' occurred in mscorlib.dll but was not handled in user code Additional information: Argument 'xmlReader' is not valid. A minimum of one .ssdl artifact must be supplied." My metadata string ends up being "res://DatabaseConnection, Version=1.2.5731.17066, Culture=neutral, PublicKeyToken=null/" The DatabaseConnection library doesn't actually have EF in it, so I tried using `Assembly.GetAssembly(typeof(Distance))`, which does use EF, but the metadata string looked similar, not containing any .ssdl stuff. – Nick Gilbert Sep 14 '15 at 15:15
  • Updated asnwer with verified solution. – Evk Sep 14 '15 at 16:42
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/89610/discussion-between-evk-and-nick-g). – Evk Sep 14 '15 at 17:22
  • Hi all - i've done exactly as you've suggested and have passed my pgsql connection string to the Context constructor and it says "keyword not supported: 'host' ---> i'm trying to do exactly what you were doing. any ideas would be much appreciated. – BenKoshy Nov 16 '16 at 02:53
2
  1. Have you looked at Code-Based Configuration? Create a DbConfiguration class with a public parameterless constructor in the same assembly as your DbContext

    class MyConfiguration : System.Data.Entity.DbConfiguration
    {
        public MyConfiguration()
        {
            SetProviderServices("Npgsql", Npgsql.NpgsqlServices.Instance);
            SetProviderFactory("Npgsql", Npgsql.NpgsqlFactory.Instance);
        }
    }
    

    Now I think the DbContext should use that provider factory by default, and you can construct the DbContext with just the connection string. But if it's in a different assembly, then you have a bit more work to do, but that can be found in the link above.

  2. A potential problem with the above solution is that any configuration in the config file will take precedence, so maybe it would be safer to use the option described in here:

    var conn = DbProviderFactories.GetFactory("MY_CONN_PROVIDER").CreateConnection();
    conn.ConnectionString = "MY_CONN_STR";
    
    new DbContext(conn, true);
    

    where your provider is "Npgsql", which was registered in RegisterDbProvider above.

    Also see https://msdn.microsoft.com/en-us/library/dd0w4a2z(v=vs.110).aspx

Community
  • 1
  • 1
jjj
  • 4,822
  • 1
  • 16
  • 39
  • Thanks, I'm trying to work with some of this stuff. In implementing the second option, I get an error on this line `var conn = DbProviderFactories.GetFactory("Npgsql").CreateConnection();` "Failed to find or load the registered .Net Framework Data Provider." Though it does seem like the `RegisterDbProvider` method is working. Any ideas as to what might be wrong? – Nick Gilbert Sep 14 '15 at 15:41
  • @NickG: From what you've mentioned before, this is in a library/plugin that's being used by another application. Perhaps you could check if the main application itself knows where to find the npgsql DLL? – jjj Sep 14 '15 at 17:34
  • 1
    Thanks for your help. I checked in the process and the plugin does indeed have access to the Npgsql.dll. I will upvote your question as it has helped me conceptually, though the accepted answer I have verified as working in answer to my question. – Nick Gilbert Sep 14 '15 at 17:48