Empty DBContext at startup - c#

I want to make a test database that erase its content each time the program starts in a ASP.Net/Blazor project or end.
What should I add to be sure that all the changes I made during runtime get erased when program stops ?
Edit : I'm also seeding data with OnModelCreating method of DbContext and I want the data seeded to be kept.

Since Blazor only works on ASP.NET Core 3, I assume you use EF Core, not just Entity Framework.
EF Core was designed with unit testing in mind. The Testing chapter in the EF Core documentation explains how you to use the in-memory provider or SQLite provider in in-memory mode for testing.
Assuming your contexts have a constructor that accepts DbContextOptions, you can configure their provider from the outside, either during testing or because you want to use different connection strings.
Given this DbContext
public class BloggingContext : DbContext
{
public BloggingContext()
{ }
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
...
}
Using one provider or another is as simple as creating the options and calling the constructor.
In-Memory provider
The in-memory provider is essentially a dictionary, so it can't cover complex query scenarios. It's easier to setup and use though.
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseInMemoryDatabase(databaseName: "Add_writes_to_database")
.Options;
// Run the test against one instance of the context
using (var context = new BloggingContext(options))
{
var service = new BlogService(context);
service.Add("https://example.com");
context.SaveChanges();
}
SQLite in-memory provider
Using the SQLite provider is almost the same. An in-memory database is created and then the code is essentially the same as before. When the connection closes, the database and the data in it are gone.
using(var connection = new SqliteConnection("DataSource=:memory:"))
{
connection.Open();
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlite(connection)
.Options;
// Create the schema in the database
using (var context = new BloggingContext(options))
{
context.Database.EnsureCreated();
}
// Run the test against one instance of the context
using (var context = new BloggingContext(options))
{
var service = new BlogService(context);
service.Add("https://example.com");
context.SaveChanges();
}
}

Related

EF Core 3.1 Creating Views in Sqlite in-memory database for testing

I'm using EF Core 3.1 to build out my Database Models for SqlServer. I'm also using EF generated migration files to handle database changes. For testing, I'm spinning up in-memory Sqlite relational database as described in microsoft documentation: https://learn.microsoft.com/en-us/ef/core/miscellaneous/testing/sqlite.
All my tests were running as expected until I added Views to my database. Views were added according to this documentation: https://learn.microsoft.com/en-us/ef/core/modeling/keyless-entity-types.
Based on Microsoft documentation, an example of a test should look something like this:
[Fact]
public void Add_writes_to_database()
{
// In-memory database only exists while the connection is open
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
try
{
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlite(connection)
.Options;
// Create the schema in the database
using (var context = new BloggingContext(options))
{
context.Database.EnsureCreated();
}
// Run the test against one instance of the context
using (var context = new BloggingContext(options))
{
var service = new BlogService(context);
service.Add("https://example.com");
context.SaveChanges();
}
// Use a separate instance of the context to verify correct data was saved to database
using (var context = new BloggingContext(options))
{
Assert.Equal(1, context.Blogs.Count());
Assert.Equal("https://example.com", context.Blogs.Single().Url);
}
}
finally
{
connection.Close();
}
}
The line context.Database.EnsureCreated(); ensures that the database is created. It creates the database, tables, and inserts associated data; according to specified logic in my protected override void OnModelCreating(ModelBuilder modelBuilder) method.
The problem is, it does not create the View. According to documentation; This is the expected behaviour.
My code to create the View looks very much like the example code:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<BlogPostsCount>(eb =>
{
eb.HasNoKey();
eb.ToView("View_BlogPostCounts");
eb.Property(v => v.BlogName).HasColumnName("Name");
});
}
I do have manually modified migration file that will create the View using raw sql. And this works on SqlServer, but it appears the context.Database.EnsureCreated(); method ignores the migration files on creating database.
Any help is appreciated.

Hangfire - Multi tenant, ASP.NET Core - Resolving the correct tenant

I got a SaaS project that needs the use Hangfire. We already implemented the requirements to identify a tenant.
Architecture
Persistence Layer
Each tenant has it's own database
.NET Core
We already have a service TenantCurrentService which returns the ID of the tenant, from a list of source [hostname, query string, etc]
We already have a DbContextFactory for Entity Framework which return a DB context with the correct connection string for the client
We are currently using ASP.NET Core DI (willing to change if that helps)
Hangfire
Using single storage (eg: Postgresql), no matter the tenant count
Execute the job in an appropriate Container/ServiceCollection, so we retrieve the right database, right settings, etc.
The problem
I'm trying to stamp a TenantId to a job, retrieved from TenantCurrentService (which is a Scoped service).
When the job then gets executed, we need to retrieve the TenantId from the Job and store it in HangfireContext, so then the TenantCurrentService knows the TenantId retrieved from Hangfire. And from there, our application layer will be able to connect to the right database from our DbContextFactory
Current state
Currently, we have been able to store tenantId retrieved from our Service using a IClientFilter.
How can I retrieve my current ASP.NET Core DI ServiceScope from IServerFilter (which is responsible to retrieve the saved Job Parameters), so I can call .GetRequiredService().IdentifyTenant(tenantId)
Is there any good article regarding this matter / or any tips that you guys can provide?
First, you need to be able to set the TenantId in your TenantCurrentService.
Then, you can rely on filters :
client side (where you enqueue jobs)
public class ClientTenantFilter : IClientFilter
{
public void OnCreating(CreatingContext filterContext)
{
if (filterContext == null) throw new ArgumentNullException(nameof(filterContext));
filterContext.SetJobParameter("TenantId", TenantCurrentService.TenantId);
}
}
and server side (where the job is dequeued).
public class ServerTenantFilter : IServerFilter
{
public void OnPerforming(PerformingContext filterContext)
{
if (filterContext == null) throw new ArgumentNullException(nameof(filterContext));
var tenantId = filterContext.GetJobParameter<string>("TenantId");
TenantCurrentService.TenantId = tenantId;
}
}
The server filter can be declared when you configure your server through an IJobFilterProvider:
var options = new BackgroundJobServerOptions
{
Queues = ...,
FilterProvider = new ServerFilterProvider()
};
app.UseHangfireServer(storage, options, ...);
where ServerFilterProvider is :
public class ServerFilterProvider : IJobFilterProvider
{
public IEnumerable<JobFilter> GetFilters(Job job)
{
return new JobFilter[]
{
new JobFilter(new CaptureCultureAttribute(), JobFilterScope.Global, null),
new JobFilter(new ServerTenantFilter (), JobFilterScope.Global, null),
};
}
}
The client filter can be declared when you instantiate a BackgroundJobClient
var client = new BackgroundJobClient(storage, new BackgroundJobFactory(new ClientFilterProvider());
where ClientFilterProvider behaves as ServerFilterProvider, delivering client filter
A difficulty may be to have the TenantCurrentService available in the filters. I guess this should be achievable by injecting factories in the FilterProviders and chain it to the filters.
I hope this will help.

EF Core - Create a migration without connection string

I have been ranging across multiple questions, tutorials and examples for something that fits this problem.
What if I don't know my connection string at the time I want to create my first initial migration? Given I am given the opportunity to set the connection string at the time of instantiating context eg:
var connection = #"Server=(localdb)\mssqllocaldb;Database=JobsLedgerDB;Trusted_Connection=True;ConnectRetryCount=0";
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);
using (var context = new BloggingContext(optionsBuilder.Options))
{
// do stuff
}
As described in the docs..
If you need to have a connection string to run migrations then for those situations where you don't have one (Tenant database where you get a connection string from a user account) how do you run an initial migration??
Do you create a dummy connection string to create the migration.. seems dodgy to me. I would love some input on this.
You can implement IDesignTimeDbContextFactory that will be used instead your host builder in the design time. UseSqlServer now has a parameterless overload.
It will look like this and has to be in the same assembly as the db context or in the startup project:
public class MyAppDbContextFactory: IDesignTimeDbContextFactory<MyAppDbContext>
{
public MyAppDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<MyAppDbContext>();
optionsBuilder.UseSqlServer();
return new MyAppDbContext(optionsBuilder.Options);
}
}
More details can be found here:
https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli#from-a-design-time-factory

Using AspNetCore.TestHost with EntityFrameworkCore.InMemory losing data

I'm trying to write an integration test for a small GraphQL server I have using graphql-dotnet. The server is working fine when I use my web application.
To run the integration test, I'm attempting to use Microsoft.AspNetCore.TestHost to configure the server and send the POST request. I'm also trying to use Microsoft.EntityFrameworkCore.InMemory to use an in-memory database rather than a "real" database running locally.
I have a TestStartUp.cs file that sets up the in-memory database and attempts to save a single record:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<MyDbContext>(options =>
options.UseInMemoryDatabase()
);
...
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
MyDbContext myDbContext)
{
myDbContext.Categories.Add(new Category {Id = 1, Name = "Category 1" });
myDbContext.SaveChanges();
app.UseMvc();
}
I've printed out the contents of the database after calling SaveChanges() and confirmed that the object is being saved. However, when my repository object tries to retrieve all Categories in the injected MyDbContext object, it shows that no Categories exist.
I've tried various configurations to no avail. Using a database that isn't in memory works fine, so I'm sure there's something I'm missing. It's driving me mad, so any assistance would be very appreciated!
In order to use InMemoryDatabase in few places, you need to provide the same database options each time (use the same database). Take a look bellow:
protected DbContextOptions<BackofficeContext> GetDbContextOptions()
{
return new DbContextOptionsBuilder<BackofficeContext>().UseInMemoryDatabase(NAME_OF_YOUR_IN_MEMORY_DATABASE).Options;
}
And then you can do something like that:
var databaseOptions = GetDbContextOptions();
using (var context = new MyContext(databaseOptions))
{
//Add some data to the database
context.SaveChanges();
}
using (var context = new MyContext(databaseOptions))
{
//Recieve data from the database
}

How can I call a SQL Stored Procedure using EntityFramework 7 and Asp.Net 5

For last couple of days I am searching for some tutorials about how to call a Stored Procedure from inside a Web API controller method using EntityFramework 7.
All tutorials I came through are showing it the other way round, i.e. Code First approach. But I already have a database in place and I need to use it to build a Web API. Various business logic are already written as Stored Procedures and Views and I have to consume those from my Web API.
Question 1: Is this at all possible to carry on with Database First approach with EF7 and consume database objects like above?
I installed EntityFramework 6.1.3 to my package by the following NuGet command:
install-package EntityFramework
which adds version 6.1.3 to my project but immediately starts showing me error message (please see the screenshot below). I have no clue about how to resolve this.
I have another test project where in project.json I can see two entries like following:
"EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final",
"EntityFramework.MicrosoftSqlServer.Design": "7.0.0-rc1-final",
However, when I am searching in Nu-Get package manager, I don;t see this version! Only 6.1.3 is coming up.
My main objective is to consume already written Stored Procedures and Views from an existing database.
1) I do not want to use ADO.Net, rather I would like to use ORM using EntityFramework
2) If EntityFramework 6.1.3 has the ability to call Stored Procs and Views from already existing database, how I can resolve the error (screenshot)?
What is the best practice to achieve this?
I hope that I correctly understand your problem. You have existing STORED PROCEDURE, for example dbo.spGetSomeData, in the database, which returns the list of some items with some fields and you need to provide the data from Web API method.
The implementation could be about the following. You can define an empty DbContext like:
public class MyDbContext : DbContext
{
}
and to define appsettings.json with the connection string to the database
{
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=MyDb;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
}
You should use Microsoft.Extensions.DependencyInjection to add MyDbContext to the
public class Startup
{
// property for holding configuration
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
.AddEnvironmentVariables();
// save the configuration in Configuration property
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<MyDbContext>(options => {
options.UseSqlServer(Configuration["ConnectionString"]);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
}
}
Now you can implement your WebApi action as the following:
[Route("api/[controller]")]
public class MyController : Controller
{
public MyDbContext _context { get; set; }
public MyController([FromServices] MyDbContext context)
{
_context = context;
}
[HttpGet]
public async IEnumerable<object> Get()
{
var returnObject = new List<dynamic>();
using (var cmd = _context.Database.GetDbConnection().CreateCommand()) {
cmd.CommandText = "exec dbo.spGetSomeData";
cmd.CommandType = CommandType.StoredProcedure;
// set some parameters of the stored procedure
cmd.Parameters.Add(new SqlParameter("#someParam",
SqlDbType.TinyInt) { Value = 1 });
if (cmd.Connection.State != ConnectionState.Open)
cmd.Connection.Open();
var retObject = new List<dynamic>();
using (var dataReader = await cmd.ExecuteReaderAsync())
{
while (await dataReader.ReadAsync())
{
var dataRow = new ExpandoObject() as IDictionary<string, object>;
for (var iFiled = 0; iFiled < dataReader.FieldCount; iFiled++) {
// one can modify the next line to
// if (dataReader.IsDBNull(iFiled))
// dataRow.Add(dataReader.GetName(iFiled), dataReader[iFiled]);
// if one want don't fill the property for NULL
// returned from the database
dataRow.Add(
dataReader.GetName(iFiled),
dataReader.IsDBNull(iFiled) ? null : dataReader[iFiled] // use null instead of {}
);
}
retObject.Add((ExpandoObject)dataRow);
}
}
return retObject;
}
}
}
The above code just execute using exec dbo.spGetSomeData and use dataRader to read all results and save there in dynamic object. If you would make $.ajax call from api/My you will get the data returned from dbo.spGetSomeData, which you can directly use in JavaScript code. The above code is very transparent. The names of the fields from the dataset returned by dbo.spGetSomeData will be the names of the properties in the JavaScript code. You don't need to manage any entity classes in your C# code in any way. Your C# code have no names of fields returned from the stored procedure. Thus if you would extend/change the code of dbo.spGetSomeData (rename some fields, add new fields) you will need to adjust only your JavaScript code, but no C# code.
DbContext has a Database property, which holds a connection to the database that you can do whatever you want with:
context.Database.SqlQuery<Foo>("exec [dbo].[GetFoo] #Bar = {0}", bar);
However, rather than doing this in your Web Api actions, I would suggest either adding a method to your context or to whatever service/repository that interacts with your context. Then just call this method in your action. Ideally, you want to keep all your SQL-stuff in one place.
Just as the above answer, you could simply use the FromSQL() instead of SqlQuery<>().
context.Set().FromSql("[dbo].[GetFoo] #Bar = {0}", 45);
Using MySQL connector and Entity Framework core 2.0
My issue was that I was getting an exception like fx. Ex.Message = "The required column 'body' was not present in the results of a 'FromSql' operation.".
So, in order to fetch rows via a stored procedure in this manner, you must return all columns for that entity type which the DBSet is associated with, even if you don't need all the data for this specific call.
var result = _context.DBSetName.FromSql($"call storedProcedureName()").ToList();
OR with parameters
var result = _context.DBSetName.FromSql($"call storedProcedureName({optionalParam1})").ToList();
For Database first approach , you have to use Scaffold-DbContext command
Install Nuget packages Microsoft.EntityFrameworkCore.Tools and Microsoft.EntityFrameworkCore.SqlServer.Design
Scaffold-DbContext "Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
but that will not get your stored procedures. It is still in the works,tracking issue #245
But, To execute the stored procedures, use FromSql method which executes RAW SQL queries
e.g.
var products= context.Products
.FromSql("EXECUTE dbo.GetProducts")
.ToList();
To use with parameters
var productCategory= "Electronics";
var product = context.Products
.FromSql("EXECUTE dbo.GetProductByCategory {0}", productCategory)
.ToList();
or
var productCategory= new SqlParameter("productCategory", "Electronics");
var product = context.Product
.FromSql("EXECUTE dbo.GetProductByName #productCategory", productCategory)
.ToList();
There are certain limitations to execute RAW SQL queries or stored procedures.You can’t use it for INSERT/UPDATE/DELETE. if you want to execute INSERT, UPDATE, DELETE queries, use the ExecuteSqlCommand
var categoryName = "Electronics";
dataContext.Database
           .ExecuteSqlCommand("dbo.InsertCategory #p0", categoryName);

Categories