Write a test for SQL Server using EF in C# - c#

I'm trying to test the below function using Nunit:
public static void Create(Contact contact)
{
using (var db = new PhonebookContext())
{
db.Database.EnsureCreated();
try
{
db.Contacts.Add(contact);
db.SaveChanges();
Console.WriteLine($"Successfully added {contact.Name}!");
}
catch
{
Console.WriteLine(Helpers.CreateErrorMessage, contact.Name);
}
}
}
This is my current test:
[Test]
public void Create_Contact_DbRowIsAdded()
{
var contact = new Contact { Name = "Abhinav", PhoneNumber = 1234567890, };
SqlAccess.Create(contact);
Assert.Equals(contact, SqlAccess.GetLastContact());
}
The test fails with the following error:
System.InvalidOperationException : No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.
If it helps, here's the code for the dbContext (which I believe the test has no access to):
public class PhonebookContext : DbContext
{
public DbSet<Contact> Contacts => Set<Contact>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
try
{
optionsBuilder.UseSqlServer(System.Configuration
.ConfigurationManager
.ConnectionStrings["SQLServer"]
.ConnectionString);
}
catch (Exception ex)
{
Console.WriteLine("An unknown error occurred while creating the database. Please make sure SQL server is running.");
Console.WriteLine(ex.Message);
}
}
}
The function works perfectly, except when it is put through the test.
Am I missing something?

That's because you do not provide in your's PhonebookContext the db configuration for Contact class (which is db entry i guess).
So in order to fix that you should do something simmilar:
internal class ContactDbConfiguration : IEntityTypeConfiguration<Contact>
{
public void Configure(EntityTypeBuilder<TaskDto> builder)
{
// register your properties of contact class here for example:
builder.HasKey(t => t.Id);
}
}
And please add on model creating method in your PhonebookContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new ContactDbConfiguration());
base.OnModelCreating(modelBuilder);
}
Finally your dbContext should be like this:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// your code here
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new ContactDbConfiguration());
base.OnModelCreating(modelBuilder);
}
public DbSet<Contact> Contacts { get; set; }

Related

How to unlock the Sqlite database file in EntityFramework.Core

I have a project that uses Microsoft.EntityFrameworkCore.Sqlite 6.0.5 on net6.0. I am expecting the Sqlite database file to be unlocked after the DbContext has been disposed.
However, the behaviour I am observing is that the Sqlite database remains locked after the DbContext has been disposed and finalised. There is a project that reproduces the behaviour here.
How can I unlock the database file?
My DbContext looks like this:
public class MyContext : DbContext
{
~MyContext()
{
Console.WriteLine("Finaliser was called.");
}
public override void Dispose()
{
base.Dispose();
Console.WriteLine("Dispose was called.");
}
public static readonly string DbFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "_temp.db");
public DbSet<Foo> Summaries { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Foo>().HasKey(nameof(Foo.Id));
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlite($"Data Source={DbFile}");
}
}
I am using it like this:
public static void AddItem()
{
using var ctx = new MyContext();
ctx.Database.EnsureCreated();
ctx.Summaries.Add(new Foo {Bar = "Foo"});
ctx.SaveChanges();
}
ClearAllPools() or specify no pooling in connection string (Pooling=false)
https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/connection-strings#pooling

Xunit new in memory database DBContext Class has live data

I am trying to unit test with the EF Core in-memory database like so:
namespace ContosoTests
{
public class TrendServiceTests
{
private static Microsoft.EntityFrameworkCore.DbContextOptions<TestContext> options = new
Microsoft.EntityFrameworkCore.DbContextOptionsBuilder<TestContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
private static TestContext _context = new TestContext(options);
private readonly TrendService trendService = new TrendService(_context);
private void SeedInMemoryDb()
{
if (!_context.TrendHistories.Any())
{
_context.TrendHistories.Add(new TrendHistory { IsActive = true, Quarter = E_Quarter.Q1,
TrendYear = 2020 });
}
if (!_context.Controls.Any())
{
_context.Controls.Add(new Control { IsActive = true});
_context.Controls.Add(new Control { IsActive = true});
_context.Controls.Add(new Control { IsActive = false});
}
_context.SaveChanges();
}
}
My TestContext inherits from my live context class to avoid the error:
Services for database providers 'Microsoft.EntityFrameworkCore.InMemory', 'Microsoft.EntityFrameworkCore.SqlServer' have been registered in the service provider. Only a single database provider can be registered in a service provider
So my TestContext just looks like:
public class TestContext : ContosoContext
{
public TestContext(DbContextOptions<TestContext> options)
{
}
public TestContext()
{
}
}
When I use the new instance of TestContext (_context) in my test class, all the live data for ContosoContext is there.
I was expecting it to be a new instance of the context class with no data so that I could test my DAL code with controlled data. But this is not the case.
I am fairly new to unit testing so any help is much appreciated.
Edit:
Here are the relevant parts of my contoso context class
public class ContosoContext: IdentityDbContext<ContosoApplicationUser>
{
public ContosoContext(DbContextOptions<ContosoContext> options) : base
(options)
{
}
public ContosoContext()
{
}
//all my DBSets here
protected override void OnConfiguring(DbContextOptionsBuilder
optionsBuilder)
{
optionsBuilder.UseSqlServer("MyConnectionString");
}
}
So the issue is that my onconfiguring method handles the connection string directly??
To fix the error you need to remove optionsBuilder.UseSqlServer("MyConnectionString"); in OnConfiguring method to avoid registering multiple database providers, change it to this:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if(!optionsBuilder.IsConfigured)
optionsBuilder.UseSqlServer("MyConnectionString");
}

Migration with dotnet ef error: Unable to create an object of type 'MyClass'

I'm trying to have a migration using dotnet ef migrations add MyMigration, but shell returns an error: Unable to create an object of type 'AuthDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728.
This is my AuthDbContext.cs file:
using Auth.Data.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace Auth.Data
{
public class AuthDbContext : IdentityDbContext<User>
{
private readonly string connStr;
//public DbSet<User> Users { get; set; }
public AuthDbContext(DbContextOptions<AuthDbContext> options) : base(options) //string connStr)
{
//this.connStr = connStr;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// string connStr = "";
// connStr = "";
// if (!optionsBuilder.IsConfigured)
// {
// optionsBuilder
// .EnableSensitiveDataLogging(true)
// .UseSqlServer(connStr);
// }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("auth");
//modelBuilder.Entity<User>().HasKey(p => new { p.User_id });
base.OnModelCreating(modelBuilder);
}
}
}
Can somebody help me to understand? Thanks.
You forgot to add this line
base.OnConfiguring(optionsBuilder);
EF Core can't finish context instantiating.

How do I connect custom logging to my ef6 migrations?

I have a DbMigrationsConfiguration that looks like this:
internal sealed class Configuration : DbMigrationsConfiguration<DatabaseProject.DB>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
AutomaticMigrationDataLossAllowed = false;
}
}
Elsewhere, in my DbContext class I have:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DB, DbProject.Migrations.Configuration>(useSuppliedContext: true));
// and so on...
And I want to use the MigrationsLogger to record some information when a migration is applied. So I set up a simple class like this using serilog:
public class EfLogger : MigrationsLogger
{
public override void Info(string message)
{
Log.Logger.Information("Machine {name} reported EF Migration Message: {message}", Environment.MachineName,
message);
}
public override void Warning(string message)
{
Log.Logger.Warning("Machine {name} reported EF Migration Warning: {message}", Environment.MachineName,
message);
}
public override void Verbose(string message)
{
Log.Logger.Verbose("Machine {name} reported EF Migration verbose message: {message}", Environment.MachineName,
message);
}
}
So how do I change my configuration to use the new logger? I can't find any examples or documentation on this anywhere.
I looked into how this works and I think you won't be able to use MigrateDatabaseToLatestVersion directly. You should be able to override it though. If you look into the actual logic there it's actually quite simple:
https://github.com/dotnet/ef6/blob/master/src/EntityFramework/MigrateDatabaseToLatestVersion%60.cs
public virtual void InitializeDatabase(TContext context)
{
Check.NotNull(context, "context");
var migrator = new DbMigrator(_config, _useSuppliedContext ? context : null);
migrator.Update();
}
To add your logging to it, create a new MigrateDatabaseToLatestVersionWithLogging implementation that inherits from the standard MigrateDatabaseToLatestVersion, then override the method and introduce a logging decorator before calling update. Something like this (untested):
https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.migrations.infrastructure.migratorloggingdecorator?view=entity-framework-6.2.0
public override void InitializeDatabase(TContext context)
{
_ = context ?? throw new ArgumentNullException(nameof(context));
var migrator = new MigratorLoggingDecorator(
new DbMigrator(_config, _useSuppliedContext ? context : null),
new EfLogger());
migrator.Update();
}

Automapper, Mapping config isn't persisting

So the problem.
Ive added
AutoMapperConfig.Configure();
to the application_Start in global.asax
it runs the code
Mapper.Initialize(x =>
{
x.AddProfile<DomainToViewModelMappingProfile>();
x.AddProfile<ViewModelToDomainMappingProfile>();
});
Mapper.AssertConfigurationIsValid();
which runs
public class DomainToViewModelMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<DBO.User, ViewModels.UserViewModel>();
}
}
and
public class ViewModelToDomainMappingProfile : Profile
{
protected override void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ViewModels.UserViewModel, DBO.User>();
});
}
}
and everything compiles and runs fine.
but in the controller:
UserViewModel model = new UserViewModel();
User user = userService.GetUser(2);
model = Mapper.Map<User, UserViewModel>(user); //this line fails as mapping doesnt exist
return View();
but if i add the mapping config in the controller method
Mapper.CreateMap<ViewModels.UserViewModel,DBO.User>();
UserViewModel model = new UserViewModel();
User user = userService.GetUser(2);
model = Mapper.Map<User, UserViewModel>(user); //Works great
return View();
it works fine.
ignore the different syntax with automapper. Ive tried the deprecated and new way of mapping and both fail.
Thanks
The problem is that you're calling Initialize method inside your Profile which leads to overriding your already existed mappings:
public class ViewModelToDomainMappingProfile : Profile
{
protected override void Configure()
{
// you should not to call Initialize method inside your profiles.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ViewModels.UserViewModel, DBO.User>();
});
}
}
And here, you have two ways:
Way #1 (using the static API - deprecated)
public class DomainToViewModelMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<DBO.User, ViewModels.UserViewModel>();
}
}
public class ViewModelToDomainMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<ViewModels.UserViewModel, DBO.User>();
}
}
// initialize your mapper by provided profiles
Mapper.Initialize(x =>
{
x.AddProfile<DomainToViewModelMappingProfile>();
x.AddProfile<ViewModelToDomainMappingProfile>();
});
Mapper.AssertConfigurationIsValid();
Way #2 (using the instance API)
// in this case just call CreateMap from Profile class - its the same as CreateMap on `cfg`
public class DomainToViewModelMappingProfile : Profile
{
public DomainToViewModelMappingProfile()
{
CreateMap<DBO.User, ViewModels.UserViewModel>();
}
}
public class ViewModelToDomainMappingProfile : Profile
{
public ViewModelToDomainMappingProfile()
{
CreateMap<ViewModels.UserViewModel, DBO.User>();
}
}
// initialize you mapper config
var config = new MapperConfiguration(cfg => {
cfg.AddProfile<DomainToViewModelMappingProfile>();
cfg.AddProfile<ViewModelToDomainMappingProfile>();
});
// and then use it
var mapper = config.CreateMapper();
// or
var mapper = new Mapper(config);
var dest = mapper.Map<Source, Dest>(new Source());
In the Way #2 you will need to store you mapper configuration somewhere (static field, DI), and then use it inside your controller. I would like to suggest to inject the Mapper instance into your controller (e.g. using some DI container).
Hope it will help.
try to override "ProfileName" :
public class DomainToViewModelMappingProfile : Profile
{
public override string ProfileName
{
get
{
return "DomainToViewModelMappingProfile";
}
}
protected override void Configure()
{
Mapper.CreateMap<DBO.User, ViewModels.UserViewModel>();
}
}
OK, thanks to MaKCbIMKo for pointing me in the right direction
As described i dont have to initialize as its already being done in the automapperconfig.
the syntax once in the profile is simply.
CreateMap<ViewModels.UserViewModel, DBO.User>();

Categories

Resources