Created
January 16, 2019 07:11
-
-
Save Konctantin/e027e53c1ecbf2f9b26c48c9077456fa to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System.Collections.Generic; | |
| using System.Data.Common; | |
| using System.Globalization; | |
| using System.Linq.Expressions; | |
| using System.Reflection; | |
| namespace System.Data.MapperExtension | |
| { | |
| /// <summary> | |
| /// Make field name for mapping. | |
| /// </summary> | |
| [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] | |
| public class SqlFieldNameAttribute : Attribute | |
| { | |
| /// <summary> | |
| /// Field name. | |
| /// </summary> | |
| public string Name { get; } | |
| /// <summary> | |
| /// Create new instance for <see cref="SqlFieldNameAttribute"/>. | |
| /// </summary> | |
| /// <param name="name">Field name.</param> | |
| public SqlFieldNameAttribute(string name) | |
| { | |
| Name = name; | |
| } | |
| } | |
| /// <summary> | |
| /// Helper class for reading data from <see cref="DbDataReader"/> to object type of <typeparamref name="T"/>. | |
| /// </summary> | |
| /// <typeparam name="T">Type for mapping from <see cref="DbDataReader"/>.</typeparam> | |
| public class DbFieldCache<T> : IDisposable where T : class, new() | |
| { | |
| private const BindingFlags BFlag = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; | |
| private delegate void Setter(T fieldType, object value); | |
| class FieldCacheInfo | |
| { | |
| internal int Index; | |
| internal TypeCode TypeCode; | |
| internal Setter Setter; | |
| } | |
| class StaticFieldCacheInfo | |
| { | |
| internal string Name; | |
| internal TypeCode TypeCode; | |
| internal Setter Setter; | |
| } | |
| private List<FieldCacheInfo> Fields { get; } = new List<FieldCacheInfo>(); | |
| private static readonly List<StaticFieldCacheInfo> staticPropertyList = new List<StaticFieldCacheInfo>(); | |
| private DbDataReader Reader { get; } | |
| private static bool IsSimpleType(TypeCode typeCode) | |
| { | |
| switch (typeCode) | |
| { | |
| case TypeCode.Empty: | |
| case TypeCode.Object: | |
| case TypeCode.DBNull: | |
| return false; | |
| default: | |
| return true; | |
| } | |
| } | |
| private static Setter GetSetter(PropertyInfo fieldInfo) | |
| { | |
| var paramExpression = Expression.Parameter(typeof(T)); | |
| var propertyExpression = Expression.Property(paramExpression, fieldInfo); | |
| var valueExpression = Expression.Parameter(typeof(object)); | |
| var convertExpression = Expression.Convert(valueExpression, fieldInfo.PropertyType); | |
| var assignExpression = Expression.Assign(propertyExpression, convertExpression); | |
| return Expression.Lambda<Setter>(assignExpression, paramExpression, valueExpression).Compile(); | |
| } | |
| static DbFieldCache() | |
| { | |
| staticPropertyList.Clear(); | |
| foreach (var property in typeof(T).GetProperties(BFlag)) | |
| { | |
| if (property.SetMethod == null) | |
| continue; | |
| var name = property.Name; | |
| // if property has attribute with fieldname. | |
| var attributes = property.GetCustomAttributes(typeof(SqlFieldNameAttribute), false); | |
| if (attributes.Length > 0) | |
| name = (attributes[0] as SqlFieldNameAttribute).Name; | |
| var type = property.PropertyType; | |
| // if it's nullable type | |
| if (Nullable.GetUnderlyingType(type) is Type utype) | |
| { | |
| type = utype; | |
| } | |
| // if it's enum, extract base type. | |
| if (type.IsEnum) | |
| { | |
| type = Enum.GetUnderlyingType(type); | |
| } | |
| var typeCode = Type.GetTypeCode(type); | |
| if (IsSimpleType(typeCode)) | |
| { | |
| var setter = GetSetter(property); | |
| var fieldInfo = new StaticFieldCacheInfo { Name = name, TypeCode = typeCode, Setter = setter }; | |
| staticPropertyList.Add(fieldInfo); | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Create new instance for <see cref="MapperExtension.DbFieldCache"/>. | |
| /// </summary> | |
| /// <param name="reader"></param> | |
| public DbFieldCache(DbDataReader reader) | |
| { | |
| Reader = reader; | |
| foreach (var property in staticPropertyList) | |
| { | |
| for (int i = 0; i < reader.FieldCount; ++i) | |
| { | |
| if (property.Name.Equals(reader.GetName(i), StringComparison.InvariantCultureIgnoreCase)) | |
| { | |
| var fieldCache = new FieldCacheInfo | |
| { | |
| Index = i, | |
| TypeCode = property.TypeCode, | |
| Setter = property.Setter | |
| }; | |
| Fields.Add(fieldCache); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Read object from current record. | |
| /// </summary> | |
| /// <returns></returns> | |
| public T Read() | |
| { | |
| var entry = new T(); | |
| foreach (var field in Fields) | |
| { | |
| if (!Reader.IsDBNull(field.Index)) | |
| { | |
| var value = Convert.ChangeType(Reader[field.Index], field.TypeCode, CultureInfo.InvariantCulture); | |
| field.Setter(entry, value); | |
| } | |
| } | |
| return entry; | |
| } | |
| /// <summary> | |
| /// Read all records from recordset. | |
| /// </summary> | |
| /// <returns></returns> | |
| public IEnumerable<T> ReadAll() | |
| { | |
| while (Reader.Read()) | |
| { | |
| var val = Read(); | |
| yield return val; | |
| } | |
| } | |
| /// <summary> | |
| /// <inheriteddoc/> | |
| /// </summary> | |
| public void Dispose() | |
| { | |
| Fields?.Clear(); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment