Database.CompatibleWithModel n EF7 ( detecting the need to run migrations) - entity-framework-core

In EF6 I was able to check whether the database needed upgrading and get an OK from the (power) user
I made use of
dbContext.Database.CompatibleWithModel and db.RunMigrations()
I can't find these methods in EF7. Can I still do this in EF7?
So far I have the following
namespace Console4Migration
{
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder();
builder.AddJsonFile("appsettings.json");
var config = builder.Build();
var connectionString = config.GetConnectionString("ApplicationDatabase");
var optionsBuilder = new DbContextOptionsBuilder<ApiDbContext>();
optionsBuilder.UseSqlServer(connectionString);
var options = new DbContextOptions<ApiDbContext>();
var db = new ApiDbContext(options);
var numUsers = db.Users.Count();
Console.WriteLine("finished opening the database");
}
}
}
and
public class ApiDbContext : IdentityDbContext<ApplicationUser>
{
public ApiDbContext(DbContextOptions<ApiDbContext> options)
: base(options)
{
}
}
public class ApplicationUser : IdentityUser
{
}

I was able to create a partial solution as shown here that detects the need to run migrations and optionally runs them.
However it does not check for whether the model is compatible which I would still like to do.
using System;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using MyApi.Entities;
namespace Console4Migration
{
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder();
builder.AddJsonFile("appsettings.json");
var config = builder.Build();
var connectionString = config.GetConnectionString("MyDatabase");
var optionsBuilder = new DbContextOptionsBuilder<ApiDbContext>();
optionsBuilder.UseSqlServer(connectionString);
var options = optionsBuilder.Options;
var db = new ApiDbContext(options);
if (EFFunctions.HasOutstandingMigrations(db))
{
var sb = new StringBuilder();
sb.AppendLine("Upgrades are needed. Enter Y to upgrade");
sb.AppendLine(EFFunctions.GetUpgradesDescription(db));
Console.WriteLine(sb);
var answer = Console.ReadKey();
if (answer.Key != ConsoleKey.Y)
{
Console.WriteLine("No upgrade performed");
Console.ReadKey();
return;
}
db.Database.Migrate();
Console.WriteLine("Migration performed");
Console.ReadKey();
return;
}
Console.WriteLine("There are no migrations outstanding");
Console.ReadKey();
}
}
}
and
using System.Linq;
using System.Text;
using Microsoft.EntityFrameworkCore;
public static class EFFunctions
{
public static string GetUpgradesDescription(DbContext db)
{
var migrations = db.Database.GetPendingMigrations();
var sb = new StringBuilder();
var enumerable = migrations as string[] ?? migrations.ToArray();
if (!enumerable.Any()) return sb.ToString();
foreach (var migration in enumerable)
{
sb.AppendLine(migration);
}
return sb.ToString();
}
public static bool HasOutstandingMigrations(DbContext db)
{
return GetUpgradesDescription(db).Length == 0;
}
}

Related

How to write NUnit test for dependency injection service .net core

I have a service class with some injected services. It's dealing with my Azure storage requests. I need to write NUnit tests for that class.
I'm new to NUnit and I'm struggling with making the object of that my AzureService.cs
Below AzureService.cs. I have used some injected services
using System;
using System.Linq;
using System.Threading.Tasks;
using JohnMorris.Plugin.Image.Upload.Azure.Interfaces;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Nop.Core.Caching;
using Nop.Core.Configuration;
using Nop.Core.Domain.Media;
using Nop.Services.Logging;
namespace JohnMorris.Plugin.Image.Upload.Azure.Services
{
public class AzureService : IAzureService
{
#region Constants
private const string THUMB_EXISTS_KEY = "Nop.azure.thumb.exists-{0}";
private const string THUMBS_PATTERN_KEY = "Nop.azure.thumb";
#endregion
#region Fields
private readonly ILogger _logger;
private static CloudBlobContainer _container;
private readonly IStaticCacheManager _cacheManager;
private readonly MediaSettings _mediaSettings;
private readonly NopConfig _config;
#endregion
#region
public AzureService(IStaticCacheManager cacheManager, MediaSettings mediaSettings, NopConfig config, ILogger logger)
{
this._cacheManager = cacheManager;
this._mediaSettings = mediaSettings;
this._config = config;
this._logger = logger;
}
#endregion
#region Utilities
public string GetAzureStorageUrl()
{
return $"{_config.AzureBlobStorageEndPoint}{_config.AzureBlobStorageContainerName}";
}
public virtual async Task DeleteFileAsync(string prefix)
{
try
{
BlobContinuationToken continuationToken = null;
do
{
var resultSegment = await _container.ListBlobsSegmentedAsync(prefix, true, BlobListingDetails.All, null, continuationToken, null, null);
await Task.WhenAll(resultSegment.Results.Select(blobItem => ((CloudBlockBlob)blobItem).DeleteAsync()));
//get the continuation token.
continuationToken = resultSegment.ContinuationToken;
}
while (continuationToken != null);
_cacheManager.RemoveByPrefix(THUMBS_PATTERN_KEY);
}
catch (Exception e)
{
_logger.Error($"Azure file delete error", e);
}
}
public virtual async Task<bool> CheckFileExistsAsync(string filePath)
{
try
{
var key = string.Format(THUMB_EXISTS_KEY, filePath);
return await _cacheManager.Get(key, async () =>
{
//GetBlockBlobReference doesn't need to be async since it doesn't contact the server yet
var blockBlob = _container.GetBlockBlobReference(filePath);
return await blockBlob.ExistsAsync();
});
}
catch { return false; }
}
public virtual async Task SaveFileAsync(string filePath, string mimeType, byte[] binary)
{
try
{
var blockBlob = _container.GetBlockBlobReference(filePath);
if (!string.IsNullOrEmpty(mimeType))
blockBlob.Properties.ContentType = mimeType;
if (!string.IsNullOrEmpty(_mediaSettings.AzureCacheControlHeader))
blockBlob.Properties.CacheControl = _mediaSettings.AzureCacheControlHeader;
await blockBlob.UploadFromByteArrayAsync(binary, 0, binary.Length);
_cacheManager.RemoveByPrefix(THUMBS_PATTERN_KEY);
}
catch (Exception e)
{
_logger.Error($"Azure file upload error", e);
}
}
public virtual byte[] LoadFileFromAzure(string filePath)
{
try
{
var blob = _container.GetBlockBlobReference(filePath);
if (blob.ExistsAsync().GetAwaiter().GetResult())
{
blob.FetchAttributesAsync().GetAwaiter().GetResult();
var bytes = new byte[blob.Properties.Length];
blob.DownloadToByteArrayAsync(bytes, 0).GetAwaiter().GetResult();
return bytes;
}
}
catch (Exception)
{
}
return new byte[0];
}
#endregion
}
}
This is my test class below, I need to create new AzureService(); from my service class. But in my AzureService constructor, I'm injecting some service
using JohnMorris.Plugin.Image.Upload.Azure.Services;
using Nop.Core.Caching;
using Nop.Core.Domain.Media;
using Nop.Services.Tests;
using NUnit.Framework;
namespace JohnMorris.Plugin.Image.Upload.Azure.Test
{
public class AzureServiceTest
{
private AzureService _azureService;
[SetUp]
public void Setup()
{
_azureService = new AzureService( cacheManager, mediaSettings, config, logger);
}
[Test]
public void App_settings_has_azure_connection_details()
{
var url= _azureService.GetAzureStorageUrl();
Assert.IsNotNull(url);
Assert.IsNotEmpty(url);
}
[Test]
public void Check_File_Exists_Async_test(){
//To Do
}
[Test]
public void Save_File_Async_Test()(){
//To Do
}
[Test]
public void Load_File_From_Azure_Test(){
//To Do
}
}
}
Question is, what exactly do you want to test? If you want to test if NopConfig is properly reading values from AppSettings, then you do not have to test AzureService at all.
If you want to test that GetAzureStorageUrl method is working correctly, then you should mock your NopConfig dependency and focus on testing only AzureService methods like this:
using Moq;
using Nop.Core.Configuration;
using NUnit.Framework;
namespace NopTest
{
public class AzureService
{
private readonly NopConfig _config;
public AzureService(NopConfig config)
{
_config = config;
}
public string GetAzureStorageUrl()
{
return $"{_config.AzureBlobStorageEndPoint}{_config.AzureBlobStorageContainerName}";
}
}
[TestFixture]
public class NopTest
{
[Test]
public void GetStorageUrlTest()
{
Mock<NopConfig> nopConfigMock = new Mock<NopConfig>();
nopConfigMock.Setup(x => x.AzureBlobStorageEndPoint).Returns("https://www.example.com/");
nopConfigMock.Setup(x => x.AzureBlobStorageContainerName).Returns("containername");
AzureService azureService = new AzureService(nopConfigMock.Object);
string azureStorageUrl = azureService.GetAzureStorageUrl();
Assert.AreEqual("https://www.example.com/containername", azureStorageUrl);
}
}
}

EF migration control logging SQL

I have some code for creating a database and applying migrations:
public static (Server Server, string ConnectionString) InitializeServerAndDatabase(string databaseName, string defaultConnectionConnectionString, DbMigrationsConfiguration migrationsConfiguration)
{
var sqlConnection = new SqlConnection(defaultConnectionConnectionString);
var serverConnection = new ServerConnection(sqlConnection);
var server = new Server(serverConnection);
var database = new Database(server, databaseName);
database.Create();
// Build database with migrations and seed data
var sqlConnectionStringBuilder = new SqlConnectionStringBuilder(defaultConnectionConnectionString);
sqlConnectionStringBuilder.InitialCatalog = databaseName;
var connectionString = sqlConnectionStringBuilder.ToString();
migrationsConfiguration.TargetDatabase = new DbConnectionInfo(connectionString, "System.Data.SqlClient");
var migrator = new DbMigrator(migrationsConfiguration);
var logger = new MigratorLoggingDecorator(migrator, new MinimalMigrationLogger());
logger.Update();
// Set environment variable so the DbContext will establish a connection to the right database
Environment.SetEnvironmentVariable("DefaultConnection", connectionString);
return (server, connectionString);
}
Since running migrations logged a lot more SQL than I wanted, I attempted minimize the logging by writing MinimalMigrationsLogger, which is used in the method above:
public class MinimalMigrationLogger : MigrationsLogger
{
public override void Info(string message)
{
// Ignore it; there's too much of it clogging up CI
}
public override void Verbose(string message)
{
// The SQL text and other info comes here
// Ignore it; there's too much of it clogging up CI
}
public override void Warning(string message)
{
Console.WriteLine(message);
}
}
However, I'm still getting SQL in my logs for creating the table and the seed data. Why does my setup not avoid this? How can I change it so that it will not log table creation and seed data SQL?
Try the following full example and see what's the difference with yours.
All you have to do ,is create the migrations on the same project
using Microsoft.SqlServer.Management.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
namespace EntityFramework.ConsoleExample
{
using Microsoft.SqlServer.Management.Smo;
class Program
{
public static string ServerName = "localhost";
public static string DbName = "EntityFramework.ConsoleExample.MyContenxt";
public static string ConnectionString = #"Server=" + ServerName + "; Database=" + DbName + #";Integrated Security = True;MultipleActiveResultSets=true";
static void Main(string[] args)
{
var conf = new EntityFramework.Console.Migrations.Configuration
{
MigrationsAssembly = typeof(Customer).Assembly
};
InitializeServerAndDatabase(DbName, ServerName, Program.ConnectionString, conf);
System.Console.ReadLine();
}
public static void InitializeServerAndDatabase(string databaseName, string serverName, string defaultConnectionConnectionString, DbMigrationsConfiguration migrationsConfiguration)
{
ServerConnection sqlConnection = new ServerConnection(serverName);
Server sqlServer = new Server(sqlConnection);
Database newDB = new Database(sqlServer, databaseName);
newDB.Create();
migrationsConfiguration.TargetDatabase = new DbConnectionInfo(defaultConnectionConnectionString, "System.Data.SqlClient");
var migrator = new DbMigrator(migrationsConfiguration);
var logger = new MigratorLoggingDecorator(migrator, new MinimalMigrationLogger());
logger.Update();
}
}
public class Customer
{
public int CustomerId { get; set; }
public int Age { get; set; }
public string Name { get; set; }
public string Name2 { get; set; }
public string Name3 { get; set; }
}
public class MyContenxt : DbContext
{
public DbSet<Customer> Customers { get; set; }
}
public class MinimalMigrationLogger : MigrationsLogger
{
public override void Info(string message)
{
System.Console.WriteLine("Info::::" + message);
}
public override void Verbose(string message)
{
//System.Console.WriteLine("Verbose::::" + message);
}
public override void Warning(string message)
{
System.Console.WriteLine("Warning::::" + message);
}
}
}

Entity Framework Core 1.1 In Memory Database fails adding new entities

I am using the following code in a unit test for the test setup:
var simpleEntity = new SimpleEntity();
var complexEntity = new ComplexEntity
{
JoinEntity1List = new List<JoinEntity1>
{
new JoinEntity1
{
JoinEntity2List = new List<JoinEntity2>
{
new JoinEntity2
{
SimpleEntity = simpleEntity
}
}
}
}
};
var anotherEntity = new AnotherEntity
{
ComplexEntity = complexEntity1
};
using (var context = databaseFixture.GetContext())
{
context.Add(anotherEntity);
await context.SaveChangesAsync();
}
When SaveChangesAsync is reached EF throws an ArgumentException with the following message:
An item with the same key has already been added. Key: 1
I'm using a fixture as well for the unit test class which populates the database with objects of the same types, though for this test I want this particular setup so I want to add these new entities to the in memory database. I've tried adding the entities on the DbSet (not the DbContext) and adding all three entities separatly to no avail. I can however add "simpleEntity" separately (because it is not added in the fixture) but EF complains as soon as I try to add "complexEntity" or "anotherEntity".
It seems like EF in memory database cannot handle several Add's over different instances of the context. Is there any workaround for this or am I doing something wrong in my setup?
The databaseFixture in this case is an instance of this class:
namespace Test.Shared.Fixture
{
using Data.Access;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
public class InMemoryDatabaseFixture : IDatabaseFixture
{
private readonly DbContextOptions<MyContext> contextOptions;
public InMemoryDatabaseFixture()
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
var builder = new DbContextOptionsBuilder<MyContext>();
builder.UseInMemoryDatabase()
.UseInternalServiceProvider(serviceProvider);
contextOptions = builder.Options;
}
public MyContext GetContext()
{
return new MyContext(contextOptions);
}
}
}
You can solve this problem by using Collection Fixtures so you can share this fixture across several test classes. This way you don't build you context several times and thus you won't get this exception:
Some information about collection Fixture
My own example:
[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{ }
[Collection("Database collection")]
public class GetCitiesCmdHandlerTests : IClassFixture<MapperFixture>
{
private readonly TecCoreDbContext _context;
private readonly IMapper _mapper;
public GetCitiesCmdHandlerTests(DatabaseFixture dbFixture, MapperFixture mapFixture)
{
_context = dbFixture.Context;
_mapper = mapFixture.Mapper;
}
[Theory]
[MemberData(nameof(HandleTestData))]
public async void Handle_ShouldReturnCountries_AccordingToRequest(
GetCitiesCommand command,
int expectedCount)
{
(...)
}
public static readonly IEnumerable<object[]> HandleTestData
= new List<object[]>
{
(...)
};
}
}
Good luck,
Seb

Autofac: cannot resolve dependency using factory after ContainerBuilder.Update()

My problem is that I want to use Func<> factory to resolve dependency. And in if I use ContainerBuilder Update() (I need it for mocking some services in integration tests), this factories still resolve outdated instances.
I created simple scenario to reproduce the problem:
class Program
{
static void Main(string[] args)
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<Test>().As<ITest>();
containerBuilder.RegisterType<Test1Factory>().As<ITestFactory>();
containerBuilder.RegisterType<TestConsumer>().AsSelf();
var container = containerBuilder.Build();
var tc1 = container.Resolve<TestConsumer>();
var cbupdater = new ContainerBuilder();
cbupdater.RegisterType<Test2>().As<ITest>();
cbupdater.RegisterType<Test2Factory>().As<ITestFactory>();
cbupdater.Update(container);
var tc2 = container.Resolve<TestConsumer>();
Console.ReadLine();
}
}
public interface ITest
{
int Id { get; set; }
}
public class Test : ITest
{
public Test()
{
Id = 1;
}
public int Id { get; set; }
}
public class Test2 : ITest
{
public Test2()
{
Id = 2;
}
public int Id { get; set; }
}
public interface ITestFactory
{
ITest Create();
}
public class Test1Factory : ITestFactory
{
public ITest Create()
{
return new Test();
}
}
public class Test2Factory : ITestFactory
{
public ITest Create()
{
return new Test2();
}
}
public class TestConsumer
{
public TestConsumer(Func<ITest> testFactory, ITest test, ITestFactory customFactory)
{
Console.WriteLine("factory: " + testFactory().Id);
Console.WriteLine("direct: " + test.Id);
Console.WriteLine("MyCustomFactory: " + customFactory.Create().Id);
Console.WriteLine("*************");
Console.WriteLine();
}
}
The output is:
factory: 1 direct: 1 MyCustomFactory: 1
factory: 1 direct: 2 MyCustomFactory: 2
Notice "factory: 1" in both cases.
Am I missing something or I have to create my cusom factory in this scenario?
P.S.
Autofac 3.5.2 or 4.0 beta 8-157
.net 4.5.1
That's by design unfortunately, the reasons, I don't know. Looking at the Autofac code gives you a better insight on how they register items with the same interface definition, in short, all registrations are maintained but the last registration wins (ref). Wait...that's not all, weirdly, for Fun<...>, you actually get them in order. You can easily test by changing the constructor of the TestConsumer class to:
public TestConsumer(Func<ITest> testFactory, IEnumerable<Func<ITest>> testFactories, IEnumerable<ITest> tests, ITest test, ITestFactory customFactory)
{
// ...
}
Note that you get all the Funcs and the ITest registration. You are simply lucky that resolving ITest directly resolves to Test2.
Now, having said all of the above, there is a way described here. You have to create a container without the registration you want to override, therefore:
/// <summary>
/// This has not been tested with all your requirements
/// </summary>
private static IContainer RemoveOldComponents(IContainer container)
{
var builder = new ContainerBuilder();
var components = container.ComponentRegistry.Registrations
.Where(cr => cr.Activator.LimitType != typeof(LifetimeScope))
.Where(cr => cr.Activator.LimitType != typeof(Func<ITest>));
foreach (var c in components)
{
builder.RegisterComponent(c);
}
foreach (var source in container.ComponentRegistry.Sources)
{
builder.RegisterSource(source);
}
return builder.Build();
}
And you can simply change your main method to the following:
static void Main(string[] args)
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<Test>().As<ITest>();
containerBuilder.RegisterType<Test1Factory>().As<ITestFactory>();
containerBuilder.RegisterType<TestConsumer>().AsSelf();
var container = containerBuilder.Build();
var tc1 = container.Resolve<TestConsumer>();
container = RemoveOldComponents(container);
var cbupdater = new ContainerBuilder();
cbupdater.RegisterType<Test2>().As<ITest>();
cbupdater.RegisterType<Test2Factory>().As<ITestFactory>();
cbupdater.Update(container);
var tc2 = container.Resolve<TestConsumer>();
Console.ReadLine();
}
PS: Wouldn't it be great to have a method which does the exact opposite of PreserveExistingDefaults()

Help with EF Code first connection string

Im trying to implement a UnitofWork pattern using this Scott Allen tutorial
My current SqlUnitOfWork is the folowing
public class SqlUnitOfWork : IUnitOfWork {
public SqlUnitOfWork() {
var connectionString =
ConfigurationManager
.ConnectionStrings[ConnectionStringName]
.ConnectionString;
_context = new ObjectContext(connectionString);
_context.ContextOptions.LazyLoadingEnabled = true;
}
public IRepository<PhysicalTest> PhysicalTests
{
get {
if (_physicalTests == null)
{
_physicalTests = new SqlRepository<PhysicalTest>(_context);
}
return _physicalTests;
}
}
public IRepository<EHR> EHRs
{
get
{
if (_EHRs == null)
{
_EHRs = new SqlRepository<EHR>(_context);
}
return _EHRs;
}
}
public void Commit() {
_context.SaveChanges();
}
SqlRepository<PhysicalTest> _physicalTests = null;
SqlRepository<EHR> _EHRs = null;
readonly ObjectContext _context;
const string ConnectionStringName = "default";
}
and my current connection string is the following
<add name="default" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;MultipleActiveResultSets=True; initial catalog=MyAppDB" providerName="System.Data.SqlClient" />
It's also worth pointing out that my controllers that are using controllers that were created with mvcscaffolding work fine but the unit of work (which for some reason needs a connectrion string as parameter instead of just using MyAppDBContext() instance) doesnt work.
The error I get when I try to invoke an action inside a controllr with the following code:
public class PhysicalTestsController : Controller
{
private IUnitOfWork unitOfWork;
private IRepository<EHR> ehrRepository;
public PhysicalTestsController(IUnitOfWork unit)
{
unitOfWork = unit;
ehrRepository = unitOfWork.EHRs;
}
public ActionResult Index(int ehrId, int? page)
{
EHR ehr = ehrRepository.FindById(ehrId);
if (ehr.UserName != User.Identity.Name)
return View("Invalid Owner");
const int pageSize = 5;
var physicaltests = ehr.PhysicalTests.OrderByDescending(test => test.CreationDate);
List<PhysicalTestListItem> physicalTestsVM = new List<PhysicalTestListItem>();
Mapper.Map(physicaltests, physicalTestsVM);
var paginatedTests = new PaginatedList<PhysicalTestListItem>(physicalTestsVM, page ?? 0, pageSize);
return View(paginatedTests);
}
}
is this one
I have changed my SqlRepository to:
public class SqlRepository<T> : IRepository<T>
where T : class, IEntity {
internal SummumnetDB context;
internal DbSet<T> _objectSet;
public SqlRepository(SummumnetDB context)
{
this.context = context;
this._objectSet = context.Set<T>();
}
..... rest of my methods here
}
and
my SqlUnitofWork to
public class SqlUnitOfWork : IUnitOfWork {
private SummumnetDB _context = new SummumnetDB();
public IRepository<PhysicalTest> PhysicalTests
{
get {
if (_physicalTests == null)
{
_physicalTests = new SqlRepository<PhysicalTest>(_context);
}
return _physicalTests;
}
}..... rest of code here
please correct me if this modifications are not appropriate or break one of these patterns
You are using ObjectContext not DbContext. ObjectContext uses EntityConnection and its System.Data.EntityClient provider. It's connection string has different format.