【问题标题】:Insert distinct record into table with foreign key using Entity Framework使用实体框架将不同的记录插入具有外键的表中
【发布时间】:2019-09-25 14:04:54
【问题描述】:

这些是我的模型,我正在使用具有代码优先方法的实体框架。

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; }
}

这是两个生成表的关系。

我想要的是如果用户存在于用户表中,那么不要创建另一个用户条目。

问题是当我添加一个新的提供者(用户类型)时,每次它都会在用户中创建一个新条目,即使用户存在;我想要的是在受访者表中添加该用户的引用。

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);
}

【问题讨论】:

    标签: c# asp.net entity-framework asp.net-web-api


    【解决方案1】:

    我认为问题是由于传递了由不同 DbContext 加载的实体,然后在新的 DbContext 中添加了对这些实体的引用。

    这里的代码没有多大意义:

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

    您不在代码中的任何地方使用“res”。您确实使用了“a”,我认为它是同一回事吗? 首先,如果你只想检查一行是否存在,使用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();
    

    我选择根据 ID 一次性加载所有适用的提供程序,而不是在循环内一次加载一个。在循环中,我只是尝试从加载的集合中检索与 ID 匹配的那个。

    我会用 try/catch 块包装它并处理异常。可能会想到的例外情况是没有为 ProviderId 找到用户。 (可以删除用户吗?)我对 ID 而不是加载的引用进行了 foreach,因为根据上述条件,该集合可能没有每个 ID 的实体。 (如果 6 个 ID 中只有 5 个加载提供者用户,则 ID 集合将有 6 个元素,但实体集合将只有 5 个)

    这里的另一点是使用Single 而不是FirstOrDefault 您期望一条记录。这会强制执行该规则,如果有多个或没有匹配记录,则会引发异常。只有在没有找到行是预期结果时才应使用“OrDefault”变体。 FirstOrDefault 将在多个匹配项进入数据库时​​隐藏问题,并且如果没有“OrderBy”子句,您将无法依赖将返回哪个引用。

    【讨论】:

    • “受访者”来自客户。就 EF 而言,它是一个 POCO 类,而不是一个被跟踪的实体。引用“TempProvidersList”中的任何实体或其相关类将被视为全新实体,从而导致插入重复项。这是一个为什么在客户端和服务器之间传递实体是一个坏主意的例子。在你的方法中尝试上面的代码,看看你是否仍然得到重复。
    • 您能否将您当前的代码作为对原始问题的编辑附加到您的原始问题中?
    • var provider = respondent.Providers.Single(x =&gt; x.UPI == providerId); 这仍在从您传入的受访者中拉出提供者。使用`var providers = db.Users.Where(x => providerIds.Contains(x.UserId)).ToList();'然后从该集合中按 ID 拉取提供程序。该集合来自 DbContext。
    • 或者var provider = db.Providers.Single(x =&gt; x.UPI == providerId); 可以工作。理想情况下,如果可能的话,最好一次性加载您需要的所有提供程序。这将在每次循环迭代时运行一个查询。
    • 我已经更新了上面的示例,将该 ID 重命名为“UPI”而不是 ProviderID,这可能有点令人困惑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多