Требуемый столбец отсутствовал в результатах операции FromSql, и [NotMapped] не помог

Я использую EF Core 3.0 для извлечения данных из хранимой процедуры SQL Server в свою объектную модель с именем Recipient. Модель Recipient имеет несколько определенных свойств, EnvelopeId и Name, которые не возвращаются из хранимой процедуры. Когда я вызываю хранимую процедуру с помощью FromSqlInterpolated, я получаю сообщение об ошибке

Обязательный столбец EnvelopeId1 отсутствовал в результатах операции FromSql.

Из того, что я прочитал в документации EF Core, я должен иметь возможность добавить атрибут [NotMapped] к этим свойствам, и EF Core должен иметь возможность игнорировать их при чтении или записи в базу данных, верно? К сожалению, у меня это не работает, и я получаю указанную выше ошибку.

Если я добавлю два столбца в хранимую процедуру и удалю атрибут [NotMapped] в своей объектной модели, все будет работать нормально. Это доказывает, что другие мои имена столбцов / свойств совпадают правильно и нигде нет опечаток.

Я видел предложения использовать DbQuery вместо DbSet, но он устарел, поэтому я не хочу его использовать. Я видел предложения об использовании двух разных объектных моделей, одна со свойствами, точно соответствующими результирующему набору хранимой процедуры, но это просто приводит к множеству дополнительных моделей. Что я здесь делаю не так?

Моя объектная модель:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MyNamespace.Models
{
    public class Recipient
    {
        [Key]
        public long RecipientId { get; set; }
        [NotMapped]
        public Guid? EnvelopeId { get; set; }
        public string Type { get; set; }
        public string UserId { get; set; }
        public string Email { get; set; }
        [NotMapped]
        public string Name { get; set; }
        public string RoleName { get; set; }
        public int RoutingOrder { get; set; }
        public string Status { get; set; }
        public DateTime StatusDate { get; set; }

        public Recipient() { }
    }
}

Мой контекст БД:

using Microsoft.EntityFrameworkCore;
using MyNamespace.Models;

namespace MyNamespace.Data
{
    public class MyDbContext : DbContext
    {
        public virtual DbSet<Recipient> Recipient { get; set; }
        public virtual DbSet<Envelope> Envelope { get; set; }
        public virtual DbSet<Template> Template { get; set; }

        public MyDbContext (DbContextOptions<MyDbContext> options) : base(options)   
        { }
    }
}

Мой код ошибки:

FormattableString sql = $"EXEC dbo.MyStoredProcedure @Param1={param1}, @Param2={param2}";
var result = await MyDbContext.Recipient.FromSqlInterpolated(sql).ToListAsync();

Изменить №1 ...

Envelope объектная модель:

public class Envelope
{
    [Key]
    public Guid EnvelopeId { get; set; }
    public string Status { get; set; }
    public DateTime StatusDate { get; set; }
    public DateTime StatusPollDate { get; set; }
    [NotMapped]
    public List<Recipient> Recipients { get; set; }

    public Envelope() {
        Recipients = new List<Recipient>();
        StatusPollDate = DateTime.Parse("1753-01-01");
    }
}

Схема набора результатов хранимой процедуры:

[RecipientId] bigint,
[Type] varchar(20),
[UserId] varchar(50),
[Email] varchar(100),
[RoleName] varchar(100),
[RoutingOrder] int,
[Status] varchar(13),
[StatusDate] datetime

Прочитав некоторые ответы и комментарии, я понял, что свойство shadow создается EF, поскольку он распознает, что Recipient является дочерним по отношению к объекту Envelope. Честно говоря, мне действительно следует включить EnvelopeId в набор результатов (и удалить [NotMapped]), чтобы успокоить EF Core. Единственная причина, по которой я не возвращал EnvelopeId в результирующем наборе хранимой процедуры, заключалась в том, что я передал его в качестве входного параметра для начала и подумал, что он тратит сетевые ресурсы, чтобы передать что-то, а затем вернуть это в каждом результат установлен рекорд.

Теперь я не знаю, откуда взялся EnvelopeId1, но именно об этом упоминается в сообщении об ошибке. Как вы можете видеть в моих объектных моделях и схеме набора результатов хранимых процедур, у меня всегда есть ссылки только на EnvelopeId. Я предполагаю, что, когда EF Core решил создать свойство shadow, он не смог использовать EnvelopeId, поскольку он уже был у меня в моей объектной модели, поэтому он создал свойство с именем EnvelopeId1, а затем ожидал, что он будет присутствовать в наборе результатов хранимая процедура.


person ConfuedProblemSolver    schedule 04.11.2019    source источник
comment
EnvelopeId звучит как ключ к кладовке, поэтому у вас возникнут проблемы, если вы сделаете его необязательным. Кроме того, у вас есть несоответствие именования, как указал тымтам.   -  person Christopher    schedule 05.11.2019
comment
@tymtam, это не опечатка. У меня нет EnvelopeId1 определено где-либо в моем коде или в наборе результатов хранимой процедуры, но EnvelopeId1 - это то, что показывает сообщение об ошибке.   -  person ConfuedProblemSolver    schedule 05.11.2019
comment
@Christopher, да EnvelopeId концептуально является первичным ключом для другого объекта (Envelope), но в соответствии с тем, как я определил свою объектную модель Recipient, я называю его только Guid?, поэтому он не потребуется. Но ваш комментарий заставляет меня думать, что он может иметь какое-то отношение к типу данных Guid?. Я пробовал нечто подобное с типом данных long, и атрибут [NotMapped] работал точно так, как ожидалось.   -  person ConfuedProblemSolver    schedule 05.11.2019
comment
Вам необходимо опубликовать свою схему для Envelope, а также образец вывода SP. То, что вы считаете, что свойство не требуется, не означает, что оно не требуется. :)   -  person Chris Schaller    schedule 05.11.2019
comment
Почему Recipients в Envelope помечено как не сопоставлено? Вы не хотите, чтобы ограничение ForeignKey в базе данных? Похоже, вы могли создать схему базы данных до того, как пометили Recipients как не сопоставлен и с тех пор не меняли схему базы данных.   -  person Chris Schaller    schedule 07.11.2019


Ответы (1)


Это сообщение об ошибке вызвано двумя причинами:

Обязательный столбец EnvelopeId1 отсутствовал в результатах операции FromSql.

Очевидно, что для каждого столбца в наборе результатов SQL должно быть соответствующее свойство в графе объектов, который вы пытаетесь гидратировать, в этом случае ваш класс Recipient не имеет EnvelopeId1.

Поскольку ваш запрос является хранимой процедурой, было бы разумно опубликовать результат SQL в подобных вопросах, поскольку он покажет структуру SQL, которую вы пытаетесь сопоставить с графом объекта.

Причина того, что EnvelopeId1 находится в ваших таблицах SQL, но не в графе объектов, заключается в том, что Entity Framework создала Свойство тени для foreign, для которого вы не определили свойство.

Вы не указали схему для Envelope, но мои деньги говорят, что у вас есть эквивалентное свойство ICollection<Recipient>.

Создание свойства NotMapped для EnvelopeId просто делает эту схему более запутанной, вместо этого мы должны сопоставить ее должным образом, или вы должны исключить ее из выходных данных хранимой процедуры.

Если вы собираетесь использовать FromSql вариантов в EF (.Net или Core), вам всегда следует определять свойства навигации в классах схемы как столбцы внешнего ключа Shadow или Auto в база данных вызовет проблемы, если вы явно не опустите столбцы, которые существуют в базе данных, но не в вашей схеме

Это означает, что SELECT * вам не нужно будет явно определять все столбцы.

Решение 1. Измените хранимую процедуру
Вы можете просто изменить хранимую процедуру, чтобы не возвращать EvelopeId1 из таблицы получателей.

Решение 2. Полностью определите свойства для обоих концов всех отношений в ваших классах данных
Здесь я использовал EnvelopeId1 в качестве имени, чтобы никаких миграций не требовалось, однако я бы порекомендовал вам изменить это на EnvelopeId , Я лично использую наличие любого поля в базе данных, которое заканчивается цифрой, как указание на то, что некоторые из моих отношений были неправильно определены.

public class Recipient
{
    [Key]
    public long RecipientId { get; set; }
    public Guid? EnvelopeId1 { get; set; }
    [ForeignKey(nameof(EnvelopeId1))]
    public virtual Envelope Envelope { get; set; }
    public string Type { get; set; }
    public string UserId { get; set; }
    public string Email { get; set; }
    [NotMapped]
    public string Name { get; set; }
    public string RoleName { get; set; }
    public int RoutingOrder { get; set; }
    public string Status { get; set; }
    public DateTime StatusDate { get; set; }

    public Recipient() { }
}

Это основано на предположении, что ваш класс Envelope имеет определение, подобное этому частичному определению:

public class Envelope
{
    [Key]
    public Guid EnvelopeId { get; set; }
    public virtual ICollection<Recipient> Recipients { get; set; } = new Hashset<Recipient>();
    ...
}
person Chris Schaller    schedule 04.11.2019