Nie jestem profesjonalistą, ale zrobiłem to następująco :
Celem moim było mapowanie automatyczne mapowanie obiektów jako parametry przy wywoływaniu procedur składowanych na serwerze MSSQL 2005 server.
Kilka założeń (które sam sobie postawiłem):
- mapowanie musi być automatyczne (pole/właściwość -> kolumna)
- będzie można wyłączyć określone pola z mapowania
- będzie można modyfikować "klucz" mapowania - to znaczy nazwa pola != nazwa kolumny, jeśli taka taka potrzeba.
Język C#. Całość napisałem osobiście, gdy Linq2SQL jeszcze był w powijakach.
Najpierw silnik mapowania, czyli dwie klasy: DBEntityTools (przydatne funkcje) oraz DBEntityAttribute - atrybut, który nadajemy polom, aby mapować.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Reflection;
using System.Data.SqlClient;
namespace HAKGERSoft.Common {
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property , AllowMultiple = false)]
public class DBEntityAttribute : Attribute {
public DBEntityAttribute(): this(null) {
}
public DBEntityAttribute(string map) {
this.Map = map;
}
protected string _Map;
public string Map {
get {
return this._Map;
}
private set {
this._Map = value;
}
}
}
public static class DBEntityTools {
public static Dictionary<string,MemberInfo> MapEntity<T>(SqlDataReader sdr) {
Dictionary<string, MemberInfo> map = new Dictionary<string, MemberInfo>();
foreach(MemberInfo member in GetEntityMembers<T>()) {
string path = Map(member);
try {
sdr.GetOrdinal(path);
map[path] = member;
}
catch(IndexOutOfRangeException) {
// cannot map
}
}
return map;
}
public static MemberInfo[] GetEntityMembers<T>() {
return typeof(T).GetFields().Concat<MemberInfo>(typeof(T).GetProperties()).Where(x =>
x.IsDefined(typeof(DBEntityAttribute), false)).ToArray();
}
public static string Map(MemberInfo member) {
DBEntityAttribute dbe = (DBEntityAttribute)member.GetCustomAttributes(typeof(DBEntityAttribute), false).First();
return (dbe.Map!=null) ? dbe.Map : member.Name;
}
}
}
W drugim pliku, klasa DBUtility (nieco obszerniejsza), mam metody, która mapuje:
// DB -> object
static IEnumerable<T> CreateCommonResult<T>(SqlDataReader sdr, bool close) where T: new() {
while(sdr.Read()) {
T res = new T();
foreach(KeyValuePair<string, MemberInfo> pair in DBEntityTools.MapEntity<T>(sdr)) {
object chunk = sdr[pair.Key];
if(chunk!=DBNull.Value) {
if(pair.Value.MemberType==MemberTypes.Field)
((FieldInfo)pair.Value).SetValue(res, chunk);
else if(pair.Value.MemberType==MemberTypes.Property && ((PropertyInfo)pair.Value).CanWrite)
((PropertyInfo)pair.Value).SetValue(res, chunk, null);
}
}
yield return res;
}
if(close)
sdr.Close();
else
sdr.NextResult();
}
// object -> DB
static SqlCommand CreateCommon<T>(CommandType cmdType, SqlCommand cmd, T t) {
cmd.CommandType=cmdType;
foreach(MemberInfo member in DBEntityTools.GetEntityMembers<T>()) {
object value = null;
if(member.MemberType==MemberTypes.Field)
value = ((FieldInfo)member).GetValue(t);
else if(member.MemberType==MemberTypes.Property)
value = ((PropertyInfo)member).GetValue(t, null);
cmd.Parameters.AddWithValue(DBEntityTools.Map(member), SetObjectSafe(value));
}
return cmd;
}
Przykład obiektu do mapowania:
public class UserInfo {
public int Id; // nie mapowane - nie ma atrybutu DBEntity
[DBEntity]
public UserRange Range; // mapowanie na kolumne "Range"
[DBEntity]
public string Password; // mapowanie na kolumne "Password"
[DBEntity]
public string Language; // mapowanie na kolumne "Language"
[DBEntity("Flags")]
public UserFlags UserFlags; // mapowanie na kolumne "Flags", a nie "UserFlags"
}