I am working on a asp.net core project which uses MongoDB and I am using dependency injection as well.
How current system works
There is only one database and the connection string is saved in the appsettings.json. Some devices will be constantly sending data through the API to save it in the MongoDB database which works as it should :)
Now I have to enhance it, there will be multiple databases and data should be saved in the relevant database based on the API request (i will be getting the database name in the API request).
My question is how that can I change the database based on the API request? while using the same DbContext. It is not an option to create multiple DbContext.
I am somewhat new to MongoDb and asp.net core so any help or guidance is much appreciated.
This is my DbContext class
public class DbContext
{
private IMongoDatabase _database;
protected readonly MongoClient mongoClient;
public DbContext(MongoSetting dbConnSettings)
{
var mongoUrl = new MongoUrl(dbConnSettings.ConnectionString);
var mongoClientSettings = MongoClientSettings.FromUrl(mongoUrl);
mongoClient = new MongoClient(mongoClientSettings);
if (mongoClient != null)
_database = mongoClient.GetDatabase(dbConnSettings.database);
}...
Section of my StartUp class
services.AddTransient<CareHomeContext>();
services.AddSingleton(provider => provider.GetService<IOptions<MongoSetting>>().Value);
If I understand your usecase correctly, it might be needed to use different databases in each request as well.
Hence I suggest to make database name as an optional parameter (so that you can use a default value from the configuration in case the database name is not provided in the request) to the DbContext class methods and create a method to get the database object (instead of getting it in the constructor) in the DbContext class as below.
private IMongoDatabase GetDatabase(string databaseName) => mongoClient.GetDatabase(databaseName ?? defaultDatabaseName);
Invoke the above method in each DbContext class method. e.g.
public async Task InsertAsync(string collectionName, Dictionary<string, object> fields, string databaseName = null) {
var database = GetDatabase(databaseName);
// Insert Code here
}
I hope this would help.
Related
I am building an API with ASP.NET core using Mongodb and i have different services user service home service and etc. I would like to know should i register every service as singleton as it is mentioned in asp.net core documention or as scoped. Link to repository https://github.com/krisk0su/apartments
UserService.cs
public class UserService
{
private readonly IMongoCollection<User> _books;
private readonly IPasswordHasher _passwordService;
public UserService(IBookstoreDatabaseSettings settings, IPasswordHasher passwordService)
{
var client = new MongoClient(settings.ConnectionString);
var database = client.GetDatabase(settings.DatabaseName);
_books = database.GetCollection<User>(settings.UsersCollectionName);
_passwordService = passwordService;
}
public List<User> Get() =>
_books
.Find(book => true)
.ToList();
public User Get(string id) =>
_books.Find(user => user.Id == id).FirstOrDefault();
public User Create(User user)
{
var password = this._passwordService.HashPassword(user.Password);
user.Password = password;
_books.InsertOne(user);
return user;
}
public void Update(string id, User bookIn) =>
_books.ReplaceOne(book => book.Id == id, bookIn);
public void Remove(User bookIn) =>
_books.DeleteOne(book => book.Id == bookIn.Id);
public void Remove(string id) =>
_books.DeleteOne(book => book.Id == id);
}
Startup.cs
services.AddSingleton<UserService>();
services.AddSingleton<BookService>();
services.AddSingleton<AuthenticationService>();
services.AddScoped<IPasswordHasher, PasswordHasher>();
The MongoDB .NET Driver reference documentation for version 2.17 explains on the Reference > Driver > Connecting page in the Mongo Client Re-use section that:
It is recommended to store a MongoClient instance in a global place, either as a static variable or in an IoC container with a singleton lifetime.
With regards to Mongo Database Re-use it doesn't mention a singleton lifetime but it does say it "is thread-safe and is safe to be stored globally", so I would interpret that to mean it can be stored safely as a singleton if that's what your implementation desired, but it doesn't need to be if you prefer another lifetime.
The implementation of IMongoDatabase provided by a MongoClient is thread-safe and is safe to be stored globally or in an IoC container.
It's the same with regards to Mongo Collection Re-use:
The implementation of IMongoCollection<TDocument> ultimately provided by a MongoClient is thread-safe and is safe to be stored globally or in an IoC container.
So again I'd interpret that to mean the choice of lifetime is up to your specific requirements.
It seems it's only the MongoClient that carries a recommendation to use a singleton lifetime.
Well its complicated.
First of all MongoClient can be singleton, so all services that uses MongoClient can be singletons as well. Its important cause singleton service cannot depends on service with shorter life (Scoped, Transient).
Now about UserService. All its dependancies is singletons and service itself don't stores any data (no fields, no props) that should live limited time or any data about particular user etc.
So it can be singleton!
But if you decided to add scoped dependancy or store any data in it:
public class UserService
{
private readonly IMongoCollection<User> users;
private readonly long userCount; //this one
public UserService(IBookstoreDatabaseSettings settings)
{
var client = new MongoClient(settings.ConnectionString);
var database = client.GetDatabase(settings.DatabaseName);
users = database.GetCollection<User>(settings.UsersCollectionName);
userCount = users.Find(_ => true).CountDocuments();
}
}
then you have to make it at least Scoped.
Btw it's much easier to have MongoClient as singleton in DI:
services.AddSingleton<IMongoClient>(s =>
new MongoClient(Configuration.GetConnectionString("MongoDb"))
);
and then use it in all services:
public class UserService
{
private readonly IMongoCollection<User> users;
public UserService(IMongoClient mongoClient)
{
var database = mongoClient.GetDatabase("DatabaseName");
users = database.GetCollection<User>(settings.UsersCollectionName);
}
}
Or if you will use just one database in your app you can move IMongoDatabase to DI as well and then you don’t need to get it every time in the service constructor.
Thank you for sharing, I am working on a MongoDB and .net core project. I have one DB with multiple collections. In start-up class
services.AddSingleton(s => { return new MongoClient(con.ConnectionString).GetDatabase(con.DatabaseName); });
My connection string and DB info are stored inside my appSetting.json.
Now in my repo, I inject
mongoDBClient.GetCollection<SomeClass>(GetCollectionNameFromAppSetting((settings.CollectionName)));
Since I am having one Database would that be ok to have that registered as a singleton? or should I change it
I'm following this tutorial in order to use Row Level security in SQL Server via Entity Framework 6 CodeFirst. The tutorial code sample shows how to use IDbConnectionInterceptor and set the current user id in session_context. To retrieve the user id, it uses static accessor method HttpContext.Current.User.Identity.GetUserId() which is coupled with Asp.Net identity and System.Web namespace.
In my multi-tenant web app, I wanted to have the tenantId injected into the DbConnectionInterceptor using Unity (without creating hard-coupling with HttpContext) and set the tenantId in the session_context. I found out that the DbConnectionInterceptor needs to be registered globally (eg. at application startup) and therefore you cannot have Unity create DbConnectionInterceptor instance per request.
I also have 2 DbContexts in my solution representing 2 different databases (Tenant database and a system database) and I only want to apply session_context to the Tenant database only.
It seems that the only option remaining to me is have the tenantId injected into the DbContext isntance via Unity and access the DbContext instance inside the Opened() method of the DbConnectionInterceptor. For this purpose I thought of using the interceptionContext parameter in the Opened() method. interceptionContext has a DbContexts(plural) property. There's no documentation on this so I assumed something like this would work:
public void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
{
var firstDbContext = interceptionContext.DbContexts.FirstOrDefault(d => d is TenantDataContext);
if (firstDbContext != null)
{
var dataContext = firstDbContext as TenantDataContext;
var tenantId = dataContext.TenantId;
DbCommand cmd = connection.CreateCommand();
cmd.CommandText = $"EXEC sp_set_session_context #key=N'TenantId', #value={tenantId};";
cmd.ExecuteNonQuery();
}
}
My code checks whether the DbContexts collection contains the TenantDataContext as the first element and executes the sp_set_session_context. But what I'm worried about is whether there's any chance for both DbContexts to be there at the same time? If that was the case, the connection to my other database would also set the session_context which I don't need. I'm wondering why Microsoft has provided this as a collection property rather than a single DbContext property. This property makes you wonder whether the same connection can be used by multiple DbContexts.
Is there anyone who has achieved what I want? Any explanation on this interceptionContext would also be helpful for me.
You can use the Connection_StateChaned event of your DbContext if you are using EF like so.
static void Main(string[] args)
{
using (var db = new AdventureWorks2016CTP3Entities())
{
db.Database.Connection.StateChange += Connection_StateChange;
db.Database.Log = (log) => System.Diagnostics.Debug.WriteLine(log);
var purchase = db.SalesOrderHeader.Select(i => i.SalesPersonID);
foreach (var m in purchase)
{
Console.WriteLine(m);
}
}
}
private static void Connection_StateChange(object sender, System.Data.StateChangeEventArgs e)
{
if(e.CurrentState == System.Data.ConnectionState.Open)
{
var cmd = (sender as System.Data.SqlClient.SqlConnection).CreateCommand();
cmd.CommandType = System.Data.CommandType.Text;
cmd.CommandText = "exec sp_set_session_context 'UserId', N'290'";
cmd.ExecuteNonQuery();
}
}
I realize this is an older question, but figured I would post our solution for those looking for one.
We are using interceptors to Inject a SQLServer session_context statement into the commands/connections running through EF.
In our case, we had to create Interceptors for DbCommand and DbConnection to handle both EF Linq queries and raw SQL queries that run through Commands. These Interceptor classes implement IDbCommandInterceptor and IDbConnectionInterceptor respectively.
For DbCommandInterceptor, we use the SqlCommand.CommandText to prepend our EXEC sp_set_session_context raw SQL to each command coming through the interceptor.
public class SessionContextDbCommandInterceptor : IDbCommandInterceptor
For DbConnectionInterceptor, we implement the Opened method and execute a SqlCommand against the connection that runs our sp_set_session_context SQL.
public class SessionContextDbConnectionInterceptor : IDbConnectionInterceptor
{
public void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
{...}
We then created a DbConfiguration class that adds the interceptors within the constructor:
public class SessionContextConfiguration : DbConfiguration
{
public SessionContextConfiguration()
{
AddInterceptor(new SessionContextDbConnectionInterceptor());
AddInterceptor(new SessionContextDbCommandInterceptor());
}
}
Then add this DbConfiguration class to our DbContext class via the DbConfigurationType Attribute as well as to our web.config:
[DbConfigurationType(typeof(SessionContextConfiguration))]
public class MyContext : DbContext
<entityFramework codeConfigurationType="MyAssembly.SessionContextConfiguration, MyAssembly">
We inject our DbContexts using Autofac as we normally would and the interceptors are automatically added to the DbContext instances because of the Configuration class.
I have a DB that is used by another web api. I need to make one DB select query to this database in my application against one table and two fields. I see I can create a database first DB context with some tools in .net core 1 by this link. However it seems heavy handed to create this entire DBContext for a database that could change as the web api changes. In addition I should not be writing to it. So I can change the access in SQL server for the user to select only. In addition I want to put my sql connection string in my appsettings.json file with all my other connection strings.
Question: Is it possible to set up an sqlconnection in my controller similar to:
private readonly SqlConnection dbCon = new SQLConnection(Configuration.GetConnectionString("DBCon"));
Where my DBCon is defined in my appsettings.json file. When I try to use the above I get the error:
The name Configuration does not exist in the current context.
So apparently Configuration isn't the answer to getting the setting from appsettings.json.
I need to set up a quick sql connection for a simple query that doesn't take any user input without a lot of configuration. Any help would be appreciated.
In 4.6 in console apps I could use ConfigurationManager to get the connectionstrings from app.config. Not sure how to get it out of appsettings.json. It may be getting it in Startup.cs but not sure how to get it in there and make it avaialable to the controller.
Update:
AppSettings.json is as follows:
{
"ConnectionStrings": {
"DefaultConnection": "valid Connection String",
"DBCon": "valid Connection String"
}, ....
You can (and should) use dependency injection to achieve this.
I would create a simple service that has a method to execute your query. The service class can have the connection string injected via an IOptions instance. Alternatively, you can inject the options directly into the controller.
OtherDbOptions
public class OtherDbOptions
{
public string ConnectionString { get; set; }
}
OtherDbService
public class OtherDbService
{
private readonly string _connectionString;
public OtherDbService(IOptions<OtherDbOptions> options)
{
_connectionString = options.Value.ConnectionString;
}
public object GetData()
{
// create your database connection and return data
}
}
Startup
public void ConfigureServices(IServiceCollection services)
{
// add options services
services.AddOptions();
// configure OtherDbOptions using code
services.Configure<OtherDbOptions>(options =>
{
options.ConnectionString = "value from appsettings.json";
});
// register OtherDbService for DI
services.AddTransient<OtherDbService>();
// other configurations
...
}
Controller (option 1 - inject service)
public OtherDbController(OtherDbService service)
{
_service = service;
}
Controller (option 2 - inject options)
public OtherDbController(IOptions<OtherDbOptions> options)
{
_options = options.Value;
}
I used SQL Server CE 4.0 in my windows app and use Entity Framework to create a model of it.
It works fine but my problems is that it doesn't have a constructor to change the connection string, and by default it reads the connection string from the app.config file.
using (var Context = new MyEntitiesModel(//has no constructor))
{
...
}
I create a dynamic connection string and
using (var Context = new MyEntitiesModel())
{
Context.Database.Connection.ConnectionString = entityConnection.ConnectionString;
}
It works fine by this way but if I remove another connection string in app.config file it gave me this.
error = invalid metasource ....
because the default constructor uses it
How can I handle it?
Create your own constructor. MyEntitiesModel is partial class you can add your own partial part of the class and add constructor accepting a connection string.
public partial class MyEntitiesModel {
public MyEntitiesModel(string connectionString) : base(connectionString) { }
}
Im using DbContext. There are several Overload Constructors eg:
ObjectContext also has a similar set of constructor overloads.
System.Data.Entity DbContext example
Context = new BosMasterEntities(nameOrConnectionString: nameOrConnectionString);
You can connect to multiple Dbs at same time.
I would like to know if it's a good practice to create a static class to get the Entity Database Context.
ThisGetEntity() return the Context. In the GetEntity method, I have a dynamic connection.
When someone go to my login page, they need to provide a database number + Username + Password. I stock the dbname in Session["DBName"].
public static class EntityFactory
{
public static DBEntities GetEntity()
{
var scsb = new SqlConnectionStringBuilder();
scsb.DataSource = ConfigurationManager.AppSettings["DataSource"];
scsb.InitialCatalog = "db1";
scsb.MultipleActiveResultSets = true;
scsb.IntegratedSecurity = true;
if (HttpContext.Current.Session["DBName"] == null)
{
HttpContext.Current.Response.Redirect("/Account/Step1");
}
else
{
scsb.InitialCatalog = HttpContext.Current.Session["DBName"].ToString();
}
var builder = new EntityConnectionStringBuilder();
builder.Metadata = "res://*/nms.bin.Models.DBModel.csdl|res://*/nms.bin.Models.DBModel.ssdl|res://*/nms.bin.Models.DBModel.msl";
builder.Provider = "System.Data.SqlClient";
builder.ProviderConnectionString = scsb.ConnectionString;
DBEntities db = new DBEntities(builder.ConnectionString);
return db;
}
When I want to get the DBContext by example in a controler, I Just need to do EntityFactory.GetEntity() and that returns me a DB context.
Is it Correct the way I do this
Is that could be a problem if 20 clients log at the same time but with a different dbname.
For the moment, I'm not using any dispose, Is it a problem? Based on my EntityFactory Class, can I make a global disposable in that class that will be call automaticly. (I think about the descrutor method).
The static factory method can be difficult to mock for unit testing. So fro example in your controller if you had:
public void SomeControllerMethod()
{
var entities = EntityFactory.GetEntity();
return entities.Something // ... get whatever data...
}
Then how would you use a mocked data context in a unit test? It would be difficult to do.
It would be better to "inject" your context into your controller, typically through the constructor (Read the Wikipedia article on the "dependency inversion principal" if you aren't familiar with the concept), like:
public class SomeController
{
private readonly IDBEntities entities;
// db context passed in through constructor,
// to decouple the controller from the backing implementation.
public void SomeController(IDBEntities entities)
{
this.entities = entities;
}
}
And then have the controllers methods use that passed in reference. This way you can use a dependency injection tool to get the appropriate db context, or pass in a mocked context.
I'm not sure if MVC2 had a good way to add a dependency injection framework though, but I know MVC3 does.
Your approach works too, there is nothing fundamentally wrong with it, it just seems harder to test. Of course if you aren't doing any unit testing and don't need to use a mock data store, then I guess it really doesn't matter :)
I typically end up using MVC3 with EntityFramework Code-First, which turns out pretty nice, and you can mock most of the data layer with List<T> instead of the actual database, you can "load" and "save" records to in-memory lists and never touch the real database.
in order :
You can improve it by passing to GetEntity() all the info it needs (like the dbname, username and password). As it is now the static method is tightly coupled with the session. Move the session out from the method.
It should not as the Session is per user.
If DBEntities inherits from DbContext you can call the Dispose after you've used the object. Es: dbEntitiesObj.Dispose();