I am new to both Ninject and the Vita ORM (vita docs) and am just having issues coming up with a binding strategy for dependency injection and would really appreciate any help.
Firstly, my application in split into 3 layers, namely the data layer ( using Vita ORM so in essence it is a single EntityApp class similar to dbContext for EF), then I have a service layer with well defined interfaces ( and existing implementations using my current repository pattern using dapper.net ) and finally a simple MVC web application with controllers calling off to the service layer.
Currently I am using ninject constructor intection to inject the repo into the service and the service into the controller and scoping them accordingly. this works great because everything is interface driven and there is no shared context between services / repositories.
But now with the introduction of an "entity context" I need to create the context once in the app ( so singleton scope ) and then would in essence like to open a new session per request and pass that to any service layer objects that require it. Over and above this, the ORM needs to be initialized at application startup ( or I suppose it can be done lazily, but at app start would be better )
Here is some code generated by vita as to how to initialize the ORM
class Program {
public static MyEntityApp App;
static void Main(string[] args) {
Console.WriteLine(" Sample application for VITA-generated model. ");
Init();
//Open session and run query
var session = App.OpenSession();
var query = from ent in session.EntitySet<IConnections>() // just random entity
// where ?condition?
select ent;
var entities = query.Take(5).ToList();
Console.WriteLine("Loaded " + entities.Count + " entities.");
foreach(var ent in entities)
Console.WriteLine(" Entity: " + ent.ToString()); // change to smth more meaningful
Console.WriteLine("Press any key ...");
Console.ReadKey();
}
private static void Init() {
App = new MyEntityApp();
App.CacheSettings.AddCachedTypes(CacheType.FullSet /* , <fully cached entity types> */ );
App.CacheSettings.AddCachedTypes(CacheType.Sparse /* , <sparsely cached entity types> */ );
var connString = #".......";
var driver = new Vita.Data.MsSql.MsSqlDbDriver();
App.LogPath = "_appLog.log";
var dbSettings = new DbSettings(driver, DbOptions.Default, connString, upgradeMode: DbUpgradeMode.Always);
App.ConnectTo(dbSettings);
}
}
As you can see they initialize and set a static variable which has reference to the context container. As can also be seen by the above is that you need to call var session = App.OpenSession(); to work with the context, so I was hoping to create one session per request and then inject that session into the service object contructors.
So here is what I have done so far
/*Map the Vita ORM session into the request scope*/
kernel.Bind<MyEntities>().ToSelf().InSingletonScope();
I am assuming that will call the contructor, and inside there I have initialized the context correctly ( and this should also only be called once )
then in the service objects i want to do something like this
here is the service impl
private IEntitySession _Session { get; set; }
public VitaSettingsServiceImpl(IEntitySession Session)
{
_Session = Session;
}
And here is my attempt at injecting that session object...
kernel.Bind<ISettingsService>().To<VitaSettingsServiceImpl>().InRequestScope().WithConstructorArgument("Session", [WANT TO CALL MYENTITIES.OpenSession() HERE]);
As you can see it is that last binding that is stumping me? how do I bind a contructor object param to a method call on an existing singleton bound object?
Like I said in the begining, I am VERY green at this and maybe I am going about it all wrong, but i have scoured the web and cant find any info regarding these to technologies being used together, so any help would be greatly appreciated
So for all the lonely souls trolling the web with similar issues, here is what I eventually cam up with :
/*Map the Vita ORM session into the request scope*/
kernel.Bind<SorbetEntities>().ToSelf().InSingletonScope();
/*Map all the services to their respective implementations*/
kernel.Bind<ISettingsService>().To<sorbet.Vita.VitaSettingsServiceImpl>().InRequestScope().WithConstructorArgument("Session", CreateVitaSession);
and then a static extension method to execute the method that will return my connection context
private static object CreateVitaSession(IContext context)
{
return context.Kernel.Get<SorbetEntities>().OpenSession();
}
And thats it. now i get a new connection context per request and I am a happy camper
Related
I am working on an application that should take data from one server and put the data into my database. I am currently struggling to update the database on startups.
I tried several approaches like creating an action which is calling a model which updates the database (with no luck), I tried to research if there is a way to do it in Startup class, but again with no luck.
I have these methods for now
TfsMethods.cs (Models class)
public TfsMethods(ApplicationDbContext db)
{
_db = db;
}
public void UpdateDbBranches()
{
var branches = GetAllBranches();
var dbBranches = _db.Branches;
foreach (var branch in branches)
{
if (dbBranches.Where(x => x.BranchName == branch.BranchName) == null)
{
_db.Add(branch);
}
}
}
where _db is ApplicationDbContext and GetAllBranches() is a method which gets data from one server and I want to put these into my DB.
BranchController.cs (Controller class)
public IActionResult UpdateDbBranches()
{
TfsMethods tfs = new TfsMethods( _db );
try
{
tfs.UpdateDbBranches();
return Ok();
}
catch( Exception e )
{
return Problem( e.ToString() );
}
}
The issue here is that I don't know what to call it on startups, even on razor page or Startup.cs
Is this somehow possible or is there any implementation that could help me with this issue?
There are several useful answers already available at:
Application startup code in ASP.NET Core
However, you should first consider if you really want to do this on startup or on a schedule. See Quartz.net for example: https://www.infoworld.com/article/3529418/how-to-schedule-jobs-using-quartznet-in-aspnet-core.html
If you're trying to do so-called Database Seeding, these links (and links in those discussions) may be useful to you:
https://github.com/Homely/Homely.AspNetCore.Hosting.CoreApp/issues/11
https://github.com/dotnet/aspnetcore/issues/10137
https://github.com/App-vNext/Polly/issues/638
I would like to reduce load on Azure Cosmos DB SQL-API, which is called from a .NET Core Web API with dependency injection.
In App Insights, I have noticed that every call to the Web API results in GetDatabase and GetCollection calls to Cosmos which can take 5s to run when Cosmos is under heavy load.
I have made CosmosClient a singleton (e.g advice here - https://learn.microsoft.com/en-us/azure/cosmos-db/performance-tips-dotnet-sdk-v3-sql)
However I could not find any advice for whether the Database or Container objects could also be singletons so these are created for each request to the Web API.
I check for the existence of the database and collection (e.g. following advice here - https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.cosmosclient.getdatabase?view=azure-dotnet#remarks and https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.cosmosclient.getcontainer?view=azure-dotnet#remarks)
This means that for every request to the Web API, the following code is run
var databaseResponse = await this.cosmosClient.CreateDatabaseIfNotExistsAsync(
this.databaseConfiguration.DatabaseName,
throughput: this.databaseConfiguration.DatabaseLevelThroughput);
var database = databaseResponse.Database;
var containerResponse = await database.CreateContainerIfNotExistsAsync(containerId, partitionKey);
var container = containerResponse.Container;
Can I make Database and Container singletons and add them to the DI to be injected like CosmosClient in order to reduce the number of calls to GetDatabase and GetCollection seen in App Insights?
According to the latest Microsoft documentation, you create a CosmosClient Service singleton, which owns the Containers you will be working with. In affect making the Containers singletons as well.
First, make your interface contract:
public interface ICosmosDbService
{
// identify the database CRUD operations you need
}
Second, define your Service based on the contract:
public class CosmosDbService : ICosmosDbService
{
private Container _container;
public CosmosDbService(CosmosClient dbClient, string databaseName, string containerName)
{
this._container = dbClient.GetContainer(databaseName, containerName);
}
// your database CRUD operations go here using the Container field(s)
}
Third, create a method in your Startup class to return a CosmosClient:
private static async Task<CosmosDbService> InitializeCosmosClientAsync(IConfigurationSection cosmosConfig)
{
var databaseName = cosmosConfig.GetSection("DatabaseName").Value;
var containerName = cosmosConfig.GetSection("ContainerName").Value;
var account = cosmosConfig.GetSection("Account").Value;
var key = cosmosConfig.GetSection("Key").Value;
var client = new Microsoft.Azure.Cosmos.CosmosClient(account, key);
var database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerName, "/id");
return new CosmosDbService(client, databaseName, containerName);
}
Finally, add your CosmosClient to the ServiceCollection:
public void ConfigureServices(IServiceCollection services)
{
var cosmosConfig = this.Configuration.GetSection("CosmosDb");
var cosmosClient = InitializeCosmosClientAsync(cosmosConfig).GetAwaiter().GetResult();
services.AddSingleton<ICosmosDbService>(cosmosClient);
}
Now your CosmosClient has only been created once (with all the Containers), and it will be reused each time you get it through Dependency Injection.
You don't need to call CreateIfNotExistsAsync every time, if you know that they are available, you can use CosmosClient.GetContainer(dbName, containerName) which is a lightweight proxy class.
Unless you are expecting the database and containers to be deleted dynamically at some point?
CreateDatabaseIfNotExistsAsync should only be called once as it is just a setup step for DB configuration.
You'd better create a DbService to persist the container object. And inject the DbService into each services instead of the DB client
In attempts to do some test-driven-development, I've created the most basic, buildable method:
public class NoteService : INoteService
{
public IEnumerable<Annotation> GetNotes(ODataQueryOptions oDataQueryOptions)
{
return new List<Annotation>();
}
}
When trying to unit test it, it seems impossible to create an instance of ODataQueryOptions:
[TestFixture]
public class NoteServiceTests
{
[Test]
public void GetNotes_Returns_IEnumerable_Of_Notes()
{
var sut = new NoteService();
var queryOptions = new ODataQueryOptions(new ODataQueryContext(new EdmCoreModel(), new EdmCollectionType())// new new new etc??
Assert.That(() => sut.GetNotes(options), Is.InstanceOf<IEnumerable<Annotation>>());
}
}
How do you create a simple instance of the object ODataQueryOptions in order to inject it for unit tests?
Will this work?
var request = new HttpRequestMessage(HttpMethod.Get, "");
var context = new ODataQueryContext(EdmCoreModel.Instance, typeof(int));
var options = new ODataQueryOptions(context, request);
I wanted to do something similar and found a workable solution:
My use case
I have a web service that passes OData query parameters down to our CosmosDB document client which translates them into a CosmosDB SQL query.
I wanted a way to write integration tests directly on the CosmosDB client without having to make outgoing calls to other downstream services.
Approaches
Tried mocking ODataQueryParameters using Moq but because it's a class and not an interface, Moq can't properly instantiate all of the properties that I need
Tried instantiating one directly, but outside of an MVC application this is extremely difficult to build the required EdmModel.
Wondered if there is a way to do it without constructing an EdmModel?
I finally figured out a way to do it with the following:
This may not solve every use case, but here's the solution that I landed on for my needs:
public static class ODataQueryOptionsBuilder
{
private static WebApplicationFactory<TEntryPoint> _app =
new WebApplicationFactory<TEntryPoint>();
public static ODataQueryOptions<T> Build<T>(
string queryString)
where T : class
{
var httpContext = new DefaultHttpContext();
httpContext.Request.QueryString = new QueryString(queryString);
httpContext.RequestServices = _app.Services;
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntityType<T>();
var model = modelBuilder.GetEdmModel();
var context = new ODataQueryContext(
model,
typeof(T),
new Microsoft.AspNet.OData.Routing.ODataPath());
return new ODataQueryOptions<T>(context, httpContext.Request);
}
}
Conclusion
I realized that all I needed to get the ODataQueryOptions was a test server that was set up exactly like my web service. Once I realized that, the solution was simple. Use WebApplicationFactory to create the test server, and then allow OData to use the IServiceProvider from that application to build the ODataQueryOptions that I needed.
Drawbacks
Realize that this solution is as performant as your TEntryPoint, so if your application takes a long time to start up, this will cause unit/integration tests to take a long time to run.
My application is in ASP.NET MVC 4.
I'm using BDContext per request, as recommended in many questions here.
I have:
public static class ContextPerRequest {
private const string myDbPerRequestContext = "dbGeTraining_";
public static DbGesForma_v2 db {
get {
if (!HttpContext.Current.Items.Contains(myDbPerRequestContext + HttpContext.Current.GetHashCode().ToString("x") + Thread.CurrentContext.ContextID.ToString())) {
HttpContext.Current.Items.Add(myDbPerRequestContext + HttpContext.Current.GetHashCode().ToString("x") + Thread.CurrentContext.ContextID.ToString(), new DbGesForma_v2());
}
return HttpContext.Current.Items[myDbPerRequestContext + HttpContext.Current.GetHashCode().ToString("x") + Thread.CurrentContext.ContextID.ToString()] as DbGesForma_v2;
}
}
/// <summary>
/// Called automatically on Application_EndRequest()
/// </summary>
public static void DisposeDbContextPerRequest() {
// Getting dbContext directly to avoid creating it in case it was not already created.
var entityContext = HttpContext.Current.Items[myDbPerRequestContext + HttpContext.Current.GetHashCode().ToString("x") + Thread.CurrentContext.ContextID.ToString()] as DbGesForma_v2;
if (entityContext != null) {
entityContext.Dispose();
HttpContext.Current.Items.Remove(myDbPerRequestContext + HttpContext.Current.GetHashCode().ToString("x") + Thread.CurrentContext.ContextID.ToString());
}
}
}
And I dispose it in Application_EndRequest() method. This approach worked well for a long time.
Now I'm trying to implement something with asynchronous tasks like this:
Task.Factory.StartNew(() => {
DoSomething();
});
This brings some problems.
HttpContext is null in subthreads, and is used in the key of the context.
Even if I would be able to pass httpcontext or null check it, if a subthread takes longer to run than the request itself, it would be disposed before the thread finishes, which would be problematic.
Any solutions?
I'm not sure what version of ASP.NET you use. Anyway, ASP.NET MVC (also WebAPI) has DependencyResolver which support those 'per-request' instance.
http://www.asp.net/mvc/overview/older-versions/hands-on-labs/aspnet-mvc-4-dependency-injection
Also, I recommend you to use DI framework with DependencyResolver, rather than implement per-request instance factory (or something like that). Most of well-known DI frameworks are support integration with ASP.NET.
For instance;
Unity
Autofac
SimpleInjector
and many others
I've found a less intrusive solution. (I know, Gongdo Gong solution is much better, but requires a lot of changes in an ongoing project)
When I call the async task I pass through the HttpContext, and at the end I dispose the Context.
Like this:
System.Web.HttpContext htcont = System.Web.HttpContext.Current;
Task.Factory.StartNew(() => {
System.Web.HttpContext.Current = htcont;
DoSomething();
ContextPerRequest.DisposeDbContextPerRequest();
});
This way HttpContext is usable inside the subthread and the context gets disposed at the end of the job.
In the following code doesn't work as
public void Foo()
{
CompanyDataContext db = new CompanyDataContext();
Client client = (select c from db.Clients ....).Single();
Bar(client);
}
public void Bar(Client client)
{
CompanyDataContext db = new CompanyDataContext();
db.Client.Attach(client);
client.SomeValue = "foo";
db.SubmitChanges();
}
This doens't work, I get error msg. "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."
How do you work with DataContexts throughout an application so you don't need to pass around a reference?
What
They really mean it with 'This is not supported.'. Attaching to an object fetched from another data context is not implemented.
There are a number of workarounds to the problem, the recommended way is by serializing objects, however this is not easy nor a clean approach.
The most simple approach I found is to use a readonly DataContext for fetching objects like this:
MyDataContext dataContext = new MyDataContext()
{
DeferredLoadingEnabled = false,
ObjectTrackingEnabled = false
};
The objects obtained from this context can be attached to another context but only applies to some scenarios.
The PLINQO framework generates detach for all entities making it easy to detach and reattach objects without receiving that error.
public void Foo()
{
CompanyDataContext db = new CompanyDataContext();
Client client = (select c from db.Clients ....).Single();
// makes it possible to call detach here
client.Detach();
Bar(client);
}
public void Bar(Client client)
{
CompanyDataContext db = new CompanyDataContext();
db.Client.Attach(client);
client.SomeValue = "foo";
db.SubmitChanges();
}
Here is the article that describing how the detach was implemented.
http://www.codeproject.com/KB/linq/linq-to-sql-detach.aspx
Yep. That's how it works.
You have tagged this asp.net so I guess it's a web app. Maybe you want one datacontext per request?
http://blogs.vertigo.com/personal/keithc/Blog/archive/2007/06/28/linq-to-sql-and-the-quote-request-scoped-datacontext-quote-pattern.aspx
(P.S. It's a lot harder in WinForms!)
I've created data access classes that encapsulate all the communication with Linq2Sql.
These classes have their own datacontext that they use on their objects.
public class ClientDataLogic
{
private DataContext _db = new DataContext();
public Client GetClient(int id)
{
return _db.Clients.SingleOrDefault(c => c.Id == id);
}
public void SaveClient(Client c)
{
if (ChangeSetOnlyIncludesClient(c))
_db.SubmitChanges();
}
}
Ofcourse you will need to keep this object instantiated as long as you need the objects.
Checking if only the rigth object has been changed is altso somewhat bothersom, you could make methods like
void ChangeClientValue(int clientId, int value);
but that can become a lot of code.
Attaching and detaching is a somewhat missing feature from Linq2Sql, if you need to use that a lot, you sould probably use Linq2Entities.
I took a look at this and found that it appears to work fine as long as the original DataContext has been disposed.
Try wrapping the DataContext with using() and make sure your changes occur after you've attached to the second DataContext? It worked for me..
public static void CreateEntity()
{
User user = null;
using (DataClassesDataContext dc = new DataClassesDataContext())
{
user = (from u in dc.Users
select u).FirstOrDefault();
}
UpdateObject(user);
}
public static void UpdateObject(User user)
{
using (DataClassesDataContext dc = new DataClassesDataContext())
{
dc.Users.Attach(user);
user.LastName = "Test B";
dc.SubmitChanges();
}
}
You need to handle object versioning.
An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy.
So, if there's no timestamp member or other 'versioning' mechanism provided there's no way for LINQ to determine whether that data has changed - hence the error you are seeing.
I resolved this issue by adding a timestamp column to my tables but there are other ways around it. Rick Strahl has written some decent articles about exactly this issue.
Also, see this and this for a bit more info.