Dapper Mapping не относится к соответствующему конструктору - PullRequest
0 голосов
/ 29 января 2020

Я запускаю тест интеграции БД, который записывает, а затем читает запись рецепта из таблицы данных PostgreSQL 10. Я получаю сообщение об ошибке:

System.InvalidOperationException: конструктор по умолчанию без параметров или одна совпадающая подпись (System.Guid globalid, имя System.String, ингредиенты System.Array, инструкции System.Array, System. Для материализации Recipes.Recipe требуется источник строки, персонализированный System.Int32, заметки System.Array, теги System.Array *

Если я добавлю конструктор без параметров (publi c, приватный или внутренний) ), тест проходит. Я, конечно, могу это сделать (это закомментировано в приведенном ниже коде), но эта проблема озадачивает меня, потому что, насколько я могу судить, у меня есть конструктор, соответствующий указанной подписи.

Я пробовал несколько вещей:

Изменение элементов подписи с IEnumerable на string []

Изменение свойств IReadOnlyCollection на свойства List.

Изменение PeopleServed от int? int.

Изменение регистра аргументов в соответствии с нижним регистром, указанным в сообщении об ошибке.

Публикация конструктора c.

Предоставление коллекции IReadOnlyCollection. Свойства внутренних сеттеров.

Добавление Newtonsoft и добавление атрибута [JsonConstructor].

Изменение порядка появления конструкторов.

Понижение версии Dapper.

Что мне не хватает?

Таблица выглядит так:

    Column     |   Type    | Collation | Nullable |                           Default
---------------+-----------+-----------+----------+--------------------------------------------------------------
 global_id     | uuid      |           | not null |
 name          | text      |           | not null |
 source        | text      |           |          |
 ingredients   | text[]    |           | not null |
 instructions  | text[]    |           | not null |
 notes         | text[]    |           |          |
 people_served | integer   |           |          |
 tags          | text[]    |           |          |
 sys_period    | tstzrange |           | not null | tstzrange(CURRENT_TIMESTAMP, NULL::timestamp with time zone)
Indexes:
    "recipes_pkey" PRIMARY KEY, btree (global_id)

SQL для создания таблицы:

CREATE TABLE IF NOT EXISTS public.recipes
(
    global_id UUID NOT NULL PRIMARY KEY,
    name TEXT NOT NULL,
    source TEXT NULL,
    ingredients TEXT[] NOT NULL,
    instructions TEXT[] NOT NULL,
    notes TEXT[] NULL,
    people_served INT NULL,
    tags TEXT[] NULL,
    sys_period tstzrange NOT NULL DEFAULT tstzrange(current_timestamp, null)
);

My SQL оператор :

SELECT 
global_id AS GlobalId,
name,
ingredients,
instructions,
source,
people_served AS PeopleServed,
notes,
tags
FROM public.recipes

Мой класс рецептов:

public class Recipe : EntityBase, IEquatable<Recipe>
{
    public Recipe(string name,
        IEnumerable<string> ingredients,
        IEnumerable<string> instructions,
        string source = default,
        int? peopleServed = default,
        IEnumerable<string> notes = default,
        IEnumerable<string> tags = default) : this(globalId: Guid.NewGuid(),
            name: name,
            source: source,
            peopleServed: peopleServed,
            ingredients: ingredients,
            instructions: instructions,
            notes: notes,
            tags: tags)
    { }

    // internal Recipe() { }

    internal Recipe(Guid globalId,
        string name,
        IEnumerable<string> ingredients,
        IEnumerable<string> instructions,
        string source = default,
        int? peopleServed = default,
        IEnumerable<string> notes = default,
        IEnumerable<string> tags = default) : base(globalId: globalId)
    {
        Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentNullException(nameof(name)) : name;
        Ingredients = (ingredients?.Any() ?? false) ? new ReadOnlyCollection<string>(ingredients.ToList()) : throw new ArgumentNullException(nameof(ingredients));
        Instructions = (instructions?.Any() ?? false) ? new ReadOnlyCollection<string>(instructions.ToList()) : throw new ArgumentNullException(nameof(instructions));
        Notes = new ReadOnlyCollection<string>(notes?.ToList() ?? new List<string>());
        Tags = new ReadOnlyCollection<string>(tags?.ToList() ?? new List<string>());
        Source = source;
        PeopleServed = peopleServed;
    }

    public string Name { get; internal set; }
    public string Source { get; internal set; }
    public int? PeopleServed { get; internal set; }
    public IReadOnlyCollection<string> Ingredients { get; }
    public IReadOnlyCollection<string> Instructions { get; }
    public IReadOnlyCollection<string> Notes { get; }
    public IReadOnlyCollection<string> Tags { get; }
    public override bool Equals(object obj)
    {
        return Equals(obj as Recipe);
    }
    public bool Equals(Recipe other)
    {
        return other != null &&
               GlobalId.Equals(other.GlobalId) &&
               Name == other.Name &&
               Source == other.Source &&
               PeopleServed == other.PeopleServed &&
               Ingredients.SequenceEqual(other.Ingredients) &&
               Instructions.SequenceEqual(other.Instructions) &&
               Notes.SequenceEqual(other.Notes) &&
               Tags.SequenceEqual(other.Tags);
    }
    public override int GetHashCode()
    {
        var hashCode = 1374191493;
        hashCode = hashCode * -1521134295 + GlobalId.GetHashCode();
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name);
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Source);
        hashCode = hashCode * -1521134295 + PeopleServed.GetHashCode();
        hashCode = hashCode * -1521134295 + EqualityComparer<IReadOnlyCollection<string>>.Default.GetHashCode(Ingredients);
        hashCode = hashCode * -1521134295 + EqualityComparer<IReadOnlyCollection<string>>.Default.GetHashCode(Instructions);
        hashCode = hashCode * -1521134295 + EqualityComparer<IReadOnlyCollection<string>>.Default.GetHashCode(Notes);
        hashCode = hashCode * -1521134295 + EqualityComparer<IReadOnlyCollection<string>>.Default.GetHashCode(Tags);
        return hashCode;
    }
    public override string ToString()
    {
        return Name;
    }
}

Базовый класс:

public abstract class EntityBase : IEquatable<EntityBase>
{
    public Guid GlobalId { get; protected set; }

    protected EntityBase() : this(Guid.NewGuid()) { }

    protected EntityBase(Guid globalId)
    {
        if (globalId.Equals(Guid.Empty)) { throw new ArgumentException($"{nameof(globalId)} cannot be an empty Guid."); }
        GlobalId = globalId;
    }

    public override string ToString()
    {
        return GlobalId.ToString();
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as EntityBase);
    }

    public bool Equals(EntityBase other)
    {
        return other != null &&
               GlobalId.Equals(other.GlobalId);
    }

    public override int GetHashCode()
    {
        return -2093202133 + GlobalId.GetHashCode();
    }
}

Неудачный тест (который проходит с конструктором по умолчанию):

[Fact]
public async Task InsertAndFetchRecipeAsync()
{
    var recipe = new Recipe(name: "Test",
        ingredients: new List<string>() { "cucumber", "tomato" },
        instructions: new List<string>() { "Chop up", "Mix together", "Season" },
        source: nameof(InsertAndFetchRecipeAsync),
        peopleServed: 1,
        notes: new List<string>() { "Test recipe", "Yummy" },
        tags: new List<string>() { "vegan", "WFPB" });

    var recipes = new Recipes(fixture.ConnectionString);

    await recipes.SaveRecipeAsync(recipe).ConfigureAwait(false);

    var fromDb = await recipes.GetRecipeAsync(recipe.GlobalId).ConfigureAwait(false);

    Assert.Equal(recipe, fromDb);

    recipe = new Recipe(name: "Test",
        ingredients: new List<string>() { "cucumber", "tomato" },
        instructions: new List<string>() { "Chop up", "Mix together", "Season" },
        source: null,
        peopleServed: null,
        notes: null,
        tags: null);

    await recipes.SaveRecipeAsync(recipe).ConfigureAwait(false);

    fromDb = await recipes.GetRecipeAsync(recipe.GlobalId).ConfigureAwait(false);

    Assert.Equal(recipe, fromDb);
}

Кто-нибудь? Что тут происходит? Помощь всегда ценится.

...