I am using Code First Migrations in my Web API (Mobile Service .NET backend) and whenever my model changes, all data is deleted from my database. This is what I am doing:
I add a new property to my model
I run "Add-Migration AddTestProperty" in the Package Manager console
The new migration is added to the Migrations folder (see code below)
public partial class AddTestProperty: DbMigration
{
public override void Up()
{
AddColumn("testdb.TestTable", "Test", c => c.String());
}
public override void Down()
{
DropColumn("testdb.TestTable", "Test");
}
}
My configuration looks like this:
public Configuration()
{
AutomaticMigrationsEnabled = false;
AutomaticMigrationDataLossAllowed = false;
}
Now if I run my webservice, the property is added to my TestTable but all data in all my tables is deleted. What am I doing wrong?
The reason my data was deleted from my database is this: If you use the service project downloaded from the Azure portal, you will find the following code in the App_Start/WebsApiConfig:
public static class WebApiConfig
{
public static void Register()
{
// Use this class to set configuration options for your mobile service
ConfigOptions options = new ConfigOptions();
// Use this class to set WebAPI configuration options
HttpConfiguration config = ServiceConfig.Initialize(new ConfigBuilder(options));
Database.SetInitializer(new todoInitializer());
}
}
public class todoInitializer : ClearDatabaseSchemaIfModelChanges<todoContext>
{
.....
To enable Code First Migrations on a Mobile Services .NET backend you need to replace the Database.SetInitializer(new todoInitializer()); with this:
//Database.SetInitializer(new todoInitializer());
var migrator = new DbMigrator(new Configuration());
migrator.Update();
This requires the following using statements:
using System.Data.Entity.Migrations;
using todoService.Migrations;
More information on how to enable Code First Migrations on a Mobile Service .NET backend.
Related
I need to dynamically creates controllers in a ASP.NET Core 6 MVC application.
I found some way to somewhat achieve this but not quite.
I'm able to dynamically add my controller but somehow it reflects only on the second request.
So here is what I do: first I initialize my console app as follows:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc.Infrastructure;
namespace DynamicControllerServer
{
internal class Program
{
static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
ApplicationPartManager partManager = builder.Services.AddMvc().PartManager;
// Store thePartManager in my Middleware to be able to add controlelr after initialization is done
MyMiddleware._partManager = partManager;
// Register controller change event
builder.Services.AddSingleton<IActionDescriptorChangeProvider>(MyActionDescriptorChangeProvider.Instance);
builder.Services.AddSingleton(MyActionDescriptorChangeProvider.Instance);
var app = builder.Build();
app.UseAuthorization();
app.MapControllers();
// Add Middleware which is responsible to cactn the request and dynamically add the missing controller
app.UseMiddleware<MyMiddleware>();
app.RunAsync();
Console.WriteLine("Server has been started successfully ...");
Console.ReadLine();
}
}
}
Then my middleware looks like this: it basically detects that there is the "dynamic" keyword in the url. If so, it will load the assembly containing the DynamicController:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using System;
using System.Reflection;
namespace DynamicControllerServer
{
public class MyMiddleware
{
public RequestDelegate _next { get; }
private string dllName = "DynamicController1.dll";
static public ApplicationPartManager _partManager = null;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext.Request.Path.HasValue)
{
var queryParams = httpContext.Request.Path.Value;
if(httpContext.Request.Path.Value.Contains("api/dynamic"))
{
// Dynamically load assembly
Assembly assembly = assembly = Assembly.LoadFrom(#"C:\Temp\" + dllName);
// Add controller to the application
AssemblyPart _part = new AssemblyPart(assembly);
_partManager.ApplicationParts.Add(_part);
// Notify change
MyActionDescriptorChangeProvider.Instance.HasChanged = true;
MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
}
}
await _next(httpContext); // calling next middleware
}
}
}
The ActionDescriptorChange provider looks like this:
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Primitives;
namespace DynamicControllerServer
{
public class MyActionDescriptorChangeProvider : IActionDescriptorChangeProvider
{
public static MyActionDescriptorChangeProvider Instance { get; } = new MyActionDescriptorChangeProvider();
public CancellationTokenSource TokenSource { get; private set; }
public bool HasChanged { get; set; }
public IChangeToken GetChangeToken()
{
TokenSource = new CancellationTokenSource();
return new CancellationChangeToken(TokenSource.Token);
}
}
}
Dynamic controller is in separate dll and is very simple:
using Microsoft.AspNetCore.Mvc;
namespace DotNotSelfHostedOwin
{
[Route("api/[controller]")]
[ApiController]
public class DynamicController : ControllerBase
{
public string[] Get()
{
return new string[] { "dynamic1", "dynamic1", DateTime.Now.ToString() };
}
}
}
Here are the packages used in that project:
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
This works "almost" fine ... when first request is made to:
https://localhost:5001/api/dynamic
then it goes in the middleware and load the assembly, but returns a 404 error.
Then second request will actually work as expected:
Second request returns the expected result:
I must doing it wrong and probably my middleware is executed too late in the flow to reflect the dynamic controller right away.
Question is: what should be the proper way to achieve this?
Second question I have is say now the external dll holding our dynamic controller is updated.
How can I reload that controller to get the new definition?
Any help would be appreciated
Thanks in advance
Nick
Here is the answer to my own question in case it can help somebody out there.
It seems building and loading the controller from the middleware will always end up with failure on the first call.
This makes sense since we are already in the http pipeline.
I end up doing same thing from outside the middleware.
Basically my application detect a change in the controller assembly, unload the original assembly and load the new one.
You cannot use the Default context since it will not allow reloading different dll for same assembly:
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); // Produce an exception on updates
To be able to reload new dll for same assembly, I’m loading each controller in its own assembly context. To do that you need to create your own class deriving from AssemblyLoadContext and managing assembly load:
public class MyOwnContext: AssemblyLoadContext
{
// You can find lots of example in the net
}
When you want to unload the assembly, you just unload the context:
MyOwnContextObj.Unload();
Now to add or remove the controller on the fly, you need to keep reference of the PartManager and the ApplicationPart.
To add controller
ApplicationPart part = new AssemblyPart(assembly);
_PartManager.ApplicationParts.Add(part);
To remove:
_PartManager.ApplicationParts.Remove(part);
On course once done, still use following piece of code to acknowledge the change:
MyActionDescriptorChangeProvider.Instance.HasChanged = true;
MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
That allow updating controller on the fly with no interruption of service.
Hope this helps people out there.
I have done a similar solution (used for managing a web app plugins) with some differences that may help you:
List all the external assemblies in a config file or appsettings.json so all the dll names and/or addresses are known at startup
Instead of registering controllers when they are called, register them at program.cs/start up :
//Foreah dllName from settings file
var assembly = Assembly.LoadFrom(#"Base address" + dllNameLoadedFromSettings);
var part = new AssemblyPart(assembly);
services.AddControllersWithViews()
.ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part));
// Any other configuration based on the usage you want
Second: I usually keep plugin dlls in the bin folder so when using IIS as soon as a dll file in bin is changed the upper-level app is automatically reset. So your second question would be solved too.
When setting up a MartenDB datastore in ASP.Net Core, you normally put code like this in your Startup.cs:
services.AddMarten(o =>
{
o.Connection(configuration.GetConnectionString("MyDatabase"));
o.AutoCreateSchemaObjects = AutoCreate.All;
o.Serializer(new JsonNetSerializer { EnumStorage = EnumStorage.AsString });
});
This allows you to then inject IDocumentSession and IDocumentStore into your various classes for working with that database.
Now what do you do if you have to connect to a second database? I looked at the ISessionFactory but it is not apparent that you can change the connection string from here. Do you need to manually create and register a new DocumentStore?
To answer my own question, I wound up creating a custom DocumentStore and ISessionFactory for each database I wanted to connect to, and then injecting the custom SessionFactory.
Here's the code (only showing one instance of each class for the sake of brevity. Just replace Db1 with Db2 for the second version of each class):
The custom DocumentStore:
public class Db1Store : DocumentStore
{
public Db1Store(StoreOptions options) : base(options)
{
}
}
The custom SessionFactory:
public class Db1SessionFactory : ISessionFactory
{
private readonly Db1Store store;
public Db1SessionFactory(Db1Store store)
{
this.store = store;
}
public IQuerySession QuerySession()
{
return store.QuerySession();
}
public IDocumentSession OpenSession()
{
return store.OpenSession();
}
}
The service registration (this replaces the services.AddMarten call):
services.AddSingleton(p =>
{
var options = new StoreOptions();
options.Connection(configuration.GetConnectionString("DB1"));
options.AutoCreateSchemaObjects = AutoCreate.All;
options.Serializer(new JsonNetSerializer { EnumStorage = EnumStorage.AsString });
return new Db1Store(options);
});
services.AddSingleton<Db1SessionFactory>();
Then you inject the Db1SessionFactory instance into your class, and run a query like this:
var result = await db1SessionFactory.QuerySession().Query<MyAwesomeTable>().ToListAsync();
Downsides:
I would prefer to inject the QuerySession or DocumentSession, but I can't see a way to do that without moving to Autofac or a similar DI Container that supports named instances.
I am not sure what downsides there will be creating these QuerySession/DocumentSessions in this manner. It may be a bad tradeoff.
I want my windows application to warn that there are migrations to run but to give the user the option to continue without running them.
At the moment I have code first projects with no migrations that access the database
However in the project where I do the database migrations I must either allow the migration to run or delete the migration and undo my changes to the model.
For the configuration I have
internal sealed class Configuration : DbMigrationsConfiguration<MyDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
}
For the migration check I have
using (var db = new MyDbContext())
{
var compatible = db.Database.CompatibleWithModel(false);
if (!compatible)
{
compatible= RunMigrations(db);
}
return compatible;
}
If I don't allow the migration then I get the model backing error when I try to access data
var n = db.mytable.count() // will error
If I comment out the Configuration class and code that calls it I can run the program without error.
The trick is to create a new dbContext inheriting from the usual
public class MigrationDbContext : MyDbContext
{
}
Then the configuration class should use the MigrationDContext
internal sealed class Configuration : DbMigrationsConfiguration<MigrationDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
}
and the migration check should use the MigrationDbContext
I'm using Entity Framework and I had to add some migrations. Now I'd like to update a customer's app and database.
I considered using update-database -script -SourceMigration migrationName command which doesn't work because I create a view in one of my migrations and it gives me the following exception in VS Console.
There is already an object named 'viewName' in the database.
It seems migrate.exe can be used too, but I'm not much familiar with its function and I prefer not to install anything on the customer's computer (because of limited access).
What's the best way to apply those migrations? I prefer to do it with a SQL script, but I can't get ride of that exception.
The migration code of the view:
public override void Up()
{
string script = #"CREATE VIEW [dbo].[viewName]
AS
SELECT *
FROM dbo.existingTable
WHERE (subquery)";
using (var db = new EFDbContext())
{
db.Database.ExecuteSqlCommand(script);
}
}
public override void Down()
{
var script = #"DROP VIEW [dbo].[viewName]";
using (var db = new EFDbContext())
{
db.Database.ExecuteSqlCommand(script);
}
}
The better way to add your views is using Sql inside Up and Down which ‘Up’ applies the changes to the target database and ‘Down’ reverts them:
public override void Up()
{
Sql("EXEC ('CREATE View [dbo].[viewName] AS --etc"
}
public override void Down()
{
Sql(#"IF EXISTS (SELECT
*
FROM sys.views
WHERE object_id = OBJECT_ID(N'dbo.viewName'))
DROP VIEW dbo.viewName)")
}
I've read and Googled everything on this, but can't seem to get it to work. I created a custom LifetimeManager for Unity in my MVC5 application based on these posts:
MVC3 Unity Framework and Per Session Lifetime Manager
This may be the issue I am experiencing
Here is my SessionLifetimeManager
public class SessionLifetimeManager : LifetimeManager
{
private string key = Guid.NewGuid().ToString();
public override object GetValue()
{
return HttpContext.Current.Session[key];
}
public override void RemoveValue()
{
HttpContext.Current.Session.Remove(key);
}
public override void SetValue(object newValue)
{
HttpContext.Current.Session[key] = newValue;
}
}
I only have a few types I'm playing with, here is the relevant registrations in UnityConfig.cs:
container.RegisterType<IEpiSession, EpiSession>(new SessionLifetimeManager(),
new InjectionConstructor(config.AppServerURI, config.PathToSysConfig));
container.RegisterType<IReportRepository, EpicorReportRepository>(new TransientLifetimeManager());
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
Note that the EpicorReportRepository has a dependency on IEpiSession via constructor injection.
public class EpicorReportRepository : IReportRepository
{
private IEpiSession session;
// DI constructor
public EpicorReportRepository(IEpiSession session) {
this.session = session;
}
// ...
}
My Problem: After the first user / session connects to the application, every new user / session after that seems to still be using the EpiSession object and credentials that the first user had create/injected for him. This seems to be a common pattern used on the interwebs, so I'm wondering what I am missing.
How did you test that IEpiSession is the same in different Sessions?
Try to open you application from different browsers. If you open several tabs in the same browser then the same session is used.
I checked your code and it works for me.
There is the only one difference in SetResolver():
DependencyResolver.SetResolver(
type => container.Resolve(type),
types => container.ResolveAll(types));
The full registration code is the following:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
...
var container = new UnityContainer();
container.RegisterType<IEpiSession, EpiSession>(
new SessionLifetimeManager(),
new InjectionConstructor("config.AppServerURI", "config.PathToSysConfig"));
container.RegisterType<IReportRepository, EpicorReportRepository>(new TransientLifetimeManager());
DependencyResolver.SetResolver(
type => container.Resolve(type),
types => container.ResolveAll(types));
}
}