Я не разу не писал статьи, к сожалению исходя из этого кода будет больше а текста меньше.
Есть очень много ORM решений, NHibernate, Entity Framwork, Linq2Sql, Gentle.Net., Dapper.NET,….
Я не выбирал долго, мне нужно было от ORM не только объектное отображения данных, но и отображения результата SQL запроса
К тому же, на этапе runtime. Мне показался Dapper очень гибким и как то ближе к теме.
Начнем
Для начало создадим приложение на котором будем тестировать.
Неважно какое приложения, Desktop, Console, Web а можно вообще ограничиться UnitTest. Но в своем случае я выбрал Windows Form Application.
Самое главное нам нужно создать почву, а именно классы от которого будут наследоваться модели бизнес логики. Итак поехали:
В данном случае я применил Атрибуты для того что бы описать поля для таблицы базы данных.
От данного атрибута можно наследоваться сколько угодно, и создавать для разных таблиц БД (Oracle, Sqlite,...).
Вторым этапом, необходимо создать класс который будет формировать SQL запрос для четырех операций CRUD(Create, Read, Update, Delete).
Так скажем это основная логика. Я не стал оптимизировать код (написан был давно на скорую руку), но в целом все идеально работает.
Наименования для данного класса я применил: OrmBaseModel. Конечно, не самое удачно…
В принципе основные классы реализованы. Теперь нужно создать модель и таблицу в БД. В моем случае в роли СУБД выступает MSSQL.
Я ограничился для примера одной таблицей: User, и простыми двумя полями: Id, Name.
SQL таблица:
Модель:
Как видно по коду, я применил атрибуты для полей модели. Ранее предполагалась что класс OrmBaseModel будет в том числе создавать таблицу по модели и удалять. Но потом я отказался от данной идей.
Допустим у нас не одна таблица а скажем три, и их нужно где то содержать вместе, для этого я создал ещё один класс: Repository
Данный класс имеет три метода для трех операций Create Update Delete, они применяются для всех модели(таблиц) и поля которые выступают как
SELECT, но отталкиваются от определенной модели.
Вот и все, CRUD создан!
Вот кратки пример как этим пользоваться:
Вторая часть, будет лучше…
Есть очень много ORM решений, NHibernate, Entity Framwork, Linq2Sql, Gentle.Net., Dapper.NET,….
Я не выбирал долго, мне нужно было от ORM не только объектное отображения данных, но и отображения результата SQL запроса
К тому же, на этапе runtime. Мне показался Dapper очень гибким и как то ближе к теме.
Начнем
Для начало создадим приложение на котором будем тестировать.
Неважно какое приложения, Desktop, Console, Web а можно вообще ограничиться UnitTest. Но в своем случае я выбрал Windows Form Application.
Самое главное нам нужно создать почву, а именно классы от которого будут наследоваться модели бизнес логики. Итак поехали:
class SqlColumnAttribute : Attribute
{
public SqlColumnAttribute(string columnName, string dbType)
{
ColumnName = columnName;
ColumnDbType = dbType;
IsPrimaryKey = false;
}
public string ColumnName { get; set; }
public string ColumnDbType { get; set; }
public bool IsPrimaryKey { get; set; }
public bool IsAutoIncrement { get; set; }
}
В данном случае я применил Атрибуты для того что бы описать поля для таблицы базы данных.
От данного атрибута можно наследоваться сколько угодно, и создавать для разных таблиц БД (Oracle, Sqlite,...).
Вторым этапом, необходимо создать класс который будет формировать SQL запрос для четырех операций CRUD(Create, Read, Update, Delete).
Так скажем это основная логика. Я не стал оптимизировать код (написан был давно на скорую руку), но в целом все идеально работает.
Наименования для данного класса я применил: OrmBaseModel. Конечно, не самое удачно…
public class OrmBaseModel
{
public string SqlInsert()
{
try
{
var stringBuilder = new StringBuilder();
var fields = new StringBuilder("(");
var values = new StringBuilder(" values (");
Type type = this.GetType();
object[] tableAttributes = type.GetCustomAttributes(typeof(TableAttribute), true);
if (tableAttributes.Length == 1)
{
stringBuilder.Append(String.Format("INSERT INTO {0} ", ((TableAttribute)tableAttributes[0]).Name));
int fieldCount = 0;
foreach (var propertyInfo in type.GetProperties())
{
object[] columnAttributes = propertyInfo.GetCustomAttributes(typeof(SqlColumnAttribute),true);
if (columnAttributes.Length == 1)
{
var columnAttribute = columnAttributes[0] as SqlColumnAttribute;
if (columnAttribute != null && columnAttribute.IsPrimaryKey && columnAttribute.IsAutoIncrement)
continue;
if (fieldCount == 0)
{
if (columnAttribute != null)
{
fields.Append(columnAttribute.ColumnName);
values.Append("@" + columnAttribute.ColumnName);
}
}
else
{
if (columnAttribute != null)
{
fields.Append("," + columnAttribute.ColumnName);
values.Append(",@" + columnAttribute.ColumnName);
}
}
fieldCount++;
}
}
fields.Append(")");
values.Append(")");
stringBuilder.Append(fields);
stringBuilder.Append(values);
}
return stringBuilder.ToString();
}
catch (Exception ex)
{
}
return null;
}
public string SqlSelect()
{
try
{
var stringBuilder = new StringBuilder();
var fieldsSql = new StringBuilder("");
var fromSql = new StringBuilder();
Type type = this.GetType();
object[] tableAttributes = type.GetCustomAttributes(typeof(TableAttribute), true);
if (tableAttributes.Length == 1)
{
stringBuilder.Append(String.Format("SELECT "));
fromSql.Append(String.Format(" from {0}", ((TableAttribute)tableAttributes[0]).Name));
int fieldCount = 0;
foreach (var propertyInfo in type.GetProperties())
{
object[] columnAttributes = propertyInfo.GetCustomAttributes(typeof(SqlColumnAttribute), true);
if (columnAttributes.Length == 1)
{
var columnAttribute = columnAttributes[0] as SqlColumnAttribute;
if (fieldCount == 0)
{
if (columnAttribute != null)
{
fieldsSql.Append(columnAttribute.ColumnName);
}
}
else
{
if (columnAttribute != null)
{
fieldsSql.Append("," + columnAttribute.ColumnName);
}
}
fieldCount++;
}
}
stringBuilder.Append(fieldsSql);
stringBuilder.Append(fromSql);
}
return stringBuilder.ToString();
}
catch (Exception ex)
{
//PMLogger.Logger.DebugWriteException("BasicModelTemplate.GetSqliteSelect", ex);
}
return null;
}
public string SqlUpdate()
{
try
{
var stringBuilder = new StringBuilder();
var fieldsSql = new StringBuilder("");
var whereSql = new StringBuilder(" WHERE ");
Type type = this.GetType();
object[] tableAttributes = type.GetCustomAttributes(typeof(TableAttribute), true);
if (tableAttributes.Length == 1)
{
stringBuilder.Append(String.Format("UPDATE {0} SET ", ((TableAttribute)tableAttributes[0]).Name));
int fieldCount = 0;
foreach (var propertyInfo in type.GetProperties())
{
object[] columnAttributes = propertyInfo.GetCustomAttributes(typeof(SqlColumnAttribute),
true);
if (columnAttributes.Length == 1)
{
var columnAttribute = columnAttributes[0] as SqlColumnAttribute;
if (columnAttribute != null && columnAttribute.IsPrimaryKey)
{
whereSql.Append(String.Format("{0}=@{0}", columnAttribute.ColumnName));
}
if (fieldCount == 0)
{
if (columnAttribute != null && !columnAttribute.IsAutoIncrement)
{
fieldsSql.Append(String.Format("{0}=@{0}", columnAttribute.ColumnName));
fieldCount++;
}
}
else
{
if (columnAttribute != null && !columnAttribute.IsAutoIncrement)
{
fieldsSql.Append(String.Format(" ,{0}=@{0}", columnAttribute.ColumnName));
fieldCount++;
}
}
}
}
stringBuilder.Append(fieldsSql);
stringBuilder.Append(whereSql);
}
return stringBuilder.ToString();
}
catch (Exception ex)
{
}
return null;
}
public string SqlDelete()
{
try
{
var stringBuilder = new StringBuilder();
var fieldsSql = new StringBuilder("");
var whereSql = new StringBuilder(" WHERE ");
Type type = this.GetType();
object[] tableAttributes = type.GetCustomAttributes(typeof(TableAttribute), true);
if (tableAttributes.Length == 1)
{
stringBuilder.Append(String.Format("DELETE FROM {0} ", ((TableAttribute)tableAttributes[0]).Name));
foreach (var propertyInfo in type.GetProperties())
{
object[] columnAttributes = propertyInfo.GetCustomAttributes(typeof(SqlColumnAttribute), true);
if (columnAttributes.Length == 1)
{
var columnAttribute = columnAttributes[0] as SqlColumnAttribute;
if (columnAttribute != null && columnAttribute.IsPrimaryKey)
{
whereSql.Append(String.Format("{0}=@{0}", columnAttribute.ColumnName));
break;
}
}
}
stringBuilder.Append(fieldsSql);
stringBuilder.Append(whereSql);
}
return stringBuilder.ToString();
}
catch (Exception ex)
{
}
return null;
}
public List<T> Select<T>(IDbConnection dbconnection)
{
return dbconnection.Query<T>(SqlSelect()).ToList();
}
public int Insert(IDbConnection dbconnection)
{
try
{
var rowsAffected = dbconnection.Execute(SqlInsert(), this);
if (PrimaryKeyPropertyInfo != null)
{
var pinfo = PrimaryKeyPropertyInfo;
object[] columnAttributes = pinfo.GetCustomAttributes(typeof(SqlColumnAttribute), true);
if (columnAttributes.Length == 1)
{
var columnAttribute = columnAttributes[0] as SqlColumnAttribute;
if (columnAttribute != null && columnAttribute.IsPrimaryKey && columnAttribute.IsAutoIncrement)
{
dynamic identity = dbconnection.Query("SELECT @@IDENTITY AS Id").Single();
pinfo.SetValue(this, Convert.ChangeType(identity.Id, TypeCode.Int32), null);
var i = identity.Id;
}
}
}
return rowsAffected;
}
catch (Exception)
{
}
return -1;
}
public int Update(IDbConnection dbconnection)
{
var rowsAffected = dbconnection.Execute(SqlUpdate(), this);
return rowsAffected;
}
public int Delete(IDbConnection dbconnection)
{
var rowsAffected = dbconnection.Execute(SqlDelete(), this);
return rowsAffected;
}
protected PropertyInfo PrimaryKeyPropertyInfo
{
get
{
Type type = this.GetType();
object[] tableAttributes = type.GetCustomAttributes(typeof(TableAttribute), true);
if (tableAttributes.Length == 1)
{
foreach (var propertyInfo in type.GetProperties())
{
object[] columnAttributes = propertyInfo.GetCustomAttributes(typeof(SqlColumnAttribute), true);
if (columnAttributes.Length == 1)
{
var columnAttribute = columnAttributes[0] as SqlColumnAttribute;
if (columnAttribute != null && columnAttribute.IsPrimaryKey)
return propertyInfo;
}
}
}
return null;
}
}
}
В принципе основные классы реализованы. Теперь нужно создать модель и таблицу в БД. В моем случае в роли СУБД выступает MSSQL.
Я ограничился для примера одной таблицей: User, и простыми двумя полями: Id, Name.
SQL таблица:
CREATE TABLE [dbo].[Users](
[Id] [int],
[Name] [varchar](50) NULL
) ON [PRIMARY]
Модель:
[Serializable]
[Table(Name = "Users")]
public class User : OrmBaseModel
{
[SqlColumn("Id", "integer", IsPrimaryKey = true)]
public int Id { get; set; }
[SqlColumn("Name", "varchar(100) NULL")]
public string Name { get; set; }
}
Как видно по коду, я применил атрибуты для полей модели. Ранее предполагалась что класс OrmBaseModel будет в том числе создавать таблицу по модели и удалять. Но потом я отказался от данной идей.
Допустим у нас не одна таблица а скажем три, и их нужно где то содержать вместе, для этого я создал ещё один класс: Repository
internal class Repository
{
private readonly SqlConnection _dbconnection;
private List<User> _users;
private readonly OrmAdapterModel _ormAdapterModel;
public Repository(SqlConnection dbconnection)
{
_dbconnection = dbconnection;
_ormAdapterModel = new OrmAdapterModel(_dbconnection);
}
public void Update(OrmBaseModel ormBaseModel)
{
ormBaseModel.Update(_dbconnection);
}
public void Delete(OrmBaseModel ormBaseModel)
{
ormBaseModel.Delete(_dbconnection);
}
public void Insert(OrmBaseModel ormBaseModel)
{
ormBaseModel.Insert(_dbconnection);
}
public List<User> Users
{
get
{
return ((new User()).Select<User>(_dbconnection));
}
}
}
Данный класс имеет три метода для трех операций Create Update Delete, они применяются для всех модели(таблиц) и поля которые выступают как
SELECT, но отталкиваются от определенной модели.
Вот и все, CRUD создан!
Вот кратки пример как этим пользоваться:
private void button1_Click(object sender, EventArgs e)
{
SqlConnection s = new SqlConnection(@"Data Source=localhost\xxx;Initial Catalog=Clients;User ID=sa;Password=xxx");
s.Open();
Repository rep = new Repository(s);
rep.Insert(new User { Id = 1, Name = "Habrahabr" });
rep.Insert(new User { Id = 2, Name = "Admin" });
rep.Insert(new User { Id = 3, Name = "User" });
rep.Insert(new User { Id = 4, Name = "Ridik" });
rep.Insert(new User { Id = 5, Name = "Alen" });
rep.Update(new User { Id = 3, Name = "Admin" });
rep.Delete(new User { Id = 3});
rep.Insert(new User { Id = 3, Name = "Hello" });
bindingSource1.DataSource = rep.Users;
}
Вторая часть, будет лучше…