2013-06-29 13 views
6

To dla mnie coś nowego. Poproszono mnie o napisanie programu ETL, który ładuje dwa zestawy danych do tej samej tabeli. Zbiór danych nr 1 jest kompletny i zawiera wszystkie dane dla tabeli. Zbiór danych nr 2 zawiera tylko zmian, które muszą zostać nałożone na pierwszy zestaw danych. Przestrzegać:Entity Framework - Aktualizuj tylko te wartości, które nie są zerowe.

// Zbiór danych # 1: Tabela Widgety

+----+------+------+------+------+ 
| ID | COL1 | COL2 | COL3 | COL4 | 
+----+------+------+------+------+ 
| 1 | abcd | abcd | abcd | abcd | 
+----+------+------+------+------+ 
| 2 | abcd | abcd | abcd | abcd | 
+----+------+------+------+------+ 

// Zbiór danych # 2: Widgets_Changes Tabela

+----+------+------+------+------+ 
| ID | COL1 | COL2 | COL3 | COL4 | 
+----+------+------+------+------+ 
| 1 |  | efgh |  | ijkl | 
+----+------+------+------+------+ 
| 2 | mnop |  | qrst |  | 
+----+------+------+------+------+ 

// Planowany Wynik: Widgety wszystkich zmian

+----+------+------+------+------+ 
| ID | COL1 | COL2 | COL3 | COL4 | 
+----+------+------+------+------+ 
| 1 | abcd | efgj | abcd | ijkl | 
+----+------+------+------+------+ 
| 2 | mnop | abcd | qrst | abcd | 
+----+------+------+------+------+ 

Oczywistym podejściem (którego staram się unikać) jest wyciągnięcie każdego widgetu z pierwszej tabeli i wykonanie właściwości b Porównanie y nieruchomości:

// Simplified example: 
using (var db = new MyEntityDatabase()){ 

    var widget  = from p in db.Widgets select p where p.ID == 1; 
    var widget_diff = from p in db.Widgets_Changes select p where p.ID == 1 

    widget.COL1 = widget_diff.COL1 ?? widget.COL1; 
    widget.COL2 = widget_diff.COL2 ?? widget.COL2; 
    widget.COL3 = widget_diff.COL3 ?? widget.COL3; 
    // ...etc 

    db.saveChanges(); 
} 

Jednakże, istnieje ponad 200 pól w tym konkretnym zbiorze, z więcej przychodzących plików, które przylegają do tej samej metodologii (kompletny zestaw danych towarzyszy diff zbioru danych), ale mają inny schemat całości. Oczywiście wolałbym mieć coś przenośnego, żebym mógł po prostu uruchamiać pliki zamiast zmuszać do twardego kodowania porównań własności według każdego zestawu danych.

Czy istnieje sposób można iterować właściwości obu obiektów i wartości aktualizacji, które nie są zerowe?

+0

Możesz użyć odbicia, aby to zrobić, użyj PropertyInfo, aby uzyskać właściwości –

Odpowiedz

9

Po pierwsze, trzeba użyć czegoś takiego, aby wybrać jednostki, które chcesz zaktualizować:

var widget  = db.Widgets.First(p => p.ID == 1); 
var widget_diff = db.Widgets_Changes.First(p => p.ID == 1); 

Teraz możesz po prostu użyć refleksji, aby zaktualizować wszystkie pola:

foreach(var fromProp in typepf(Widget).GetProperties()) 
{ 
    var toProp = typeof(Widget_Change).GetProperty(fromProp.Name); 
    var toValue = toProp.GetValue(widget_diff, null); 
    if (toValue != null) 
    { 
     fromProp.SetValue(widget, toValue, null); 
    } 
} 

Może to być przyspieszonym przez budowanie listy właściwości z przodu, więc musisz użyć tylko odbicie raz:

public static class WidgetUtil 
{ 
    public static readonly IEnumerable<Tuple<PropertyInfo, PropertyInfo>> PropertyMap; 

    static Util() 
    { 
     var b = BindingFlags.Public | BindingFlags.Instance; 
     PropertyMap = 
      (from f in typeof(Widget).GetProperties(b) 
      join t in typeof(WidgetChange).GetProperties(b) on f.Name equals t.Name 
      select Tuple.Create(f, t)) 
      .ToArray(); 
    } 
} 

... 

foreach(var propertyPair in WidgetUtil.PropertyMap) 
{ 
    var toValue = propertyPair.Item2.GetValue(widget_diff, null); 
    if (toValue != null) 
    { 
     propertyPair.Item1.SetValue(widget, toValue, null); 
    } 
} 

Jeśli masz wiele takich typów jednostek, można nawet rozważyć przeprowadzenie tej w ogólnej użyteczności:

public static class WidgetUtil<T1, T2> 
{ 
    public static readonly IEnumerable<Tuple<PropertyInfo, PropertyInfo>> PropertyMap; 

    static WidgetUtil() 
    { 
     var b = BindingFlags.Public | BindingFlags.Instance; 
     PropertyMap = 
      (from f in typeof(T1).GetProperties(b) 
      join t in typeof(T2).GetProperties(b) on f.Name equals t.Name 
      select Tuple.Create(f, t)) 
      .ToArray(); 
    } 
} 
+0

Moja dobroć. Jest to o wiele lepsze niż wszystko, na co mogłem liczyć. Dziękuję Ci! –

+0

@ ajax81 Cieszę się, że mogę pomóc, Happy Coding :) –

+0

Zrobiłeś mój dzień! +1 –

4

Możesz chcieć użyć refleksji do tego. Przeprowadź pętlę przez wszystkie właściwości/pola dla każdego widgetu/różnicy, uzyskaj wartość tej właściwości/pola, jeśli różnica jest zerowa, a następnie użyj wartości początkowej.

using(var db = new MyEntityDatabase()) 
{ 
    var widget  = from p in db.Widgets select p where p.ID == 1; 
    var widget_diff = from p in db.Widgets_Changes select p where p.ID == 1; 

    var properties = typeof(MyWidgetType).GetProperties(BindingFlags.Public | BindingFlags.Instance); 
    foreach(var property in properties) 
    { 
     //widget.column = widget_diff.column ?? widget.colum; 
     property.SetValue(property.GetValue(widget_diff) ?? property.GetValue(widget), widget); 
    } 

    //You can do the same for fields here if the entity has any fields (probably not). 
} 
+0

Dokładnie tego, czego szukałem. Dziękuję Ci! –

+0

Należy zauważyć, że jeśli 'widget' i' widget_diff' są różnych typów, to nie będzie działać. Będziesz musiał użyć odpowiedniego "PropertyInfo" dla każdego obiektu. –

2

@ odpowiedź pswg jest wielka, jednak kiedy starałem się wdrożyć go natknąłem się kilka błędów (takich jak na przykład nie można sprawdzić dla wartości null z obj.Equals (null), null nie mieć metodę równości).

Oto „kompletną kopiowania-wklejania rozwiązanie” wielkiej odpowiedź @ pswg (jako produkt uboczny)

statyczna metoda rodzajowa InjectNonNull pobiera podmiot źródłowy, który chcesz zaktualizować i cel „oszczędni "encja z wartościami null i przenosi tylko właściwości inne niż null w obiekcie docelowym.

private static class PropertyLister<T1, T2> 
    { 
     public static readonly IEnumerable<Tuple<PropertyInfo, PropertyInfo>> PropertyMap; 

     static PropertyLister() 
     { 
      var b = BindingFlags.Public | BindingFlags.Instance; 
      PropertyMap = 
       (from f in typeof(T1).GetProperties(b) 
       join t in typeof(T2).GetProperties(b) on f.Name equals t.Name 
       select Tuple.Create(f, t)) 
        .ToArray(); 
     } 
    } 


    public static T InjectNonNull<T>(T dest, T src) 
    { 
     foreach (var propertyPair in PropertyLister<T, T>.PropertyMap) 
     { 
      var fromValue = propertyPair.Item2.GetValue(src, null); 
      if (fromValue != null && propertyPair.Item1.CanWrite) 
      { 

       propertyPair.Item1.SetValue(dest, fromValue, null); 
      } 
     } 

     return dest; 
    } 
+0

+1 Tak, faktycznie widziałem tę kwestię chwilę po powrocie. 'toValue.Equals (null)' powinno być 'toValue! = null' lub' Object.Equals (toValue, null) '. Poprawiłem moją odpowiedź na kompletność, ale dodanie "propertyPair.Item1.CanWrite" również jest dobrym pomysłem. –

Powiązane problemy