Seguiamo i principi di Domain Driven Design nel nostro modello combinando dati e comportamenti nelle classi di oggetti entità e valore. Spesso abbiamo bisogno di personalizzare il comportamento per i nostri clienti. Ecco un semplice esempio in cui un cliente desidera modificare il modo in cui FullName è formattato:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
// Standard format is Last, First
public virtual string FullName => $"{LastName}, {FirstName}";
}
public class PersonCustom : Person
{
// Customer wants to see First Last instead
public override string FullName => $"{FirstName} {LastName}";
}
DbSet è configurato su DbContext come ci si aspetterebbe:
public virtual DbSet<Person> People { get; set; }
Al runtime, la classe PersonCustom deve essere istanziata al posto della classe Person. Questo non è un problema quando il nostro codice è responsabile dell'istanziazione dell'entità, come quando si aggiunge una nuova persona. Il contenitore / fabbrica IoC può essere configurato per sostituire la classe PersonCustom per la classe Person. Tuttavia, quando si interrogano i dati, EF è responsabile dell'istanziazione dell'entità. Quando si interrogano context.People, c'è un modo per configurare o intercettare la creazione dell'entità per sostituire PersonCustom per la classe Person al runtime?
Ho usato l'ereditarietà sopra, ma avrei potuto implementare un'interfaccia IPerson se invece fa la differenza. Ad ogni modo, è possibile assumere che l'interfaccia sarà la stessa in entrambe le classi.
Modifica: un po 'più di informazioni su come questo viene distribuito ... La classe Person farebbe parte della build standard che va a tutti i clienti. PersonCustom andrebbe in un assembly personalizzato separato che va solo al cliente che vuole la modifica, in modo da ottenere la build standard e l'assembly personalizzato. Non creeremo una build separata dell'intero progetto per soddisfare la personalizzazione.
Ho avuto un compito simile anche nel mio progetto. Esistono modi di intercettazione per fare ciò ma hanno due problemi:
Così ho finito per convertire oggetti in tipi richiesti usando AutoMapper . Ci sono anche altre librerie là fuori che possono fare la stessa cosa. In questo modo in ogni dominio puoi facilmente ottenere l'oggetto richiesto.
Questo potrebbe non essere quello che hai chiesto, ma spero che questo ti possa aiutare.
Sulla base di tutto ciò che hai fornito, credo che la creazione di un altro DbContext
per l'altro cliente si adatta meglio al tuo scenario.
Sotto codice mostra un test in cui ho usato PersonContext
per aggiungere un'entità Person
. Quindi utilizzare PersonCustomContext
per leggere la stessa entità, ma viene istanziato come PersonCustom
.
Un altro punto di interesse è la configurazione esplicita della chiave PersonCustom
per puntare alla chiave PersonId
definita nel suo tipo base Person
.
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using Xunit;
public class Tests
{
[Fact]
public void PersonCustomContext_can_get_PersonCustom()
{
var connection = new SqliteConnection("datasource=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder()
.UseSqlite(connection)
.Options;
using (var ctx = new PersonContext(options))
ctx.Database.EnsureCreated();
using (var ctx = new PersonContext(options))
{
ctx.Add(new Person { FirstName = "John", LastName = "Doe" });
ctx.SaveChanges();
}
using (var ctx = new PersonCustomContext(options))
{
Assert.Equal("John Doe", ctx.People.Single().FullName);
}
}
}
public class PersonContext : DbContext
{
public PersonContext(DbContextOptions options) : base(options) { }
public DbSet<Person> People { get; set; }
}
public class PersonCustomContext : DbContext
{
public PersonCustomContext(DbContextOptions options) : base(options) { }
public DbSet<PersonCustom> People { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PersonCustom>(builder =>
{
builder.HasKey(p => p.PersonId);
});
}
}
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual string FullName => $"{LastName}, {FirstName}";
}
public class PersonCustom : Person
{
public override string FullName => $"{FirstName} {LastName}";
}
Avvertenza: per qualche ragione, il test fallisce usando il provider in memoria. Quindi potresti considerare che se tu o il tuo cliente utilizzate in memoria per il test. Potrebbe essere un bug su EF Core stesso poiché funziona perfettamente bene con sqlite inmemory come mostrato.