Вставить отдельную запись в таблицу с внешним ключом, используя Entity Framework - PullRequest
0 голосов
/ 08 мая 2019

Это моя модель, и я использую Entity Framework с подходом, основанным на коде.

public class Respondent
{
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int RespondentId { get; set; }
        public User Requester { get; set; }

        public User Provider { get; set; }
        public string Role { get; set; }
}

public class User: IUser
{
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int UserId { get; set; }
        public int UPI { get; set; }
        public string Name { get; set; }
}

и это отношение обеих сгенерированных таблиц.

То, что я хочу, это если пользователь существует в таблице User, тогда не создавайте другую запись пользователя.

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

private Context db = new Context();

public async Task<IHttpActionResult> PostRespondent(Respondent respondent)
{
        var TempProvidersList = respondent.Providers.ToList();

        try
        {
            var requester = db.Users.Single(s => s.UserId == respondent.Requester.UserId);
            requester.IsPublic = false;
            var providerIds = respondent.Providers.Select(x => x.UPI).ToList();

            foreach (var providerId in providerIds)
            {
                if (!db.Respondents.Any(x => x.Requester.UserId == requester.UserId && x.Provider.UPI == providerId))
                {
                    var provider = respondent.Providers.Single(x => x.UPI == providerId);
                    provider.IsPublic = true;

                    db.Respondents.Add(new Respondent() { Requester = requester, Provider = provider, Role = provider.Role });
                }
            }

            db.SaveChanges();
        }
        catch (Exception ex)
        {
        }

        return CreatedAtRoute("DefaultApi", new { id = respondent.RespondentId }, respondent);
}

1 Ответ

2 голосов
/ 08 мая 2019

Я полагаю, что проблема связана с передачей сущностей, которые были загружены другим DbContext, и последующим добавлением ссылок на эти сущности в новом DbContext.

Этот код не имеет особого смысла:

var res = db.Respondents
    .Include(user => user.Requester)
    .Include(user => user.Provider)
    .Where(o => o.Requester.UserId == requester.UserId ).ToList();

Где вы не используете "res" где-либо в коде. Вы используете «а», что, как я полагаю, означает одно и то же? Во-первых, если вы просто хотите проверить наличие строки, используйте Any()

if (!db.Respondents.Any(x => x.Requester.UserId == requester.UserId)) 

Вместо загрузки всех доступных объектов, просто чтобы проверить, существуют ли некоторые из них.

Проблемная область, которую я вижу, такова:

tempProvider = TempProvidersList[i];
db.Respondents.Add(new Respondent() { Requester = requester, Provider = tempProvider, Role = TempProvidersList[i].Role });

Это установка ссылки вашего провайдера в новом Ответчике на Пользователя (провайдера), который, вероятно, был загружен из другого экземпляра DbContext.

Для решения этой проблемы любые ссылки, которые вы устанавливаете, должны быть загружены из текущего DbContext:

var providerIds = respondent.Providers.Select(x => x.UPI).ToList();
var providers = db.Users.Where(x => providerIds.Contains(x.UPI)).ToList();

var requester = db.Users.Single(s => s.UserId == respondent.Requester.UserId);
requester.IsPublic = false;
foreach(var providerId in providerIds)
{
   if (!db.Respondents.Any(x => x.Requester.UserId == requester.UserId)) 
   {
       var provider = providers.Single(x => x.UPI == providerId);
       provider.IsPublic = true;

       db.Respondents.Add(new Respondent() { Requester = requester, Provider = provider, Role = provider.Role });
   }
}
db.SaveChanges();

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

Я бы обернул это блоком try / catch и обработал исключения. Возможные исключения, которые приходят на ум, - это когда пользователь не найден для ProviderId. (Могут ли пользователи быть удалены?) Я сделал foreach по идентификаторам, а не по загруженным ссылкам, потому что в этой коллекции может не быть сущности для каждого идентификатора на основании вышеуказанного условия. (если только 5 из 6 идентификаторов загружают пользователя провайдера, в коллекции идентификаторов будет 6 элементов, а в коллекции сущностей будет только 5)

Еще один момент здесь - использовать Single вместо FirstOrDefault, где вы ожидаете одну запись. Это обеспечивает соблюдение этого правила и генерирует исключение, если существует более одной или нет совпадающих записей. Варианты «OrDefault» следует использовать только в том случае, если поиск строки не является ожидаемым результатом. FirstOrDefault скроет проблемы, если в БД попадет более одного совпадения, и без предложения «OrderBy» вы не сможете рассчитывать, какая ссылка будет возвращена.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...