Как стать автором
Поиск
Написать публикацию
Обновить

CRUD с помощью Dapper.NET своими руками. Часть 1

Я не разу не писал статьи, к сожалению исходя из этого кода будет больше а текста меньше.

Есть очень много 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;

        }



Вторая часть, будет лучше…
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.