Laden...

Einfacher Tabellen Mapper

Erstellt von jnetrick vor 16 Jahren Letzter Beitrag vor 16 Jahren 5.595 Views
J
jnetrick Themenstarter:in
32 Beiträge seit 2008
vor 16 Jahren
Einfacher Tabellen Mapper

verwendetes Datenbanksystem: Datenbankunabhängig - Referenzsystem SQL Server 2005

Ich bin gerade dabei mir eine kleine Hilfsroutine zu schreiben, welche anhand
der Property Namen meiner Entität aus einem DbDataReader die Ergebnisse ausliest und daraufhin eine generische Liste füllt. Natürlich müssen die Propertynamen bei meiner Methode mit den Spalten der Ergebnistabelle übereinstimmen.
Funktioniert soweit auch ganz gut. Allerding ist die Performanz nicht unbedingt sonderlich zufriedenstellend.

Im direkten Vergleich schneidet das befüllen einer DataTable durch einen DbDataAdapter um einiges besser ab.


public static List<T> MapToList<T,P>(DbDataReader dbDataReader, int capacity) where T: new()
																					  where P : T, new()
		{
			List<T> ReturnList = new List<T>(capacity);
			PropertyInfo[] PropertyInfoArr =
								typeof(T).GetProperties();
			while (dbDataReader.Read()) {
				T NewEntity = new P();
				
				#region map simple types
				foreach (PropertyInfo propInfo in PropertyInfoArr) {
					object RowValue = null;
					Type PropertyType = propInfo.PropertyType;

					try {
						RowValue = dbDataReader[propInfo.Name];
						if(!(RowValue is DBNull))
						{
							if (RowValue.GetType() != PropertyType &&
								!PropertyType.IsEnum &&
								!PropertyType.IsAssignableFrom(RowValue.GetType())) {
								if (TypeHelper.IsNullableType(PropertyType)) {
									RowValue = Convert.ChangeType(RowValue, Nullable.GetUnderlyingType(PropertyType));
								} else {
									RowValue = Convert.ChangeType(RowValue, PropertyType);
								}
							} 
							propInfo.SetValue(NewEntity, RowValue, null);							
						}						
					} catch (IndexOutOfRangeException) {
						continue;
					} 
				}
				#endregion
				ReturnList.Add(NewEntity);
			}

			return ReturnList;
		}

Hat von euch vielleicht noch jemand Ideen wie man das ganze noch optimieren könnte. Ich schau mir gerade den Quellcode der DbDataAdapter.Fill Methoden genauer an. Ist aber doch sehr schwierig anhand des Quellcodes darauf zu schließen wie der Mechanismus funktioniert.

Natürlich könnte ich einen bestehenden OR-Mapper verwenden, das ist aber gar nicht meine Intention. Ich möchte nur eine kleine Methode welche ich bei Bedarf schnell einsetzen kann.

J
jnetrick Themenstarter:in
32 Beiträge seit 2008
vor 16 Jahren

Hab das Problem jetzt selbst gelöst.


public static List<T> MapToList<T,P>(DbDataReader dbDataReader, int capacity) where T: new()
																					  where P : T, new()
		{
			List<T> ReturnList = new List<T>(capacity);
			Type EntityType = typeof(T);
			IDictionary<string, PropertyInfo> EntityProperties = 
							TypeHelper.GetPropertiesAsDictionary<T>();
			PropertyInfo[] PropertyInfoArr = EntityType.GetProperties();
			int j=0;
			dbDataReader.GetSchemaTable().WriteXml("C:\\test.xml");
			while (dbDataReader.Read()) {
				T NewEntity = new P();
				//Console.WriteLine("Row: " + j++);
				#region map simple types
				
				for(int i=0;i< dbDataReader.FieldCount;i++)
				{
					object RowValue = null;
					try
					{
						RowValue = dbDataReader.GetValue(i);
						if (!(RowValue is DBNull)) {
							if (RowValue.GetType() !=
								EntityProperties[dbDataReader.GetName(i)].PropertyType) {
								//handle different types
								Type PropertyType =
									EntityProperties[dbDataReader.GetName(i)].PropertyType;
								//if the property type is nullable - we need the underlying type
								if (Nullable.GetUnderlyingType(PropertyType) != null) {								
									PropertyType = Nullable.GetUnderlyingType(PropertyType);
								}

								RowValue = TypeHelper.ConvertType(RowValue, PropertyType);
								
							}
							EntityProperties[dbDataReader.GetName(i)].
										SetValue(NewEntity, RowValue, null);
						}
					}catch(Exception)
					{
						continue;
					}
				}
				#endregion
				ReturnList.Add(NewEntity);
			}

			return ReturnList;
		}


public static object ConvertType(object source, Type targetType) {
			if(targetType == typeof(bool))
			{
				return Convert.ToBoolean(source);
			} else if (targetType == typeof(int)) {
				return Convert.ToInt32(source);
			} else if (targetType == typeof(long)) {
				return Convert.ToInt64(source);
			} else if (targetType == typeof(short)) {
				return Convert.ToInt16(source);
			} else if (targetType == typeof(byte)) {
				return Convert.ToByte(source);
			} else if (targetType == typeof(ulong)) {
				return Convert.ToUInt64(source);
			}
			return source;
		}


Die Bremse war anscheinend die Methode
Convert.ChangeType

Hab die Typänderung jetzt durch eine eigene Routine ersetzt.

Vielleicht hat aber jemand noch eine Idee wie man eine solche Typkonvertierung noch schöner/performanter hinbekommt.

Bin jetzt aber mit der Performanz eigentlich schon zufrieden.
Der Unterschied war extrem. Vorher lag die Reaktionszeit bei einer Tabelle mit
zehn Spalten und 8000 Zeilen bei ca. 1 Minute jetzt
dauerts nur noch eine Sekunden 🙂

O
778 Beiträge seit 2007
vor 16 Jahren

Bei 8000 Zeilen wuerde es sich glaub ich lohnen statt mit SetValue mithilfe einer dynamischen Zugriffsmethode auf die Eigenschaft zuzugreifen. Das koenntest du dann machen mit DynamicMethod. Musst aber aufpassen, dass du zwischen virtuellen und "tatsaechlichen" unterscheidest

I
256 Beiträge seit 2005
vor 16 Jahren

Gibt schon so ein Funktion in DotNetNuke

Hab mir mal mittels Reflector das angeschaut. Die machen die ChangeType so

public static object ChangeType(object value, Type conversionType, IFormatProvider provider)
{
    if (conversionType == null)
    {
        throw new ArgumentNullException("conversionType");
    }
    if (value == null)
    {
        if (conversionType.IsValueType)
        {
            throw new InvalidCastException(Environment.GetResourceString("InvalidCast_CannotCastNullToValueType"));
        }
        return null;
    }
    IConvertible convertible = value as IConvertible;
    if (convertible == null)
    {
        if (value.GetType() != conversionType)
        {
            throw new InvalidCastException(Environment.GetResourceString("InvalidCast_IConvertible"));
        }
        return value;
    }
    if (conversionType == ConvertTypes[3])
    {
        return convertible.ToBoolean(provider);
    }
    if (conversionType == ConvertTypes[4])
    {
        return convertible.ToChar(provider);
    }
    if (conversionType == ConvertTypes[5])
    {
        return convertible.ToSByte(provider);
    }
    if (conversionType == ConvertTypes[6])
    {
        return convertible.ToByte(provider);
    }
    if (conversionType == ConvertTypes[7])
    {
        return convertible.ToInt16(provider);
    }
    if (conversionType == ConvertTypes[8])
    {
        return convertible.ToUInt16(provider);
    }
    if (conversionType == ConvertTypes[9])
    {
        return convertible.ToInt32(provider);
    }
    if (conversionType == ConvertTypes[10])
    {
        return convertible.ToUInt32(provider);
    }
    if (conversionType == ConvertTypes[11])
    {
        return convertible.ToInt64(provider);
    }
    if (conversionType == ConvertTypes[12])
    {
        return convertible.ToUInt64(provider);
    }
    if (conversionType == ConvertTypes[13])
    {
        return convertible.ToSingle(provider);
    }
    if (conversionType == ConvertTypes[14])
    {
        return convertible.ToDouble(provider);
    }
    if (conversionType == ConvertTypes[15])
    {
        return convertible.ToDecimal(provider);
    }
    if (conversionType == ConvertTypes[0x10])
    {
        return convertible.ToDateTime(provider);
    }
    if (conversionType == ConvertTypes[0x12])
    {
        return convertible.ToString(provider);
    }
    if (conversionType == ConvertTypes[1])
    {
        return value;
    }
    return convertible.ToType(conversionType, provider);
}


Schau die überhaupt mal die dotnetnuke.dll an, da ist schon viel geteste funktionaltität drinn.

J
jnetrick Themenstarter:in
32 Beiträge seit 2008
vor 16 Jahren

Onlinegurke könntest du mir bitte noch etwas genauer erklären wie du das mit DynamicMethod gemeint hast. Ich habe im Bereich System.Reflection.Emit noch keine Erfahrung. Soweit ich das jetzt aber beim durchlesen der Hilfe zu DynamicMethod
verstanden habe, kann man sich damit neue Methoden zur Laufzeit erzeugen.
Inwieweit hilft mir dies den Zugriff auf den PropertySetter zu beschleunigen?

J
jnetrick Themenstarter:in
32 Beiträge seit 2008
vor 16 Jahren

Hallo,
ich habe jetzt mal selbst recherchiert und habe jetzt eine Klasse bei CodeProject gefunden Fast Dynamic Property Access mit der meine Routine nochmals um einiges schneller läuft.

Mit der Klasse PropertyAccessor kann man sich zur Laufzeit Code generieren mit dem dann der Zugriff auf die Properties erfolgt (-> Beitrag von OnlineGurke)
ich bin echt beeindruckt wie stark sich die Beschleunigung auswirkt:
Tabelle mit 8 Spalten und 356.000 Zeilen
mit DbDataAdapter.Fill(DataTable) ca 3,6 Sekunden
mit meiner Routine und Standard Reflection ca 18 Sekunden!!!
mit Fast Property Access ca 4 Sekunden

Wow!!!
Danke euch allen habe jetzt meine Methode fertig 🙂
Bei Interess kann ich gerne mal den kompletten Quellcode posten.

F
10.010 Beiträge seit 2004
vor 16 Jahren

Haben wir ein extra Forum für.

2.891 Beiträge seit 2004
vor 16 Jahren

Mit der Klasse PropertyAccessor kann man sich zur Laufzeit Code generieren mit dem dann der Zugriff auf die Properties erfolgt (-> Beitrag von OnlineGurke)
Bei Interess kann ich gerne mal den kompletten Quellcode posten.

Hallo,

sehr interessant dieser Beitrag. Mache nämlich gerade genau das gleiche bzw. habe es gemacht (O/R-Mapper). An Ansatz mit den dynamischen Typen an dieser Stelle hätte ich gar nicht gedacht. Vor allem, weil es auch noch viel schneller geht.
Nur das ChangeType-Problem ist ja noch nicht ganz gelöst, oder? Die genannten Optimierungsansätze zielen darauf ab, nicht die ChangeType()-Methode zu benutzen, sondern direkt die "richtige". Da wäre dann ja auch denkbar, die Typkonvertierung direkt in den PropertyAccessor zu integrieren, dann würden sogar die ganzen Vergleiche wegfallen... Hm, ich glaub, ich setze mich da mal ran. 8)
EDIT: Fertich 🙂 DynamicFieldAccessor statt Reflection - schneller dynamischer Zugriff auf Felder/Properties

Gruß
dN!3L

J
jnetrick Themenstarter:in
32 Beiträge seit 2008
vor 16 Jahren

Hi ich habe jetzt noch eine sehr interessante Möglichkeit gefunden wie man sich mit den neuen Sprachfeatures von C# 3.0 einen einfachen Mapper bauen kann - dachte ich poste das ganze mal hier:


public static List<T> MapToList<T>(DbDataReader dbDataReader, T Prototype) {
			List<T> ReturnList = new List<T>();
			Type EntityType = typeof(T);
			ConstructorInfo[] Constructors = EntityType.GetConstructors();
			ParameterInfo[] ConPara = Constructors[0].GetParameters();

			
			int columnCount = dbDataReader.FieldCount;
			while (dbDataReader.Read()) {
				object[] o = new object[Constructors[0].GetParameters().Length];
				
				object[] dataRow = new object[columnCount];
				dbDataReader.GetValues(dataRow);
				ConvertDbNullToNull(dataRow);
				T NewEntity = (T)Constructors[0].Invoke(dataRow);
				ReturnList.Add(NewEntity);
			}

			return ReturnList;
		}

private static void ConvertDbNullToNull(object[] values) {
			for (int i = 0; i < values.Length; i++) {
				if (values[i] is DBNull) {
					values[i] = null;
				}
			}
		}

damit kann man dann z.B. folgendes machen:


DbDataReader dataReader = ...

var Prototype = new { Id = 0, Name = ""};
var EntityList = MapToList(dataReader, Prototype);


Sehr nützlich wenn mann nur mal eben ein paar Daten abfragen möchte