Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to achieve read/write separation with Entity Framework

I have a database setup using 'master/slave replication'. I have one master and (at least) one slave, possibly ℕ slaves. For simplicity from here on I'll talk about one master, one slave because determining which slave to use includes some business-logic not relevant to the actual problem at hand.

Here's a schematic of the setup (with ℕ slaves):

Overview

In the application (currently using Dapper) I have the following, simplified, code:

abstract class BaseRepo
{
    private readonly string _readconn;
    private readonly string _writeconn;

    public BaseRepo(string readConnection, string writeConnection)
    {
        _readconn = readConnection;     //Actually IEnumerable<string> for ℕ slaves
        _writeconn = writeConnection;
    }

    private SqlConnection GetOpenConnection(string cnstring)
    {
        var c = new SqlConnection(cnstring);
        c.Open();
        return c;
    }

    public SqlConnection GetOpenReadConnection()
    {
        return this.GetOpenConnection(_readconn);
        // Actually we use some business-logic to determine *which* of the slaves to use
    }

    public SqlConnection GetOpenWriteConnection()
    {
        return this.GetOpenConnection(_writeconn);
    }
}

class CustomerRepo : BaseRepo
{
    // ...ctor left out for brevity...

    // "Read" functions use the "read" connection
    public IEnumerable<Customer> ListCustomers()
    {
        using (var c = this.GetOpenReadConnection())
        {
            return c.Query<Customer>("select * from customers order by name");
        }
    }

    // "Write" functions use the "write" connection
    public void UpdateCustomer(Customer cust)
    {
        using (var c = this.GetOpenWriteConnection())
        {
            c.Execute("update customers set name = @name where id = @id", cust);
        }
    }
}

My question is; suppose I want to use Entity Framework ("code first", should that be relevant) instead of Dapper; how would I best go about achieving the same concept; inserts/updates/deletes are executed against the "master" database and selects are executed against a slave (or any of the slaves). Does EF support this scenario at all? What would I need to do to make this work?


Additional info: I already use 'read-only' and 'write-only' users at the SQL Server level as a 'last line of defence' to prevent any mistakes in the DAL. What I'm looking for is a method of limiting my DAL to avoid having to catch SQL Server exceptions because of 'not allowed' actions and having to go to the (incorrect) SQL server in the first place before finding out the desired action is not allowed. I could use the same approach as I do now; instantiate/use the correct DbContext in the method itself (listcustomers/updatecustomer in the above example). I get that. But that would mean I'd have to create a 'wrapper' function for each "CRUD" action on each "entity" which was kind of why I was moving from dapper to EF in the first place; simply expose a DBSet and have EF take care of the changetracking/SQL queries etc. and now, hopefully, also figure out which connectionstring to use for each action.

like image 244
RobIII Avatar asked Mar 03 '16 13:03

RobIII


1 Answers

As proposed by others, create a read/write context by default and then create a readonly one inheriting from it. Also be sure to implement in a partial class a constructor accepting another configuration if you wish too.

public partial class CustomerEntities : DbContext
{
    protected CustomerEntities(string nameOrConnectionString):base(nameOrConnectionString)
    {         
    }
}

public class ReadonlyCustomerEntities : CustomerEntities
{
    public ReadonlyCustomerEntities ()
        : base("name=ReadonlyCustomerEntities")
    {          
    }

    public override int SaveChanges()
    {
        // Throw if they try to call this
        throw new InvalidOperationException("This context is read-only.");
    }
}
like image 51
LeBaptiste Avatar answered Oct 08 '22 07:10

LeBaptiste