ormdictionarydapper

Issue about dapper


public class Profile
{
    public int ID { get; set; }

    public string Name { get; set; }

    public string Phone { get; set; }

    public string Address { get; set; }

    public ExtraInfo Extra { get; set; }
}

public class Topic
{
    public int ID { get; set; }

    public string Title { get; set; }

    public DateTime CreateDate { get; set; }

    public string Content { get; set; }

    public int UID { get; set; }

    public int TestColum { get; set; }

    public string Name { get; set; }

    public Profile Author { get; set; }

    public Attachment Attach { get; set; }
}

correct:

var list = conn.Query<Topic, Profile, Topic>(
            @"select top 3 
                T.ID,
                T.Title,
                T.CreateDate,
                P.Phone,
                P.Name
            from Topic as T
            inner join Profile P on T.UID = P.ID",
            (T, P) => { T.Author = P; return T; },
            null,
            null,
            true,
            "Phone");

throw a exception at SqlMapper.cs line 2177:

var list = conn.Query<Topic, Profile, Topic>(
            @"select top 3 
                T.ID,
                T.Title,
                T.CreateDate,
                P.Name,
                P.Phone
            from Topic as T
            inner join Profile P on T.UID = P.ID",
            (T, P) => { T.Author = P; return T; },
            null,
            null,
            true,
            "Name");

Now,i delete the Topic's property "Name",that will be correct.

I think the key is at SqlMapper.cs line 1153

int current = 0;
var splits = splitOn.Split(',').ToArray();
var splitIndex = 0;

Func<Type, int> nextSplit = type =>
{
    var currentSplit = splits[splitIndex].Trim();
    if (splits.Length > splitIndex + 1)
    {
        splitIndex++;
    }

    bool skipFirst = false;
    int startingPos = current + 1;
    // if our current type has the split, skip the first time you see it. 
    if (type != typeof(Object))
    {
        var props = DefaultTypeMap.GetSettableProps(type);
        var fields = DefaultTypeMap.GetSettableFields(type);

        foreach (var name in props.Select(p => p.Name).Concat(fields.Select(f => f.Name)))
        {
            if (string.Equals(name, currentSplit, StringComparison.OrdinalIgnoreCase))
            {
                skipFirst = true;
                startingPos = current;
                break;
            }
        }

    }

    int pos;
    for (pos = startingPos; pos < reader.FieldCount; pos++)
    {
        // some people like ID some id ... assuming case insensitive splits for now
        if (splitOn == "*")
        {
            break;
        }
        if (string.Equals(reader.GetName(pos), currentSplit, StringComparison.OrdinalIgnoreCase))
        {
            if (skipFirst)
            {
                skipFirst = false;
            }
            else
            {
                break;
            }
        }
    }
    current = pos;
    return pos;
};

"if our current type has the split, skip the first time you see it. "

When "current type" have a property that name equal "split",but we didn't select this field from db,dapper will throw a exception.

This is a design issue, or I am not used correctly?


Solution

  • Based on your edit, it does indeed look like this is a scenario that should be handled better; it would be worth logging this as a bug on the project site - as this is quite subtle, and deciding the right way to fix this requires some thought.

    It looks like it should work, and I'm having difficulty making it fail with the code you show - the following works fine (tested with the 1.13 codebase):

    public void TestSplitWithMissingMembers()
    {
        var result = connection.Query<Topic, Profile, Topic>(
        @"select 123 as ID, 'abc' as Title,
                 cast('01 Feb 2013' as datetime) as CreateDate,
                 'def' as Phone, 'ghi' as Name",
        (T, P) => { T.Author = P; return T; },
        splitOn: "Phone").Single();
    
        result.ID.Equals(123);
        result.Title.Equals("abc");
        result.CreateDate.Equals(new DateTime(2013, 2, 1));
        result.Name.IsNull();
        result.Content.IsNull();
    
        result.Author.Phone.Equals("def");
        result.Author.Name.Equals("ghi");
        result.Author.ID.Equals(0);
        result.Author.Address.IsNull();
    }
    

    Note I added:

    public Profile Author { get; set; }
    

    to Topic, but otherwise the code is identical. Is there any chance the issue has changed between your actual code and your example code? Happy to investigate, but I need to know I'm looking at the right thing.