La nostra azienda fornisce una suite di varie applicazioni che manipolano i dati in un database. Ogni applicazione ha una logica aziendale specifica, ma tutte le applicazioni condividono un sottoinsieme comune di regole aziendali. Le cose comuni sono incapsulate in un gruppo di DLL COM legacy, scritte in C++, che usano "ADO classico" (di solito chiamano stored procedure, a volte usano SQL dinamico). La maggior parte di queste DLL ha metodi basati su XML (per non parlare dei metodi basati su formati proprietari!) Per creare, modificare, eliminare e recuperare oggetti, e anche azioni extra come metodi che copiano e trasformano rapidamente molte entità.Sovrascrittura di SaveChanges nel codice Entity Framework 5 Prima di replicare il comportamento di una vecchia libreria legacy
Le DLL middleware sono ormai molto vecchie, i nostri sviluppatori di applicazioni desiderano un nuovo middleware orientato agli oggetti (non orientato al xml) che possa essere facilmente utilizzato dalle applicazioni C#. Molte persone nell'azienda dicono che dovremmo dimenticare i vecchi paradigmi e passare a nuove cose interessanti come Entity Framework. Sono affascinati dalla semplicità dei POCO e vorrebbero utilizzare LINQ per recuperare i dati (i metodi di query basati su Xml delle DLL non sono così facili da usare e non saranno mai flessibili come LINQ).
Quindi sto provando a creare un modello per uno scenario semplificato (lo scenario reale è molto più complesso, e qui posterò solo un sottoinsieme semplificato dello scenario semplificato!). Sto utilizzando Visual Studio 2010, Entity Framework 5 Code First, SQL Server 2008 R2. Per favore abbi pietà se faccio degli errori stupidi, sono una novità di Entity Framework. Dato che ho molti dubbi diversi, li posterò in thread separati. Questo è il primo. metodi Legacy XML hanno una firma come questo:
bool Edit(string xmlstring, out string errorMessage)
Con un formato come questo:
<ORDER>
<ID>234</ID>
<NAME>SuperFastCar</NAME>
<QUANTITY>3</QUANTITY>
<LABEL>abc</LABEL>
</ORDER>
Il metodo Edit implementato la seguente logica di business: quando una quantità viene modificato, un must "ridimensionamento automatico" essere applicato a tutti gli ordini che hanno la stessa etichetta. E.g. ci sono tre ordini: OrderA ha quantity = 3, label = X. OrderB ha quantity = 4, label = X. OrderC ha quantity = 5, label = Y. Io chiamo il metodo Edit che fornisce una nuova quantità = 6 per OrderA, cioè sto raddoppiando la quantità di OrderA. Quindi, secondo la logica aziendale, la quantità di OrderB deve essere automaticamente raddoppiata e deve diventare 8, poiché OrderB e OrderA hanno la stessa etichetta. OrderC non deve essere modificato perché ha un'etichetta diversa.
Come posso replicarlo con le classi POCO e Entity Framework? È un problema perché il vecchio metodo Modifica può cambiare solo un ordine alla volta, mentre il Entity Framework può cambiare molti ordini quando viene chiamato SaveChanges. Inoltre, una singola chiamata a SaveChanges può anche creare nuovi ordini. Presupposti temporanei, solo per questo test: 1) se molte quantità di ordine sono cambiate contemporaneamente, e il fattore di scala non è lo stesso per tutti, NO si verifica il ridimensionamento; 2) Gli ordini appena aggiunti non vengono ridimensionati automaticamente anche se hanno la stessa etichetta di un ordine ridimensionato.
Ho provato a implementarlo ignorando SaveChanges.Classe
POCO:
using System;
namespace MockOrders
{
public class Order
{
public Int64 Id { get; set; }
public string Name { get; set; }
public string Label { get; set; }
public decimal Quantity { get; set; }
}
}
file di migrazione (per creare gli indici):
namespace MockOrders.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class UniqueIndexes : DbMigration
{
public override void Up()
{
CreateIndex("dbo.Orders", "Name", true /* unique */, "myIndex1_Order_Name_Unique");
CreateIndex("dbo.Orders", "Label", false /* NOT unique */, "myIndex2_Order_Label");
}
public override void Down()
{
DropIndex("dbo.Orders", "myIndex2_Order_Label");
DropIndex("dbo.Orders", "myIndex1_Order_Name_Unique");
}
}
}
DbContext:
using System;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
namespace MockOrders
{
public class MyContext : DbContext
{
public MyContext() : base(GenerateConnection())
{
}
private static string GenerateConnection()
{
var sqlBuilder = new System.Data.SqlClient.SqlConnectionStringBuilder();
sqlBuilder.DataSource = @"localhost\aaaaaa";
sqlBuilder.InitialCatalog = "aaaaaa";
sqlBuilder.UserID = "aaaaa";
sqlBuilder.Password = "aaaaaaaaa!";
return sqlBuilder.ToString();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new OrderConfig());
}
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
var groupByLabel = from changedEntity in ChangeTracker.Entries<Order>()
where changedEntity.State == System.Data.EntityState.Modified
&& changedEntity.Property(o => o.Quantity).IsModified
&& changedEntity.Property(o => o.Quantity).OriginalValue != 0
&& !String.IsNullOrEmpty(changedEntity.Property(o => o.Label).CurrentValue)
group changedEntity by changedEntity.Property(o => o.Label).CurrentValue into x
select new { Label = x.Key, List = x};
foreach (var labeledGroup in groupByLabel)
{
var withScalingFactor = from changedEntity in labeledGroup.List
select new
{
ChangedEntity = changedEntity,
ScalingFactor = changedEntity.Property(o => o.Quantity).CurrentValue/changedEntity.Property(o => o.Quantity).OriginalValue
};
var groupByScalingFactor = from t in withScalingFactor
group t by t.ScalingFactor into g select g;
// if there are too many scaling factors for this label, skip automatic scaling
if (groupByScalingFactor.Count() == 1)
{
decimal scalingFactor = groupByScalingFactor.First().Key;
if (scalingFactor != 1)
{
var query = from oo in this.AllTheOrders where oo.Label == labeledGroup.Label select oo;
foreach (Order ord in query)
{
if (this.Entry(ord).State != System.Data.EntityState.Modified
&& this.Entry(ord).State != System.Data.EntityState.Added)
{
ord.Quantity = ord.Quantity * scalingFactor;
}
}
}
}
}
return base.SaveChanges();
}
public DbSet<Order> AllTheOrders { get; set; }
}
class OrderConfig : EntityTypeConfiguration<Order>
{
public OrderConfig()
{
Property(o => o.Name).HasMaxLength(200).IsRequired();
Property(o => o.Label).HasMaxLength(400);
}
}
}
sembra funzionare (salvo errori ovviamente), ma questo era un esempio di una sola classe: una vera applicazione di produzione può avere centinaia di classi! Temo che in uno scenario reale, con molti vincoli e logica aziendale, l'override di SaveChanges potrebbe rapidamente diventare lungo, ingombrante e soggetto a errori. Alcuni colleghi sono anche preoccupati per le prestazioni. Nelle nostre legacy DLL, molta logica di business (come le azioni "automatiche") vive in stored procedure, alcuni colleghi temono che l'approccio basato su SaveChanges possa introdurre troppi round-trip e ostacolare le prestazioni. Nell'override di SaveChanges possiamo anche invocare stored procedure, ma per quanto riguarda l'integrità transazionale? Cosa succede se apporto modifiche al database prima di chiamare "base.SaveChanges()" e "base.SaveChanges()" non riesce?
C'è un approccio diverso? Mi sto perdendo qualcosa?
Grazie mille!
Demetrio
p.s. A proposito, c'è una differenza tra l'override di SaveChanges e la registrazione all'evento "SavingChanges"? Ho letto questo documento, ma non spiega se c'è una differenza: http://msdn.microsoft.com/en-us/library/cc716714(v=vs.100).aspx
Questo post: Entity Framework SaveChanges - Customize Behavior?
dice che "quando si esegue l'override SaveChanges si può mettere la logica personalizzata prima e dopo aver chiamato base.SaveChanges". Ma ci sono altri avvertimenti/vantaggi/inconvenienti?
ancora fino a questo? – NSGaga
Al momento il progetto prototipo è bloccato, e il mio capo mi ha rimandato a lavorare sul materiale "legacy": stored procedure T-SQL, DLL native C++, strati C# estremamente sottili basati su RCW e così via (li chiamo librerie "legacy", ma hanno sempre bisogno di nuove funzionalità ed estensioni, quindi per la gestione non sono ancora "legacy"). Ma prima o poi dovrò affrontare nuovamente Entity Framework. Quindi sì, la domanda è ancora aperta ... – Demetrio78
pensi che il codice sia l'approccio migliore in questo caso? Non è possibile che un primo approccio al modello si adatti meglio al tuo caso? è possibile chiamare le stored procedure direttamente anziché utilizzare le modifiche di salvataggio. Avere una classe parziale per ogni entità che può essere salvata con un metodo Save() che richiama l'archivio appropriato. – KinSlayerUY