Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create overload Query<T> to customize the mapper #360

Closed
mdissel opened this issue Oct 20, 2015 · 7 comments
Closed

create overload Query<T> to customize the mapper #360

mdissel opened this issue Oct 20, 2015 · 7 comments

Comments

@mdissel
Copy link

mdissel commented Oct 20, 2015

Add a new overload to Query with an optional parameter Func<IDataRecord, T, T> map
In this function the caller can modify the default mapping with access to the IDataRecord and the result object T

Something like this:

public static IEnumerable<T> Query<T>(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType, Func<IDataRecord, T, T> map
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null, Func<IDataRecord, T, T> map = null
#endif
) {
@mrinalkamboj
Copy link

There are multiple ways to achieve it, we do not need this modification ideally:

  1. Try out Dapper.FluentMap, which allows to create a Fluent Mapping for a given entity, so the models are attribute free
  2. Following is the custom code for attribute based mapping of entity with the Database result:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Dapper
{
    /// <summary>
    /// Uses the Name value of the <see cref="ColumnAttribute"/> specified to determine
    /// the association between the name of the column in the query results and the member to
    /// which it will be extracted. If no column mapping is present all members are mapped as
    /// usual.
    /// </summary>
    /// <typeparam name="T">The type of the object that this association between the mapper applies to.</typeparam>
    public class ColumnAttributeTypeMapper<T> : FallbackTypeMapper
    {
        public ColumnAttributeTypeMapper()
            : base(new SqlMapper.ITypeMap[]
                {
                    new CustomPropertyTypeMap(
                       typeof(T),
                       (type, columnName) =>
                           type.GetProperties().FirstOrDefault(prop =>
                               prop.GetCustomAttributes(false)
                                   .OfType<ColumnAttribute>()
                                   .Any(attr => attr.Name == columnName)
                               )
                       ),
                    new DefaultTypeMap(typeof(T))
                })
        {
        }
    }

    /// <summary>
    /// 
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
    public class ColumnAttribute : Attribute
    {
        public string Name { get; set; }
    }

    /// <summary>
    /// 
    /// </summary>
    public class FallbackTypeMapper : SqlMapper.ITypeMap
    {
        private readonly IEnumerable<SqlMapper.ITypeMap> _mappers;

        public FallbackTypeMapper(IEnumerable<SqlMapper.ITypeMap> mappers)
        {
            _mappers = mappers;
        }


        public ConstructorInfo FindConstructor(string[] names, Type[] types)
        {
            foreach (var mapper in _mappers)
            {
                try
                {
                    ConstructorInfo result = mapper.FindConstructor(names, types);
                    if (result != null)
                    {
                        return result;
                    }
                }
                catch (NotImplementedException)
                {
                }
            }
            return null;
        }

        public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
        {
            foreach (var mapper in _mappers)
            {
                try
                {
                    var result = mapper.GetConstructorParameter(constructor, columnName);
                    if (result != null)
                    {
                        return result;
                    }
                }
                catch (NotImplementedException)
                {
                }
            }
            return null;
        }

        public SqlMapper.IMemberMap GetMember(string columnName)
        {
            foreach (var mapper in _mappers)
            {
                try
                {
                    var result = mapper.GetMember(columnName);
                    if (result != null)
                    {
                        return result;
                    }
                }
                catch (NotImplementedException)
                {
                }
            }
            return null;
        }


        public ConstructorInfo FindExplicitConstructor()
        {
            return _mappers
                .Select(mapper => mapper.FindExplicitConstructor())
                .FirstOrDefault(result => result != null);
        }
    }
}

@NickCraver
Copy link
Member

@mrinalkamboj tip on the issues: use code fences to get awesome highlighting, check out your updated comment to see my update, like this:

```C#
public string MyProp { get; set; }
```

@mrinalkamboj
Copy link

@NickCraver Thanks, will be careful from next time onward

@mdissel
Copy link
Author

mdissel commented Oct 27, 2015

In my case i want to map multiple fields into a single dictionary, so for each a record i just want to execute an action to modify the result object..

@mrinalkamboj
Copy link

@mdissel IMO that has to be a custom code, not sure if it would be feasible to provide the same Out of box, though @NickCraver can suggest better

@NickCraver
Copy link
Member

See the proposal above about extending the mapper, this would allow you to change the mapping for the type in a central place, hopefully covering this use case as well.

@NickCraver
Copy link
Member

I'm going to close this one out because I don't think such an API will happen in this form. Mainly because Query is simply the wrong place, and we cache the deserializer where this is affected. It'd be much better to plug in where the deserializer generation happens - we'll work on opening up the TypeMapper a bit more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants