ORM своими руками (часть вторая)
В предыдущей части мы написали парочку своих аттрибутов, с помощью которых описали маппинг C# класса на таблицу базы данных. Дальше возьмем Reflection и Generic механизмы .NET и напишем класс, который будет являться CRUD контроллером наших сущностей. Для того чтобы класс работал побыстрее, парсинг метаданных вынесен в конструктор, и заглушкой в этой реализации является один метод - ResolveConnection(). Его можно сконфигурировать как в NHibernate классом-конфигуратором, либо впрыскиванием зависимости DbProviderFactory и строки подключения, но это вопросы лишь архитектуры, и я думаю у мало-мальски толкового разработчика будет много идей на этот счет.
Ниже листинг класса, позволяющего выполнять стандартные CRUD операции:
public class DataContext<T> where T: class, new()
{
private readonly string _dbTableName;
private readonly Dictionary<PropertyInfo, DbColumnAttribute> _dbAllFields;
private readonly Dictionary<PropertyInfo, DbColumnAttribute> _dbPKFields;
private readonly PropertyInfo _autoincrementProperty;
private readonly PropertyInfo _versionProperty;public DataContext()
{
_dbAllFields = new Dictionary<PropertyInfo, DbColumnAttribute>();
_dbPKFields = new Dictionary<PropertyInfo, DbColumnAttribute>();
//parse metadata
Type t = typeof(T);
object[] attributes = t.GetCustomAttributes(typeof(DbTableAttribute), false);
if (attributes.Length > 0)
{
_dbTableName = ((DbTableAttribute)attributes[0]).Name;
}
PropertyInfo[] properties = t.GetProperties();
foreach (PropertyInfo info in properties)
{
object[] attrubutes = info.GetCustomAttributes(typeof(DbColumnAttribute), false);
if (attrubutes.Length > 0)
{
DbColumnAttribute att = ((DbColumnAttribute) attrubutes[0]);
_dbAllFields[info] = att;
if(att.IsPrimaryKey) _dbPKFields[info] = att;
if(att.AutoIncrement) _autoincrementProperty = info;
if(att.IsVersion) _versionProperty = info;
}
}
}public IList<T> Query(string where, string order)
{
List<T> items = new List<T>();
string query = string.Format(“SELECT * FROM [{0}]“, _dbTableName);
if (!string.IsNullOrEmpty(where))
query = string.Format(“{0} WHERE {1}”, query, where);
if (!string.IsNullOrEmpty(order))
query = string.Format(“{0} ORDER BY {1}”, query, order);
//
using (DbConnection connection = ResolveConnection())
{
DbCommand cmd = connection.CreateCommand();
cmd.CommandText = query;
connection.Open();
using (DbDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
T item = GetItemFromReader(reader);
items.Add(item);
}
}
connection.Close();
}
//
return items;
}public IList<T> Query(string where)
{
return Query(where, null);
}public IList<T> LoadAll()
{
return Query(null, null);
}public bool Delete(T item)
{
string query = string.Format(“DELETE FROM [{0}] WHERE {1}”, _dbTableName, BuildWherePKQuery(item));
using (DbConnection connection = ResolveConnection())
{
DbCommand cmd = connection.CreateCommand();
cmd.CommandText = query;
connection.Open();
return cmd.ExecuteNonQuery() > 0;
}
}public T GetItemByPK(T itemWithPK)
{
string query = string.Format(“SELECT * FROM [{0}] WHERE {1}”, _dbTableName, BuildWherePKQuery(itemWithPK));
using (DbConnection connection = ResolveConnection())
{
DbCommand cmd = connection.CreateCommand();
cmd.CommandText = query;
connection.Open();
using (DbDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
T item = GetItemFromReader(reader);
return item;
}
}
connection.Close();
}
return null;
}public bool Modify(T item)
{
if(_versionProperty != null)
{
T originalItem = GetItemByPK(item);
long versionold = Convert.ToInt64(_versionProperty.GetValue(originalItem, null));
long versionNew = Convert.ToInt64(_versionProperty.GetValue(item, null));
if(versionold != versionNew)
{
throw new BLException(“Concurency violation”, BLExceptionType.ConcurencyViolation);
}
versionold++;
_versionProperty.SetValue(item, Convert.ChangeType(versionold, _versionProperty.PropertyType) , null);
}
//
string query = string.Format(“UPDATE [{0}] SET {1} WHERE {2}”, _dbTableName, BuildModifySubQuery(item), BuildWherePKQuery(item));
using (DbConnection connection = ResolveConnection())
{
DbCommand cmd = connection.CreateCommand();
cmd.CommandText = query;
connection.Open();return cmd.ExecuteNonQuery() > 0;
}
}public bool Create(T item)
{
string query = string.Format(“INSERT INTO [{0}] {1}”, _dbTableName, BuildCreateQuery(item));
using (DbConnection connection = ResolveConnection())
{
connection.Open();
DbTransaction tran = connection.BeginTransaction();
try
{
DbCommand cmd = connection.CreateCommand();
cmd.CommandText = query;
cmd.Transaction = tran;
int n = cmd.ExecuteNonQuery();
if (n > 0 && _autoincrementProperty != null)
{
DbCommand cmdA = connection.CreateCommand();
cmdA.CommandText = “SELECT @@IDENTITY AS NEW_PK”;
cmdA.Transaction = tran;
object pk = cmdA.ExecuteScalar();
_autoincrementProperty.SetValue(item, Convert.ChangeType(pk, _autoincrementProperty.PropertyType), null);
}
tran.Commit();
return n > 0;
}
catch(Exception)
{
tran.Rollback();
throw;
}
}
}public int CreateAll(IList<T> items)
{
int cnt = 0;
foreach (T item in items)
{
if (Create(item)) cnt++;
}
return cnt;
}public int ModifyAll(IList<T> items)
{
int cnt = 0;
foreach (T item in items)
{
if (Modify(item)) cnt++;
}
return cnt;
}public int DeleteAll(IList<T> items)
{
int cnt = 0;
foreach (T item in items)
{
if(Delete(item)) cnt++;
}
return cnt;
}private static DbConnection ResolveConnection()
{
return new SqlConnection(“Data Source=.;Initial Catalog=test;Integrated Security=True;Pooling=False”);
}private string BuildWherePKQuery(T item)
{
List<string> queryParts = new List<string>();
foreach (PropertyInfo info in _dbPKFields.Keys)
{
object val = info.GetValue(item, null);
string queryPart = null;
if(val != null)
queryPart = string.Format(“[{0}] = ‘{1}’”, _dbPKFields[info].Name, ToSqlVal(val));
else
queryPart = string.Format(“[{0}] IS NULL”, _dbPKFields[info].Name);
queryParts.Add(queryPart);
}
return string.Join(” AND “, queryParts.ToArray());
}private string BuildModifySubQuery(T item)
{
List<string> queryParts = new List<string>();
foreach (PropertyInfo info in _dbAllFields.Keys)
{
if (!_dbAllFields[info].IsPrimaryKey)
{
object val = info.GetValue(item, null);
string queryPart = null;
if (val != null)
queryPart = string.Format(“[{0}] = ‘{1}’”, _dbAllFields[info].Name, ToSqlVal(val));
else
queryPart = string.Format(“[{0}] = NULL”, _dbAllFields[info].Name);
queryParts.Add(queryPart);
}
}
return string.Join(“, “, queryParts.ToArray());
}private string BuildCreateQuery(T item)
{
List<string> fields = new List<string>();
List<string> values = new List<string>();
foreach (PropertyInfo info in _dbAllFields.Keys)
{
if (!_dbAllFields[info].AutoIncrement)
{
object val = info.GetValue(item, null);
string strVal = null;
strVal = val != null ? string.Format(“‘{0}’”, ToSqlVal(val)) : “NULL”;
values.Add(strVal);
fields.Add(_dbAllFields[info].Name);
}
}
return string.Format(“({0}) VALUES ({1})”, string.Join(“,”, fields.ToArray()), string.Join(“,”, values.ToArray()));
}private static object ToSqlVal(object val)
{
if (val is string)
{
string strVal = (string) val;
if (!string.IsNullOrEmpty(strVal))
{
if (strVal.IndexOf(‘\”) > -1)
{
return strVal.Replace(“‘”, “””);
}
}
}
else if (val is DateTime || val is float || val is decimal || val is double)
{
return Convert.ToString(val, CultureInfo.InvariantCulture);
}return val;
}private T GetItemFromReader(IDataRecord reader)
{
T item = new T();
foreach (PropertyInfo info in _dbAllFields.Keys)
{
object val = reader[_dbAllFields[info].Name];
if (val != DBNull.Value)
{
if (info.PropertyType.IsGenericType && info.PropertyType.Name.IndexOf(“Nullable”) > -1)
{
PropertyInfo piNull = info.PropertyType.GetProperty(“Value”);
info.SetValue(item, Convert.ChangeType(val, piNull.PropertyType), null);
}
else
{
info.SetValue(item, Convert.ChangeType(val, info.PropertyType), null);
}
}
}
return item;
}
}* This source code was highlighted with Source Code Highlighter.
В принципе код говорит сам за себя. Не претендую на высокие инженерные мысли, тестик написан за пару дней. Кто увидит ляпы - велкам в коменты. Пример использования довольно прост:
DataContext<Entity1> context = new DataContext<Entity1>();
IList<Entity1> entities = context.Query(“Id < 190000″, “CreatedTs”);
Entity1 e = new Entity1() {CreatedTs = DateTime.Now, Description = “myDescription”, Name = “ee11″, Value = null };
context.Create(e);
e.Description = “Modified__”;
context.Modify(e);
context.Delete(e);* This source code was highlighted with Source Code Highlighter.