Getting a connection string for EF Core DbContext - c#

I'm trying to write a dbcontext for EF Core and I need to get a connection string from my IConfiguration. If I ask for an IConfiguration in my constructor for this class, it should get it through dependency injection if I use this?
services.AddDbContext<PostgresContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("BloggingContext")));
The above code is from the Npgsql EFCore instructions but I have no idea where Configuration is coming from. I could really use some help here. Should I just use the AddSingleton method here and let DI do its thing?
Here's my class if you're interested
namespace Foo
{
public class PostgresContext : DbContext
{
private PostgresSettings settings { get; init; }
PostgresContext(IConfiguration configuration)
{
settings = configuration.GetSection(nameof(PostgresSettings)).Get<PostgresSettings>();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(settings.getConnectionString());
}
}
}

Your (subclass) of DbContext should not be named PostgresContext or contain any postgres specific code.
Your dbContext should named something like
"MyAppDbContext".
This will contain DbSet(s) of your Orm-entities and their mappings.
Again, it should not contain postgres specific stuff.
...
Now, somewhere you need to say "the concrete I want to use for my EF setup is postgres".
That should be ONE place. Your IoC registration(s). DotNet-Core's "IoC container" is referred to as the IServiceCollection.
My postgres IoC registration looks like this:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Npgsql;
string completeConnectionString = /* get this from your IConfiguration */;
services.AddDbContext<MyAppDbContext>(
optionsBuilder => optionsBuilder.UseNpgsql(
completeConnectionString,
options => options.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromMilliseconds(100),
errorCodesToAdd: null))
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking),
ServiceLifetime.Transient);
(Sidenote, you should not use the TrackingBehavior and ServiceLifetime items blindly. My usages are for a Asp.Net-Core application. If you have a "winforms" type of application (client-install..you may have different settings.)
While you are currently using postgres, you should be able to TRIVIALLY change to Ms-Sql-Server by only changing the AddDbContext line. That is a "tell-tale" sign that you actually did things "well".
Your "names" of your objects should not be tied to a specific RDBMS.
EntityFramework-Core (or any ORM for that matter) is an abstraction away from a specific RDBMS.
....
Here is a suggestion to keep your code "clean".
You might/should-probably have a .csproj that is about the Entity-Framework-Orm concerns.
And it should reference
(the versions might be 3.1'ish OR 6.0'ish OR 7.0'ish)
But pay attention to the packages.
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.10" />
Note, the lack of any reference that is "postgres" specific.
..
then in your "top-entry-point" layer (your asp.net-core app or dot-net-core console-app as examples)... you have a reference to:
You will have the postgres package (or ms-sql-server or whatever rdbms-to-entity-framework-core package) that you are currently targeting.
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.7" />
This last package reference is what allows you to do the AddDbContext using the "UseNpgsql" "choice".
.....
In a nutshell, you are not writing EF-Core code for postgres specifically.
(EF-Core is NOT ADO.NET, where in ADO.NET you probably wrote code that was rdbms specific...unless you used only the most basic SQL statements).
You are writing EF-Core code (again , an abstraction away from a specific RDBMS).......... and with (only) the AddDbContext/UseNpgsql are you choosing a concrete(rdms-provider) to use.
...
Here is a link as example.
https://github.com/granadacoder/dotnetcore-hostedservice-containerized-one/blob/master/src/DataLayer.EntityFramework/Contexts/WorkerServiceExampleOneDbContext.cs#L29L39
That shows the kind of things that go in a DbContext. and it should not be anything postgres (or any rdbms) specific.
You can also "internet search" the 2 below items:
AddDbContext UseNpgsql
and you'll find articles like:
https://code-maze.com/configure-postgresql-ef-core/
You will note their "ApplicationContext" has DbSet(s) in it... and nothing postgres specific.

Related

Is it possible for EF Core to respect code in OnModelCreating/ConfigureConventions as part of migration code generation?

I don't know if I'm expecting too much, but there have been a few different things I've ran into where I attempted to configure EF Core 6 behavior in OnModelCreating and/or ConfigureConventions that simply don't seem to be respected when new migrations are created.
For example, I would like to force all string properties to be varchar (instead of nvarchar). There are dozens of examples of handling this via model property attributes that do seem to be respected at migration code generation time, but unfortunately they are all at the property level. I have also seen examples setting IsUnicode(false) via overriding ConfigureConventions like so:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.DefaultTypeMapping<string>()
.HasColumnType("varchar")
.IsUnicode(false);
}
But this code doesn't seem to be respected at migration code generation time. It's simply ignored. I realize it will be respected at runtime when queries are being generated, but I'm specifically talking about during development when generating new migration code.
Should this code in ConfigureConventions be respected when new migration code is created such that if I add a new string property to the model and then add a new migration, that the generated code should show the new column type as varchar?
UPDATE per #David Brown's request for repro example:
I created a new .NET6 Core Console app with the following:
Program.cs
using ConsoleApp1;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddDbContext<MyDbContext>();
})
.Build();
host.Run();
MyDbContext.cs
using Microsoft.EntityFrameworkCore;
namespace ConsoleApp1
{
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
public DbSet<WorkRequest> WorkRequests { get; set; }
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.DefaultTypeMapping<string>()
.HasColumnType("varchar")
.IsUnicode(false);
base.ConfigureConventions(configurationBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=MyDb;Trusted_Connection=True;");
}
}
}
WorkRequest.cs
namespace ConsoleApp1
{
public class WorkRequest
{
public Int64 Id { get; set; }
//[Unicode(false), MaxLength(256)]
public string SubmitterId { get; set; }
}
}
ConsoleApp1.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Migrations\" />
</ItemGroup>
</Project>
I then simply drop to the Package Manager Console and enter:
add-migration InitialCreate -verbose
The resulting migration code always shows SubmitterId as nvarchar(max) unless I use the commented property attribute instead of the ConfigureConventions code:
SubmitterId = table.Column<string>(type: "nvarchar(max)", nullable: false),
The code in OnModelCreating / ConfigureConventions is of course respected in migrations.
The problem you are hitting is the DefaultTypeMapping method, specifically the difference between expectations and what it really does.
Just looking at the method name, one would expect exactly what you did - that it specifies the default mapping for a CLR type. From the other side, there is another method called Properties which returns a builder with similar methods (just starting with Are / Have instead of Is / Has), so it's unclear what's the difference and which one you should use.
The documentation of DefaultTypeMapping does not help a lot:
Marks the given type as a scalar, even when used outside of entity types. This allows values of this type to be used in queries that are not referencing property of this type.
Unlike Properties(Type) this method should only be called on a non-nullable concrete type. Calling it on a base type will not apply the configuration to the derived types.
Calling this is rarely needed. If there are properties of the given type calling Properties(Type) should be enough in most cases.
It shows a small difference in the behavior and says that using the method is rarely needed, but does not say why.
Default type mapping section of the docs is a bit cleaner:
Generally, EF is able to translate queries with constants of a type that is not supported by the provider, as long as you have specified a value converter for a property of this type. However, in queries that don't involve any properties of this type, there is no way for EF to find the correct value converter. In this case, it's possible to call DefaultTypeMapping to add or override a provider type mapping
So, it turns out this method and the associated fluent configuration calls affect only query translation, and not the entity property mappings (thus migrations).
Shortly, inside ConfigureConventions use Properties method and fluent API for configuring the model defaults, and they will be respected everywhere, including migrations, e.g. in your example:
configurationBuilder
.Properties<string>()
//.HaveColumnType("varchar") // not needed, see below
.AreUnicode(false); // abstract way of specifying varchar vs nvarchar
As a side note, be also aware of the following little note in the docs
Data annotations do not override pre-convention configuration.
which means that if you use data annotations (attributes) for non default columns of the type configured via Properties, they won't be respected (you have to use fluent API and OnModelCreating). This is quite important difference from "default" EF Core conventions which have less priority than data annotations, and just another example of poorly documented poor design decision (they say, for me it is implementation bug) difference between logical expectation (we are only overriding default conventions, right?) and the actual behavior.

Using dependency injection in SpecFlow step-file

We're using Unity as our dependency injection framework.
I want to create an acceptance test and need an instance of DossierService.
Unfortunately I get the following exception:
BoDi.ObjectContainerException: 'Interface cannot be resolved [...]'
[Binding]
public class DossierServiceSteps : BaseSteps
{
private IDossierService dossierService;
public DossierServiceSteps(IDossierService dossierService)
{
this.dossierService = dossierService;
}
}
What exactly is BoDi? I can't find any useful information..
How can I tell SpecFlow to use the normal Unity container?
Thanks in advance
Edit:
I've tried using SpecFlow.Unity like so:
public static class TestDependencies
{
[ScenarioDependencies]
public static IUnityContainer CreateContainer()
{
var container = UnityConfig.GetConfiguredContainer();
container.RegisterTypes(typeof(TestDependencies).Assembly.GetTypes().Where(t => Attribute.IsDefined(t, typeof(BindingAttribute))),
WithMappings.FromMatchingInterface,
WithName.Default,
WithLifetime.ContainerControlled);
return container;
}
}
In UnityConfig the types are correctly registered
container.RegisterType<IDossierService, DossierService>(new InjectionConstructor(typeof(IDataService), typeof(IDossierRepository), typeof(IDbContext), true));
But I still get the same exception. When I put a breakpoint at the start of the CreateContainer() method of TestDependencies it doesn't break...
For anyone looking for available plugins/libraries that support DI in Specflow project: https://docs.specflow.org/projects/specflow/en/latest/Extend/Available-Plugins.html#plugins-for-di-container
I prefer - https://github.com/solidtoken/SpecFlow.DependencyInjection
Example
Create DI container:
[ScenarioDependencies]
public static IServiceCollection CreateServices()
{
var services = new ServiceCollection();
Config config = JObject.Parse(File.ReadAllText("config.json")).ToObject<Config>();
services.AddSingleton(config);
services.AddScoped<DbConnections>();
services.AddScoped<ApiClients>();
return services;
}
Consume dependencies (via parameterized constructors):
[Binding]
public sealed class CalculatorStepDefinitions
{
private readonly DbConnections dbConnections;
public CalculatorStepDefinitions(DbConnections dbConnections) => this.dbConnections = dbConnections;
...
}
We solved this problem by implementing SpecFlow RuntimePlugin. In our case it was Castle.Windsor, but principle is the same. First you define the plugin which override default SpecFlow Instance Resolver:
public class CastleWindsorPlugin : IRuntimePlugin
{
public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters)
{
runtimePluginEvents.CustomizeScenarioDependencies += (sender, args) =>
{
args.ObjectContainer.RegisterTypeAs<CastleWindsorBindingInstanceResolver, IBindingInstanceResolver>();
};
}
}
Where in CastleWindsorBindingInstanceResolver we needed to implement single method: object ResolveBindingInstance(Type bindingType, IObjectContainer scenarioContainer);. This class contains container and resolution (in your case instance of IUnityContainer. I recommend to inject to the container instance of self, so that you could inject the instance of IUnityContainer to SpecFlow binding classes)
This plugin needs to be in separate assembly and you load that to your test project like adjusting app.config like this:
<specFlow>
<plugins>
<add name="PluginAssemblyName" path="." type="Runtime" />
</plugins>
...
</specFlow>
What exactly is BoDi? I can't find any useful information..
BoDI is a very basic Dependency Injection framework that ships within Specflow. You can find its code repository here.
See this entry from the blog of SpecFlow's creator, Gáspár Nagy (emphasis mine):
SpecFlow uses a special dependency injection framework called BoDi to handle these tasks. BoDi is an embeddable open-source mini DI framework available on GitHub. Although it is a generic purpose DI, its design and features are driven by the needs of SpecFlow. By the time the SpecFlow project started, NuGet had not existed yet, so the libraries were distributed via zip downloads and the assemblies had to be referenced manually. Therefore we wanted to keep the SpecFlow runtime as a single-assembly library. For this, we needed a DI framework that was small and could be embedded as source code. Also we did not want to use a well-known DI framework, because it might have caused a conflict with the version of the framework used by your own project. This led me to create BoDi.
You can find an example of how to register types and interfaces in BoDI here:
[Binding]
public class DependencyConfiguration
{
private IObjectContainer objectContainer;
public DependencyConfiguration(IObjectContainer objectContainer)
{
this.objectContainer = objectContainer;
}
[BeforeScenario(Order = 0)]
public void ConfigureDependencies()
{
if (...)
objectContainer.RegisterTypeAs<RealDbDriver, IControllerDriver>();
else
objectContainer.RegisterTypeAs<StubDbDriver, IControllerDriver>();
}
}
However, be warned that (in the words of Gáspár Nagy):
Although it is possible to customize the dependency injection used by SpecFlow, it already scratches the boundaries of BoDi’s capacity. A better choice would be to use a more complex DI framework for this.
In this situation usually you should use Mock of your Interface.

The default DbConfiguration instance was used before the 'EntityFrameworkConfiguration' type was discovered

public class EntityFrameworkConfiguration : DbConfiguration
{
public EntityFrameworkConfiguration()
{
this.SetModelCacheKey(ctx => new EntityModelCacheKey((ctx.GetType().FullName + ctx.Database.Connection.ConnectionString).GetHashCode()));
}
}
To make the above code work i have added below line in web.config
But for other project where i am using the assembly reference i am getting exception:
{"The default DbConfiguration instance was used by the Entity
Framework before the 'EntityFrameworkConfiguration' type was
discovered. An instance of 'EntityFrameworkConfiguration' must be set
at application start before using any Entity Framework features or
must be registered in the application's config file. See
http://go.microsoft.com/fwlink/?LinkId=260883 for more information."}
Your question doesn't state how you are using this custom DbConfiguration.
You could get this error a couple of different ways.
Configuration style setup
as described here: Entity Framework Config File Settings
Configuration as code
as described here: Entity Framework Code-Based Configuration (EF6 onwards)
You can hack at this style by doing things like DbConfiguration.SetConfiguration(xxxx). I didnt find this useful at all.
What this really comes down to is how you construct your DbContext.
Configuration file style constructors
https://github.com/aspnet/EntityFramework6/blob/master/src/EntityFramework/DbContext.cs#L75
With no arguments - EF6 is uses the configuraiton files to determine the right DbCofniguration to use
with some "connection string-like" arguments again EF6 is using configuration files to determine the DbConfiguration
no config, or bad config - and you will get this sort or exception
Configuration as Code style constructors
https://github.com/aspnet/EntityFramework6/blob/master/src/EntityFramework/DbContext.cs#L139
I think this yields better control.
Attribute your DbContext, then use a manually created DbConnection
public class EntityFrameworkConfiguration : DbConfiguration
{
public EntityFrameworkConfiguration()
{
this.SetModelCacheKey(ctx => new EntityModelCacheKey((ctx.GetType().FullName + ctx.Database.Connection.ConnectionString).GetHashCode()));
}
}
[DbConfigurationType(typeof(EntityFrameworkConfiguration))]
public class MyContext : DbContext
{
public MyContext(DbConnection existingConnection, bool contextOwnsConnection)
: base(existingConnection, contextOwnsConnection)
{ }
public DbSet<Stuff> Stuff { get; set; }
}
using(var conn = new SqlConnection(asqlserverConnectionString))
using (var db = new MyContext(conn, true))
{
var value = await db.Stuff.Where(s => s.xxx.Equals(primaryKey)).Select(s => new { s.BinaryContent } ).SingleOrDefaultAsync();
}
If you are using Code-based configuration, try updating the config file thusly:
<entityFramework codeConfigurationType="MyNamespace.MyDbConfiguration, MyAssembly">
...Your EF config...
</entityFramework>
In my case i have two different edmx files and both are in different class libraries.
I got this error when i added those two libraries in the same project.
I don't have any single clue how is that sorted out but; when i call any method from my first DbContext class, the second one worked like miracle happened. It was throwing this error when second context class called first.
My Ef version is: 6.4

Entity code first migrations and deleting databases

We're using EF Code First and migrations for a project. We're commiting our migrations to source, and everything is great. However, if someone delete's their database, or we get a new person on the project, the database will throw errors because it's trying to run the migrations. Is there a way to make it so that if the database doesn't exist, it ignores migrations? I can't seem to find anything about this.
I would look at how you are using DbMigrationsConfiguration from the Entity framework. You might need something like this in your global asx file:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<YourContext, YourConfiguration>());
Then in your configuration file for migrations you may need something like this:
internal sealed class YourConfiguration : DbMigrationsConfiguration<YourContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
}...
Without more info and code examples, I can only point you in the right direction.

What is the equivalent of HybridHttpOrThreadLocalScoped in structure map 3?

With structuremap 2.6.4.1 my container is configured like this:
existingContainer.Configure(expression =>
{
expression.For<IDocumentSession>()
.HybridHttpOrThreadLocalScoped()
.Use(container =>
{
var store = container.GetInstance<IDocumentStore>();
return store.OpenSession();
});
}
HybridHttpOrThreadLocalScoped does not exist in structure map 3 so my question is, what is the equivalent configuration in structuremap 3?
As of StructureMap 3, anything HttpContext related lives within a separate Nuget package called StructureMap.Web which can be found here.
The reason for this is StructureMap 3 is now PLC (Portalble Class Library) compliant, so splitting web-related lifecycles into its own package makes sense.
It is there, says here http://jeremydmiller.com/2014/03/31/structuremap-3-0-is-live/ that is now a Structuremap.Web nuget to add to your project for it to work.

Categories

Resources