Sto creando un'applicazione su Entity Framework Core e desidero, in una certa misura, applicare una migrazione in fase di runtime.
Il mio approccio è quello di avere il modello di database corrente in memoria e creare un nuovo modello, quindi calcolare la differenza tra i due modelli usando IMigrationsModelDiffer.GetDifferences().
Da lì, invece di stampare le differenze in una classe di Migration
, voglio creare direttamente le MigrationCommand
e applicare questi comandi al mio database.
Quanto sopra sembra abbastanza semplice ma sto avendo un sacco di problemi con il sistema di iniezione delle dipendenze.
Questo è il codice che ho adesso:
static DbContextOptions GetOptions(IModel model)
{
var builder = new DbContextOptionsBuilder();
builder
.UseSqlServer(connStr)
.UseModel(model);
return builder.Options;
}
class Test1ModelAEntity
{
public int Id { get; set; }
public string StrProp { get; set; }
}
static void Main(string[] args)
{
var sqlServerServices = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
var conventions = new ConventionSet();
sqlServerServices.GetRequiredService<IConventionSetBuilder>().AddConventions(conventions);
var emptyModelBuilder = new ModelBuilder(conventions);
var emptyModel = emptyModelBuilder.Model;
var test1ModelBuilder = new ModelBuilder(conventions);
test1ModelBuilder.Entity<Test1ModelAEntity>()
.ToTable("ModelA");
var test1Model = test1ModelBuilder.Model;
using (TestContext ctx = new TestContext(GetOptions(emptyModel)))
{
var migrationServices = new ServiceCollection()
.AddDbContextDesignTimeServices(ctx)
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
var operations = migrationServices.GetRequiredService<IMigrationsModelDiffer>().GetDifferences(emptyModel, test1Model);
var commands = migrationServices.GetRequiredService<IMigrationsSqlGenerator>().Generate(operations, test1Model);
var connection = migrationServices.GetRequiredService<IRelationalConnection>();
migrationServices.GetRequiredService<IMigrationCommandExecutor>().ExecuteNonQuery(commands, connection);
}
}
Questo codice genera una NullReferenceException
con questa traccia dello stack:
at Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping.<>c.<GetRootType>b__10_0(IEntityType t)
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.GetSortedProperties(TableMapping target)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.<Add>d__37.MoveNext()
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.<DiffCollection>d__73`1.MoveNext()
at System.Linq.Enumerable.ConcatIterator`1.MoveNext()
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.Sort(IEnumerable`1 operations, DiffContext diffContext)
at Sandbox.Program.Main(String[] args) in D:\Sandbox\Program.cs:line 108
Ho ispezionato il codice sorgente e sembra che ci sia un problema con il modo in cui EFCore sta interpretando il mio modello. Sto usando l'anteprima di EFCore versione 2.1 2.
In realtà sto principalmente cercando configurazioni casuali sul mio IServiceCollection
s perché non ho idea di come configurarlo. Sto anche cercando di stare lontano dalle classi interne di EFCore ma, se necessario, posso usarne uno o due al momento.
C'è un modo per sfruttare le funzionalità integrate di EFCore per generare un po 'di SQL dato una coppia di s IModel
? In tal caso, come faccio a impostare DI per disporre di tutti i servizi richiesti?
Grazie per i commenti che mi hanno indirizzato nella direzione corretta.
In sintesi, stavo cercando di creare i miei modelli usando un set di convenzioni vuoto. Questo ovviamente porta a tutti i tipi di problemi in quanto è necessario generare esplicitamente l'intero modello, che è molto complesso.
Per utilizzare il set di convenzioni previsto, dovevo scaricarlo dal mio contesto usando ConventionSet.CreateConventionSet
. Dovevo anche convalidare manualmente il mio modello prima di poterlo usare nelle query e inserire comandi. Il resto della logica è praticamente lo stesso.
Ecco il mio codice finale, inclusi i test che ho eseguito per garantire che tutto funzioni come previsto:
static DbContextOptions GetOptions(IModel model)
{
var builder = new DbContextOptionsBuilder();
builder
.UseSqlServer(connStr)
.UseModel(model);
return builder.Options;
}
//Test 1
class Test1EntityA
{
public int Id { get; set; }
public string StrProp { get; set; }
}
//Test 2
class Test2EntityA
{
public int Id { get; set; }
public string StrProp { get; set; }
public ICollection<Test2ModelBEntity> Children { get; set; }
}
class Test2EntityB
{
public int Id { get; set; }
public int EntityAId { get; set; }
public Test2EntityA EntityA { get; set; }
}
static void Main(string[] args)
{
var emptyModelBuilder = new ModelBuilder(new ConventionSet());
var emptyModel = emptyModelBuilder.Model;
using (var baseCtx = new TestContext(GetOptions(emptyModel)))
{
//Get all services we need from the base context
var conventions = ConventionSet.CreateConventionSet(baseCtx);
var migrationServices = new ServiceCollection()
.AddDbContextDesignTimeServices(baseCtx)
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
//Test 1
var test1ModelBuilder = new ModelBuilder(conventions);
test1ModelBuilder.Entity<Test1EntityA>()
.ToTable("EntityA");
var test1Model = test1ModelBuilder.GetInfrastructure().Metadata;
test1Model.Validate();
var operations = migrationServices.GetRequiredService<IMigrationsModelDiffer>().GetDifferences(emptyModel, test1Model);
var commands = migrationServices.GetRequiredService<IMigrationsSqlGenerator>().Generate(operations, test1Model);
var connection = migrationServices.GetRequiredService<IRelationalConnection>();
migrationServices.GetRequiredService<IMigrationCommandExecutor>().ExecuteNonQuery(commands, connection);
using (TestContext ctx = new TestContext(GetOptions(test1Model)))
{
ctx.Set<Test1EntityA>().Add(new Test1EntityA
{
StrProp = "test1",
});
ctx.SaveChanges();
}
//Test 2
var test2ModelBuilder = new ModelBuilder(conventions);
test2ModelBuilder.Entity<Test2EntityA>()
.ToTable("EntityA");
test2ModelBuilder.Entity<Test2EntityB>()
.ToTable("EntityB");
var test2Model = test2ModelBuilder.GetInfrastructure().Metadata;
test2Model.Validate();
operations = migrationServices.GetRequiredService<IMigrationsModelDiffer>().GetDifferences(test1Model, test2Model);
commands = migrationServices.GetRequiredService<IMigrationsSqlGenerator>().Generate(operations, test2Model);
migrationServices.GetRequiredService<IMigrationCommandExecutor>().ExecuteNonQuery(commands, connection);
using (TestContext ctx = new TestContext(GetOptions(test2Model)))
{
var e = new Test2EntityA
{
StrProp = "test2",
};
ctx.Set<Test2EntityA>().Add(e);
ctx.Set<Test2EntityB>().Add(new Test2EntityB
{
EntityA = e,
});
ctx.SaveChanges();
Console.WriteLine(ctx.Set<Test2EntityB>().Where(b => b.EntityA.StrProp == "test2").Count());
}
}
}