123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- using System.Collections.Concurrent;
- using System.ComponentModel.DataAnnotations.Schema;
- using System.Data;
- using System.Data.Common;
- using System.Linq.Expressions;
- using System.Reflection;
- using System.Runtime.CompilerServices;
- using EasyDevCore.Common;
- using Microsoft.EntityFrameworkCore.Storage;
- namespace EasyDevCore.Database
- {
- /// <summary>
- /// Mapper <see cref="DbDataReader" /> to model of type />
- /// </summary>
- /// <typeparam name="T">Model type</typeparam>
- public class DbDataReaderMapper<T> where T : class, new()
- {
- private struct Prop
- {
- public int ColumnOrdinal;
- public string ColumnName;
- public Action<object, object> Setter { get; set; }
- }
- /// <summary>
- /// Contains different columns set information mapped to type <typeparamref name="T"/>.
- /// </summary>
- private static readonly ConcurrentDictionary<ulong, Prop[]> PropertiesCache = new ConcurrentDictionary<ulong, Prop[]>();
- private readonly DbDataReader _reader;
- private readonly Prop[] _properties;
- /// <summary>
- /// Initializes a new instance of the <see cref="DbDataReaderMapper{T}"/> class.
- /// </summary>
- /// <param name="reader">The reader.</param>
- public DbDataReaderMapper(RelationalDataReader reader) : this(reader.DbDataReader)
- {
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="DbDataReaderMapper{T}" /> class.
- /// </summary>
- /// <param name="reader">The reader.</param>
- public DbDataReaderMapper(DbDataReader reader)
- {
- _reader = reader;
- _properties = MapColumnsToProperties();
- }
- /// <summary>
- /// Gets the rows.
- /// </summary>
- /// <returns></returns>
- public IEnumerable<T> GetRows()
- {
- while (_reader.Read())
- {
- yield return MapNextRow();
- }
- }
- /// <summary>
- /// Gets the rows asynchronous.
- /// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns></returns>
- public async IAsyncEnumerable<T> GetRowsAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- while (await _reader.ReadAsync(cancellationToken).ConfigureAwait(false))
- {
- yield return await MapNextRowAsync().ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Map <see cref="DbDataReader"/> to a T and apply an action on it for each row
- /// </summary>
- /// <param name="action">Action to apply to each row</param>
- public void Map(Action<T> action)
- {
- while (_reader.Read())
- {
- T row = MapNextRow();
- action(row);
- }
- }
- /// <summary>
- /// Map <see cref="DbDataReader"/> to a T and apply an action on it for each row
- /// </summary>
- /// <param name="action">Action to apply to each row</param>
- public Task MapAsync(Action<T> action)
- {
- return MapAsync(action, CancellationToken.None);
- }
- /// <summary>
- /// Map <see cref="DbDataReader"/> to a T and apply an action on it for each row
- /// </summary>
- /// <param name="action">Action to apply to each row</param>
- /// <param name="cancellationToken">The cancellation instruction, which propagates a notification that operations should be canceled</param>
- public async Task MapAsync(Action<T> action, CancellationToken cancellationToken)
- {
- while (await _reader.ReadAsync(cancellationToken).ConfigureAwait(false))
- {
- T row = await MapNextRowAsync(cancellationToken).ConfigureAwait(false);
- action(row);
- }
- }
- /// <summary>
- /// Maps the next row.
- /// </summary>
- /// <returns></returns>
- public T MapNextRow()
- {
- T row = new T();
- for (int i = 0; i < _properties.Length; ++i)
- {
- object value = _reader.IsDBNull(_properties[i].ColumnOrdinal) ? null : _reader.GetValue(_properties[i].ColumnOrdinal);
- try
- {
- _properties[i].Setter(row, value);
- }
- catch (Exception ex)
- {
- ex.Data["Mapping"] = $"Model[{_properties[i].ColumnName}] = Column[{_reader.GetName(_properties[i].ColumnOrdinal)}]";
- throw;
- }
- }
- return row;
- }
- /// <summary>
- /// Maps the next row asynchronous.
- /// </summary>
- /// <returns></returns>
- public Task<T> MapNextRowAsync()
- {
- return MapNextRowAsync(CancellationToken.None);
- }
- /// <summary>
- /// Maps the next row asynchronous.
- /// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns></returns>
- public async Task<T> MapNextRowAsync(CancellationToken cancellationToken)
- {
- T row = new T();
- for (int i = 0; i < _properties.Length; ++i)
- {
- object value = await _reader.IsDBNullAsync(_properties[i].ColumnOrdinal, cancellationToken).ConfigureAwait(false)
- ? null
- : _reader.GetValue(_properties[i].ColumnOrdinal);
- try
- {
- _properties[i].Setter(row, value);
- }
- catch (Exception ex)
- {
- ex.Data["Mapping"] = $"Column[{_reader.GetName(_properties[i].ColumnOrdinal)}] = Model[{_properties[i].ColumnName}]";
- throw;
- }
- }
- return row;
- }
- private Prop[] MapColumnsToProperties()
- {
- Type modelType = typeof(T);
- string[] columns = new string[_reader.FieldCount];
- Type[] colTypes = new Type[_reader.FieldCount];
- for (int i = 0; i < _reader.FieldCount; ++i)
- {
- columns[i] = _reader.GetName(i);
- colTypes[i] = _reader.GetFieldType(i);
- }
- ulong propKey = CheckSumHelper.CRC64(modelType.FullName + "|" + string.Join("|", columns) + "|" + string.Join("|", colTypes.Select(c => c.FullName)));
- if (PropertiesCache.TryGetValue(propKey, out var s))
- {
- return s;
- }
- var propertyInfos = modelType.GetProperties(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanWrite && !p.GetCustomAttributes<NotMappedAttribute>().Any())
- .Select(p => new { Property = p, Mapping = p.GetCustomAttribute<ColumnAttribute>() });
- var mappingFields = (from c in columns.Select((v, i) => new { Index = i, Name = v, ColType = colTypes[i] })
- from p in propertyInfos
- where c.Name.Equals(p.Mapping != null ? p.Mapping.Name : p.Property.Name, StringComparison.InvariantCultureIgnoreCase)
- select new { c.Index, p.Property, p.Mapping, c.ColType });
- var properties = new List<Prop>();
- ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
- ParameterExpression value = Expression.Parameter(typeof(object), "value");
- //MethodInfo changeTypeMethod = typeof(Convert).GetMethod("ChangeType", new[] { typeof(object), typeof(Type) });
- foreach (var fi in mappingFields)
- {
- PropertyInfo prop = fi.Property;
- // "x as T" is faster than "(T) x" if x is a reference type
- UnaryExpression instanceCast = prop.DeclaringType.IsValueType ? Expression.Convert(instance, prop.DeclaringType) : Expression.TypeAs(instance, prop.DeclaringType);
- UnaryExpression valueCast = prop.PropertyType.IsValueType ? Expression.Convert(value, prop.PropertyType) : Expression.TypeAs(value, prop.PropertyType); MethodCallExpression setterCall = Expression.Call(instanceCast, prop.GetSetMethod(), valueCast);
- var setter = (Action<object, object>)Expression.Lambda(setterCall, instance, value).Compile();
- Action<object, object> setterConvert = null;
- if (prop.PropertyType != fi.ColType)
- {
- setterConvert = ((Expression<Action<object, object>>)((o, v) => setter(o, v == null ? null : Convert.ChangeType(v, fi.Property.PropertyType)))).Compile();
- }
- properties.Add(new Prop
- {
- ColumnOrdinal = fi.Index,
- ColumnName = prop.Name,
- Setter = setterConvert == null ? setter : setterConvert
- });
- }
- Prop[] propertiesArray = properties.ToArray();
- PropertiesCache[propKey] = propertiesArray;
- return propertiesArray;
- }
- }
- }
|