I'm trying to create a common function in a C# Webapi project to connect to one of two databases based on the value of an input flag. Each database has the same structure, but different data and each invocation would use the same database throughout.
The rest of my code is database agnostic, so it needs to be able to use a common db object, rather than making the decision every time a call to the database is done.
This is the code I thought would do the trick:
public static dynamic GetDb(string scope = "dev") {
dynamic db;
if (Globals.db == null) {
switch (scope.ToLower()) {
case "tns":
db = new TnsDb();
break;
case "sng":
db = new SngDb();
break;
default:
db = new TnsDb();
break;
}
Globals.db = db;
} else {
db = Globals.db;
}
return db;
}
I'm using the Entity Framework, which has created an access class for each database I've connected to the project and contains a method for each stored procedure I need to access. What I would like to do is to get this common method to return an object representing the appropriate database class.
The problem I'm having is that the method isn't returning a usable object. I've tried using a return type of dynamic (as above), a plain object and DbContext (the parent class of the Entity Framework db class) and a few other things out of desperation, but each time the calling statement receives back an object that has none of the methods present in the Entity Framework database class.
Any ideas of how to dynamically select and return the appropriate object that does retain all the methods will be gratefully received, as I will be able to save at least some of my hair.
You mention that each database has the same structure yet your functions uses different db context. You cant use dynamic object in this context using dynamic object.
Since your db has the same structure you could just change the connection string to the db when you initialize the DbContext. If you really want to use separate db contexts then you should use an interfact which your fuctions returns
IMyDatabase
{
BbSet<Model1> Model1{get;set;}
BbSet<Model2> Model2{get;set;}
}
TnsDb:IMyDatabase
{
BbSet<Model1> Model1{get;set;}
BbSet<Model2> Model2{get;set;}
}
SngDb:IMyDatabase
{
BbSet<Model1> Model1{get;set;}
BbSet<Model2> Model2{get;set;}
}
and both of you context need to implement this then your fuctions could be like this
public static IMyDatabase GetDb(string scope = "dev") {
dynamic db;
if (Globals.db == null) {
switch (scope.ToLower()) {
case "tns":
return new TnsDb();
case "sng":
return new SngDb();
default:
return new TnsDb();
}
}
But you shouldn't be using two separate db context just one would be enough in your case with different connection string
Although using static functions for this purpose not really good, you could some repository pattern using dependency injection
Success! I ended up creating an interface that had the same methods as the db class and returned that from the common method.
Here's a snippet from the interface:
public interface IDatabase {
ObjectResult<Method_1_Result> Method_1(Nullable<int> id, string username);
ObjectResult<Method_2_Result> Method_2(string username, string password);
}
and here are the equivalent methods from the db classes:
public partial class TnsDb : DbContext, IDatabase {
public TnsDbDev()
: base("name=TnsDb")
{
}
public virtual ObjectResult<Method_1_Result> Method_1(Nullable<int> id, string username)
{
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<Method_1_Result>("Method_1", idParameter, usernameParameter);
}
public virtual ObjectResult<Method_2_Result> Method_2(string username, string password)
{
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<Method_2_Result>("Method_2", usernameParameter, passwordParameter);
}
}
public partial class SngDb : DbContext, IDatabase {
public SngDbDev()
: base("name=SngDb")
{
}
public virtual ObjectResult<Method_1_Result> Method_1(Nullable<int> id, string username)
{
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<Method_1_Result>("Method_1", idParameter, usernameParameter);
}
public virtual ObjectResult<Method_2_Result> Method_2(string username, string password)
{
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<Method_2_Result>("Method_2", usernameParameter, passwordParameter);
}
}
Note that I had to delete the get and set bodies from the interface method, as they weren't needed and kept generating errors.
I also suspect that this solution is specific to my situation of needing to connect to two databases with exactly the same schema and wouldn't work if the schemas were slightly different, as they couldn't share the same interface.
The technique also requires you to remember to re-add the reference to the interface every time you re-generate the db class (in this case. the TnsDb class) and keep it up to date whenever you change any methods in the db class.
Anyway, I hope that helps anyone with the same problem as I had. Thanks to all that helped me solve this.
Related
I have one ASP-WebForms-Application for 3 Companies. Every Company has its own Database and own EDMX-Model. The Structure in the Databases is the same. On Parameters I am checking which Company it is and want to have one DbContext for all the Models.
I am very new in Entity Framework and don't know how to make one Context for a few Models.
I have tried to make a class that gives me a DbContext, in which I want to make the DBContext one of the Models.
I am trying:
public static DbContext holeDbContextWsvWsbSvs()
{
DbContext context = new DbContext();
string verbandKürzel = config.holeVerbandKürzel();
if (verbandKürzel == "wsv")
{
context = new wsvEntities();
}
else if (verbandKürzel == "wsb")
{
context = new wsbEntities();
}
else if (verbandKürzel == "svs")
{
context = new svsEntities();
}
return context;
}
But in Entity Framework 6.0 it seems as an emtpy Constructor is not possible!
Is it possible to intialize a Null(Fake,Pseudo)-DbContext or something and then change it to the Model-Context?
Can anyone give me an impulse please, how can i realize my plans?
EDIT:
Okay, changing the Context to the Model inside the Method achieved by giving the Constructor any string. But although giving the Context a specific Model inside the method, I am returning a DbContext an can not use Object-Attributes after that.
Any Suggestions? Or do really have to make at every position i need to work with the DataModel IF-ELSEs and do the same logic for each Model?
If the structure of the DbContexts are identical and you just want to point at one of three databases, you don't need to declare 3 separate DbContexts, just one, then use the Constructor argument to nominate the appropriate connection string.
public class AppDbContext : DbContext
{
// Define DbSets etc.
public AppDbContext(string connection)
: base (connection)
{ }
}
Then in your initialization code:
public AppDbContext holeDbContextWsvWsbSvs()
{
string verbandKürzel = config.holeVerbandKürzel();
switch (verbandKürzel)
{
case "wsv":
return new AppDbContext("wsvEntities");
case "wsb":
return new AppDbContext("wsbEntities");
case "svs":
return new AppDbContext("svsEntities");
default:
throw new InvalidOperationException("The configured client is not supported.");
}
}
Assuming you have connections strings called "wsvEntities", "wsbEntities", and "svsEntities" respectively.
If the DbContexts are not identical in structure then honestly you likely will have much bigger problems that won't be solved by exposing them via the base DbContext.
Objective:
Use EF to enter in data from a POST request from an API. The POST request will contain an "ID", that will map the connection string to an enum, which will have the same name connection string name as it has in the Web.config. Create the "base" context object and add the object to the appropriate table.
Note:
I know I can do this using SqlCommand, but I wanted to take a crack at it using entity framework instead, but I hit a wall.
I've used EF for years, but I wanted to make this POST method as global as I can get it. This API will accept numerous requests from numerous different web sites, but all will use the same model. Each websites POST will go into a different database (that's how they are requesting it).
The problem that I foresee is, each "entity" knows what tables it contains. So when one types context.TABLE.Add(object), EF understands that you want to put this "Car" object in the "Car" table (obviously).
Can this be done using a "global" entity???
public class DbConnectionNames
{
public enum DbConnectionStringNames
{
SocHopeHcpEntities = 1, // "1" is passed into the POST to map
XXXXEntities = 2,
......
}
}
<add name="SocHopeHcpEntities" connectionString=".........." />
<add name="XXXXEntities" connectionString=".........." />
.....
var professional = new Professional
{
....
....
};
string connStringContext = Enum.GetName(typeof(DbConnectionNames.DbConnectionStringNames), model.FormId).ToString();
string connectionString = ConfigurationManager.ConnectionStrings[connStringContext].ConnectionString;
using (var context = new ObjectContext(connectionString))
{
context.Professionals.Add(professional); // obviously this doesn't work
context.SaveChanges();
}
EDIT:
My EF is NOT using POCO, but is already based off a DB to begin with. There could be XX number of different databases, all holding the same similar table. I already have a YYYEntities.Context.cs file auto-generated that inherits from DbContext:
public partial class SocHopeHcpEntities : DbContext
{
public SocHopeHcpEntities()
: base("name=SocHopeHcpEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<AreasOfWork> AreasOfWorks { get; set; }
public virtual DbSet<Professional> Professionals { get; set; }
}
You still need a context that understands what a Professional is. For example:
public class ProfessionalContext : DbContext
{
public ProfessionalContext(string connectionString)
: base(connectionString)
{
//This line is optional but it prevents initialising the database
//every time you connect to a new database
Database.SetInitializer<ProfessionalContext>(null);
}
public DbSet<Professional> Professionals { get; set; }
}
And use it like this:
using (var context = new ProfessionalContext(connectionString))
{
context.Professionals.Add(professional);
context.SaveChanges();
}
DavidG's answer showed you how to pass a connection string to a strongly typed context that knows the entity types you are dealing with. That context inherits from DbContext which you can use directly as illustrated below.
It is worth noting the generic way that does not involve a context 'object' that is specific to your database.
For example see how ObjectContext is used here:
System.Data.Objects.ObjectContext oc = new System.Data.Objects.ObjectContext("connection string");
oc.AddObject("ProfessionalsTable", professional);
Another example is using DbContext:
System.Data.Entity.DbContext dbc = new DbContext("");
dbc.Set(typeof(Professional)).Add(professional);
The generic approach is better if you also do not know which table you want to insert to, so you can also make the object that you want to insert dynamic.
I am trying to create an app the can handle multiple database types.
So far I have created my Interface like so. Its very simple and all the database will do is Load and Save a profile
public interface IDataManager
{
Profile LoadProfile(int profileId);
bool SaveProfile(Profile profile);
bool CreateDatabase();
bool OpenConnection();
bool CloseConnection();
}
and lets just say the Profile class for the above looks like this.
public class Profile
{
public int Id { get; set; }
public string Name { get; set; }
}
My question is what is the best way to make it so that all the implementations of IDataManager return the same object types?
Here is an example of what I mean by this. (This is not quality code its just an example)
I create an SQLite class that implements IDataManager and then create an instance.
public IDataManager DataManager = new SQLiteDataManager();
Later in the code I want to load a Profile so I call the LoadProfile.
Profile profile = DataManager.LoadProfile(1);
My SQLite implementation of the LoadProfile method looks like this
public Profile LoadProfile(int profileId)
{
// Copied and pasted from a WinRT app
using (var conn = new global::SQLite.Net.SQLiteConnection(new global::SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), _sqlpath))
{
var tmp = conn.Table<PROFILE>().First(x => x.ID == profileId);
}
// do something and return
}
Now as you can see the return type from the query (tmp = type PROFILE) is not the same type as the LoadProfile method return type (Profile).
Do I have to convert tmp to Profile? which means it must be done in all the methods with return types, and for every different database implementation. or is there a better way of doing this?
Hope this makes sense.
If you use Entity Framework you can just use the provider for the db type you will be using but keep all the api and models the same
A list of Entity Framework providers for various databases
In my previous applications when I used linq-to-sql I would always use one class to put my linq-to-sql code in, so I would only have one DataContext.
My current application though is getting too big and I started splitting my code up in different classes (One for Customer, one for Location, one for Supplier...) and they all have their own DataContext DatabaseDesignDataContext dc = new DatabaseDesignDataContext();
Now when I try to save a contact with a location (which I got from a different DataContext) I get the following error:
"An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported."
I assume this is because I create a DataContext for every class, but I wouldn't know how to this differently?
I'm looking for any ideas, thanks.
My classes look like the following:
public class LocatieManagement
{
private static DatabaseDesignDataContext dc = new DatabaseDesignDataContext();
public static void addLocatie(locatie nieuweLocatie)
{
dc.locaties.InsertOnSubmit(nieuweLocatie);
dc.SubmitChanges();
}
public static IEnumerable<locatie> getLocaties()
{
var query = (from l in dc.locaties
select l);
IEnumerable<locatie> locaties = query;
return locaties;
}
public static locatie getLocatie(int locatie_id)
{
var query = (from l in dc.locaties
where l.locatie_id == locatie_id
select l).Single();
locatie locatie = query;
return locatie;
}
}
That happens if the entity is still attached to the original datacontext. Turn off deferred loading (dc.DeferredLoadingEnabled = false):
partial class SomeDataContext
{
partial void OnCreated()
{
this.DeferredLoadingEnabled = false;
}
}
You may also need to serialize/deserialize it once (e.g. using datacontractserializer) to disconnect it from the original DC, here's a clone method that use the datacontractserializer:
internal static T CloneEntity<T>(T originalEntity) where T : someentitybaseclass
{
Type entityType = typeof(T);
DataContractSerializer ser =
new DataContractSerializer(entityType);
using (MemoryStream ms = new MemoryStream())
{
ser.WriteObject(ms, originalEntity);
ms.Position = 0;
return (T)ser.ReadObject(ms);
}
}
This happens because you're trying to manage data from differing contexts - you will need to properly detach and attach your objects to proceed - however, I would suggest preventing the need to do this.
So, first things first: remove the data context instances from your entity classes.
From here create 'operational' classes that expose the CRUDs and whatnot to work with that specific type of entity class, which each function using a dedicated data context for that unit of work, perhaps overloading to accept a current context for when a unit of work entails subsequent operations.
I know everybody probably gets tired of hearing this, but you really should look at using Repositories for Data Access (and using the Unit of Work pattern to ensure that all of the repositories that are sharing a unit of work are using the same DataContext).
You can read up on how to do things here: Revisiting the Repository and Unit of Work Patterns with Entity Framework (the same concepts apply to LINQ to SQL as well).
Another solution I found for this is to create one parent class DataContext
public class DataContext
{
public static DatabaseDesignDataContext dc = new DatabaseDesignDataContext();
}
And let all my other classes inherit this one.
public class LocatieManagement : DataContext
{
public static void addLocatie(locatie nieuweLocatie)
{
dc.locaties.InsertOnSubmit(nieuweLocatie);
dc.SubmitChanges();
}
}
Then all the classes use the same DataContext.
I have a class that uses linq to access the database. Some methods call others. For example:
class UserManager
{
public User[] getList()
{
using(var db = new MyContext())
{
return db.Users.Where(item => item.Active == false);
}
}
public User[] addUser(string name)
{
using(var db = new MyContext())
{
db.Users.InsertOnSubmit(new User() { id = Guid.NewId(), name = name, active = false ...});
}
return getList();
}
...
In the call to addUser I am required to return the new list. (I know as it stands it isn't a great design, but I have eliminated detail for simplicity.) However, the call to getList creates a second data context.
I could pad this out with extra methods, viz:
public getList()
{
using(var db = new MyContext())
return getList(db);
}
public getList(MyContext db)
{
...
}
Then replace my call in addUser so as to keep the same data context.
I seem to see this type of thing a lot in my code, and I am concerned with the cost of creating and releasing all these data contexts. Does anyone have an opinion as to whether it is worthwhile putting in the extra work to eliminate the creation and deletion of these contexts?
Microsoft provides the following advice/recommendation to not reuse DataContext instances http://msdn.microsoft.com/en-us/library/bb386929.aspx
Frequently Asked Questions (LINQ to SQL)
Connection Pooling
Q. Is there a construct that can help
with DataContext pooling?
A. Do not try to reuse instances of
DataContext. Each DataContext
maintains state (including an identity
cache) for one particular edit/query
session. To obtain new instances based
on the current state of the database,
use a new DataContext.
You can still use underlying ADO.NET
connection pooling. For more
information, see SQL Server Connection
Pooling (ADO.NET).
It is ok to reuse for different parts of the same logical operation (perhaps by passing the data-context in as an argument), hut you shouldn't reuse much beyond that:
it caches objects; this will grow too big very quickly
you shouldn't share it between threads
once you've hit an exception, it gets very unwise to reuse
Etc. So: atomic operations fine; a long-life app context; bad.
What I usually do is create a class the you could call something like DataManager with all data functions as members. This class creates an instance of MyContext on its constructor.
class DataManager
{
private MyContext db;
public DataManager() {
db = new MyContext();
}
public User[] getList()
{
return db.Users.Where(item => item.Active == false);
}
public User[] addUser(string name)
{
db.Users.InsertOnSubmit(new User() { id = Guid.NewId(), name = name, active = false ...});
return getList();
}
}
You create an instance of this class whenever you are doing a set of operations. On a Controller, for instance, you could have this class as a member. Just don't make a global var out of it, instantiate and dispose when you are done with it.