2013-06-29 28 views
6

Đây là một chút mới cho tôi. Tôi đã được yêu cầu viết một chương trình ETL tải hai bộ dữ liệu vào cùng một bảng. Tập dữ liệu số 1 đã hoàn thành và chứa tất cả dữ liệu cho bảng. Tập dữ liệu số 2, tuy nhiên, chỉ chứa thay đổi cần phải được phủ lên tập dữ liệu đầu tiên. Quan sát:Khung thực thể - Chỉ cập nhật các giá trị không rỗng

// Dataset # 1: Widget Bảng

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

// Dataset # 2: Widgets_Changes Bảng

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

// Các kết quả được mong đợi: Widgets với tất cả các thay đổi

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

Cách tiếp cận rõ ràng (mà tôi đang cố gắng tránh) là kéo từng tiện ích ra khỏi bảng đầu tiên và thực hiện thuộc tính-b y-bất động sản so sánh:

// 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(); 
} 

Tuy nhiên, có hơn 200 trường trong tập dữ liệu đặc biệt này, với các tập tin hơn cả incoming mà tuân theo cùng phương pháp này (hoàn thành bộ dữ liệu kèm theo bộ dữ liệu khác) nhưng có một schema khác nhau hoàn toàn. Rõ ràng, tôi muốn có một cái gì đó di động mà tôi chỉ có thể chạy các tập tin thông qua thay vì phải so sánh tài sản-by-tài sản mã cứng cho mỗi tập dữ liệu.

Có cách nào tôi có thể lặp qua các thuộc tính của cả hai đối tượng và cập nhật các giá trị không rỗng?

+0

Bạn có thể sử dụng tính năng phản chiếu để thực hiện việc đó, sử dụng PropertyInfo để nhận các thuộc tính –

Trả lời

9

Trước hết, bạn cần phải sử dụng một cái gì đó như thế này để chọn các đối tượng bạn muốn cập nhật:

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

Bây giờ bạn chỉ có thể sử dụng phản ánh để cập nhật tất cả các lĩnh vực:

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); 
    } 
} 

này có thể được tăng tốc một chút bằng cách xây dựng danh sách các thuộc tính lên phía trước, vì vậy bạn chỉ phải sử dụng sự phản chiếu một lần:

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); 
    } 
} 

Nếu bạn có nhiều loại thực thể như vậy, bạn thậm chí có thể muốn xem xét việc này vào một tiện ích chung:

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

Lòng tốt của tôi. Điều này tốt hơn rất nhiều so với bất cứ điều gì tôi có thể hy vọng. Cảm ơn bạn! –

+0

@ ajax81 Vui mừng được giúp đỡ, Chúc mừng mã hóa :) –

+0

Bạn đã thực hiện một ngày của tôi! +1 –

4

Bạn có thể muốn sử dụng phản ánh cho việc này. Lặp qua tất cả các thuộc tính/trường cho mỗi widget/sự khác biệt, nhận giá trị của thuộc tính/trường đó, nếu chênh lệch là null, thì sử dụng giá trị ban đầu.

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

Chính xác những gì tôi đang tìm kiếm. Cảm ơn bạn! –

+0

Lưu ý rằng nếu 'widget' và' widget_diff' là các loại khác nhau, điều này sẽ không hoạt động. Bạn sẽ cần sử dụng 'PropertyInfo' thích hợp cho từng đối tượng. –

2

@ câu trả lời pswg của là rất tốt, tuy nhiên khi tôi đã cố gắng để thực hiện nó tôi gặp phải một số lỗi (như ví dụ bạn không thể kiểm tra null với obj.Equals (null), null không có phương thức Equals).

Đây là một "giải pháp sao chép thể dán hoàn chỉnh" của câu trả lời tuyệt vời @ pswg của (như một sản phẩm phụ)

Một phương pháp chung tĩnh InjectNonNull được thực thể nguồn bạn muốn cập nhật và các điểm đến "sparce "thực thể có giá trị rỗng và chỉ chuyển các thuộc tính không trống trên thực thể đích.

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 Có, tôi thực sự đã thấy vấn đề đó một chút trong khi quay trở lại. 'toValue.Equals (null)' phải là 'toValue! = null' hoặc' Object.Equals (toValue, null) '. Tôi đã sửa chữa câu trả lời của tôi cho đầy đủ, nhưng tôi thêm 'propertyPair.Item1.CanWrite' là một ý tưởng tốt, quá. –

Các vấn đề liên quan