In my organization there are three different groups using three different databases. The schema of the databases are the same -- same table structure, relational logic, etc -- and the three groups need me to report on their data in my application in the same way.
I understand that in my application I need to create a separate data context for each database, and in my repositories I need to explicitly declare the data context(s) used by that repository. I'm fine with all of that.
But if all three repos need to run the same given method, just against different data contexts, do I really need to write the same method three times? Is there no way that I can put my method in a class library, say, or some other fourth file, and then run the method against the context I need to use as and when?
For example, my repo looks like this:
public interface IMyRepo
{
List<string> GetMyData();
}
public class MyRepo : IMyRepo
{
private readonly ContextA _context;
public MyRepo(ContextA context) {
_context = context;
}
public List<string> GetMyData()
{
//code to get my data and return it
}
}
Now remember that because of the way my org's database is setup, I need to write this repo three times, once for each group. All that changes is the context.
I'd like to take the actual code for method GetMyData() and move it to a fourth file, then just link my repo to that file and call the method through an object reference, passing the context as a parameter, say. So I could call GetMyData() from my repo above like this:
//method call in my context above
public List<string> GetMyData()
{
//call actual GetMyData() method in my linked file
return linkedFile.GetMyData(_context);
}
Is something even remotely like I describe possible?
EDIT: I was asked by @RStevoUK to provide a code example for the linked file I describe, so I'll try to explain here. So in my context I have the method GetMyData(), which runs certain code and returns a list object. In the .NET Framework days, I could put the actual code for GetMyData() into a separate class file, and then pass in the ConnectionString object that the method would use to connect to the correct database and retrieve its data.
CLASS:
public class ExternalClassFile
{
public List<string> GetMyData(string connectionString)
{
using (SqlConnection connectionString)
{
//code to get my data and return it
}
}
}
Then, to run this method against a given database, all I would need to do is call an instance of the class, and invoke the method with the correct connection string:
ExternalClassFile ecf = new ExternalClassFile();
public List<string> CallSharedMethod()
{
string connection = \\connecting string code;
return ecf.GetMyData(connection);
}
I don't think I can do that with MVC; what I understand of it leads me to believe I can't. Also, I don't think Generics are what I want, since GetMyData() would contain an actual linq query
var data = (from d in _context.DatabaseTable_Model
select d).ToList();
for example. That "_context" variable references my datacontext, and that's all that changes.
I've tried to setup a "_context" variable using a "var" declaration instead of a specific context name, but MVC doesn't like that. Is there something else I can try that would make the above linq example reusable among multiple contexts?
I hope this makes more sense.
CodePudding user response:
What I'm hearing is that the only difference between your three repositories would be the specific type of DbContext that gets injected into them. And the only reason you're stuck being tightly coupled to a given type is that you have code accessing a property on that type, like _context.DatabaseTable_Model
.
You can typically rewrite your data access code to rely on DbSet<>
instead of a specific DbContext
type. And you can get a generic DbSet<>
off of any DbContext
by calling its DbContext.Set<>()
method.
var baseQuery = var data = _context.Set<DatabaseTable_Model>();
var data = (from d in baseQuery
select d).ToList();
Then you can make your repo take any type of DbContext:
private readonly DbContext _context;
public MyRepo(DbContext context) {
_context = context;
}
However, it's worth challenging the assumption that you need three separate data context types in the first place. If the schemas of the databases are literally the same, then the only difference between these classes should be their name, right? And the only reason the name matters is because DbContext
's default constructor uses naming conventions to decide how to find the connection string to use.
DbContext has other constructors, though, so if you make your context class use one of those other constructors it should be perfectly possible to construct separate instances of the same class pointing at different databases by passing in the name of the connection string (or the actual connection string, or the connection itself) that you want it to use.