Nasza firma dostarcza zestaw różnych aplikacji, które manipulują danymi w bazie danych. Każda aplikacja ma określoną logikę biznesową, ale wszystkie aplikacje mają wspólny podzbiór reguł biznesowych. Najczęściej spotykane są w starych bibliotekach DLL COM, napisanych w C++, które używają "klasycznego ADO" (zwykle nazywają je procedurami przechowywanymi, czasami używają dynamicznego SQL). Większość z tych bibliotek DLL ma metody oparte na XML (nie wspominając o metodach opartych na formacie prawnie zastrzeżonym!) Do tworzenia, edycji, usuwania i pobierania obiektów, a także dodatkowe działania, takie jak metody, które szybko kopiują i przekształcają wiele jednostek.Zastępowanie zmian SaveChanges w Entity Framework 5 Code Najpierw replikować zachowanie starej starszej biblioteki
DLL oprogramowania warstwy pośredniej są już bardzo stare, a nasi programiści aplikacji chcą nowego oprogramowania zorientowanego obiektowo (nie w języku xml), które może być z łatwością używane przez aplikacje C#. Wiele osób w firmie mówi, że powinniśmy zapomnieć o starych paradygmatach i przejść do nowych, fajnych rzeczy, takich jak Entity Framework. Intryguje ich prostota POCO i chcieliby używać LINQ do pobierania danych (Metody zapytań oparte na Xml bibliotek DLL nie są tak łatwe w użyciu i nigdy nie będą tak elastyczne jak LINQ).
Więc próbuję stworzyć makietę dla uproszczonego scenariusza (rzeczywisty scenariusz jest znacznie bardziej złożony, a tutaj zamieszczę tylko uproszczony podzestaw uproszczonego scenariusza!). Używam Visual Studio 2010, Entity Framework 5 Code First, SQL Server 2008 R2. Prosimy o litość, jeśli popełnię głupie błędy, jestem nowy w Entity Framework. Ponieważ mam wiele różnych wątpliwości, opublikuję je w osobnych wątkach. To jest pierwszy. Metody Legacy XML mieć podpis takiego:
bool Edit(string xmlstring, out string errorMessage)
w formacie jak poniżej:
<ORDER>
<ID>234</ID>
<NAME>SuperFastCar</NAME>
<QUANTITY>3</QUANTITY>
<LABEL>abc</LABEL>
</ORDER>
Edit sposób realizowane następującą logikę biznesową: kiedy ilość zmienia się się, że „automatyczne skalowanie” Must stosować się do wszystkich zamówień, które mają tę samą etykietę. E.g. są trzy rozkazy: OrderA ma ilość = 3, label = X. OrderB ma ilość = 4, label = X. OrderC ma ilość = 5, label = Y. Zadzwonię do metody Edit, dostarczając nową ilość = 6 dla OrderA, tj. podwojenie ilości OrderA. Następnie, zgodnie z logiką biznesową, ilość zamówienia B musi być automatycznie podwojona i musi wynosić 8, ponieważ OrderB i OrderA mają tę samą etykietę. OrderC nie może zostać zmieniony, ponieważ ma inną etykietę.
Jak mogę skopiować to za pomocą klas POCO i Entity Framework? Jest to problem, ponieważ stara metoda edycji może zmienić tylko jedno zamówienie naraz, podczas gdy Entity Framework może zmienić wiele zamówień, gdy zostanie wywołana funkcja SaveChanges. Ponadto pojedyncze połączenie z SaveChanges może również tworzyć nowe zamówienia. Tymczasowe założenia, tylko dla tego testu: 1) jeśli wiele Ilości Zamówień zmienia się w tym samym czasie, a współczynnik skalowania nie jest taki sam dla wszystkich z nich, NIE występuje skalowanie; 2) nowo dodane zamówienia nie są automatycznie skalowane, nawet jeśli mają taką samą etykietę skalowanego zamówienia.
Próbowałem go zaimplementować, zastępując SaveChanges.
POCO klasy:
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; }
}
}
plik migracji (do tworzenia indeksów):
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);
}
}
}
wydaje się działać (wyjąwszy błędy oczywiście), ale był to przykład z tylko jedną klasą: prawdziwa aplikacja do produkcji może mieć setki klas! Obawiam się, że w prawdziwym scenariuszu, z wieloma ograniczeniami i logiką biznesową, nadpisanie SaveChanges może szybko stać się długie, zagracone i podatne na błędy. Niektórzy koledzy martwią się także o wydajność. W naszych starszych bibliotekach DLL wiele logiki biznesowej (takich jak "automatyczne" działania) żyje w procedurach przechowywanych, niektórzy koledzy obawiają się, że podejście oparte na SaveChanges może wprowadzić zbyt wiele podróży w obie strony i utrudnić działanie. W override SaveChanges moglibyśmy również wywołać procedury składowane, ale co z integralnością transakcyjną? Co się stanie, jeśli wprowadzę zmiany do bazy danych , zanim zadzwonię do "base.SaveChanges()", a "base.SaveChanges()" nie powiedzie się?
Czy istnieje inne podejście? Czy czegoś brakuje?
Dziękuję bardzo!
Demetrio
p.s. Przy okazji, czy istnieje różnica między nadpisaniem SaveChanges i rejestracją w zdarzeniu "SavingChanges"? Czytałem ten dokument, ale nie wyjaśnia, czy istnieje różnica: http://msdn.microsoft.com/en-us/library/cc716714(v=vs.100).aspx
tego posta: Entity Framework SaveChanges - Customize Behavior?
mówi, że „gdy nadrzędne SaveChanges można umieścić niestandardową logikę przed i po wywołaniu base.SaveChanges”. Ale czy istnieją inne zastrzeżenia/zalety/wady?
nadal do tego? – NSGaga
W tej chwili prototypowy projekt utknął, a mój szef wysłał mnie z powrotem do pracy nad "starszymi" rzeczami: procedury przechowywane T-SQL, natywne biblioteki DLL C++, wyjątkowo cienkie warstwy C# oparte na RCW i tak dalej (nazywam je biblioteki "starsze", ale wymagają one ciągle nowych funkcji i rozszerzeń, więc w przypadku zarządzania nie są jeszcze "starsze"). Ale wcześniej czy później znowu będę musiał stawić czoła Entity Framework. Więc tak, pytanie nadal jest otwarte ... – Demetrio78
myślisz, że kod pierwszy jest najlepszym podejściem w tym przypadku? Którego modelu nie da się lepiej dopasować do twojego przypadku? można wywołać procedury składowane bezpośrednio, zamiast korzystać z zapisanych zmian. Posiadanie klasy częściowej dla każdej jednostki, którą można zapisać przy użyciu metody Save(), która wywołuje odpowiedni magazyn. – KinSlayerUY