【问题标题】:Entity Framework and LINQ left join and include on same table实体框架和 LINQ 左连接并包含在同一张表上
【发布时间】:2014-06-11 02:54:01
【问题描述】:

我的用户具有以下类结构,以及他们对可能关联的不同公司的权限:

public class User
{
    public Guid Id { get; set; }
    public List<Permission> Permissions { get; set; }
    public Company DefaultCompany { get; set; }
}

public class Permission
{
    public User User { get; set; }
    public Company Company { get; set; }
    public int PermissionLevel { get; set; }
}

public class Company
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

这会产生三个 SQL 表。 Permission.User_Id > User.Id 和 Permission.Company_Id > Company.Id 之间有一个 FK。 User.DefaultCompany 和 Company 表之间没有 显式 关系(即 FK)。这是故意的,因为我们的数据库架构中有一个遗留选择。

我还有一个数据库存储库方法,它通过用户 ID 获取用户,并包含完整的公司记录:

public User GetById(Guid Id)
{
    return (from r in this.Context.Users.Include("Permissions.Company")
        where r.Id == Id
        select r)
        .SingleOrDefault();
}

这工作正常,但它没有设置DefaultCompany 属性。所以我尝试通过将此方法更改为以下方法来设置它。值得指出的是,代表 DefaultCompany 的 Company 记录与 User 共享相同的 ID 值。

public User GetById(Guid Id)
{
    return (from r in this.Context.Users.Include("Permissions.Company")
            where r.Id == Id
            join c in this.Context.Companies on r.Id equals c.Id into companies
            from company in companies.DefaultIfEmpty()
            select new { User = r, Company = company })
            .ToList()
            .Select(p => { p.User.DefaultCompany = p.Company; return p.User; })
            .SingleOrDefault();
}

事实上,这确实设置了DefaultCompany,但它具有选择Permissions 列表的副作用。我可以将这一切作为两个单独的操作来完成,如下面的代码所示,但如果没有必要,我宁愿不要两次访问数据库。

public User GetById(Guid Id)
{
    var u = (from r in this.Context.Users.Include("Permissions.Company")
            where r.Id == Id
            select r)
            .SingleOrDefault();

    u.DefaultCompany = (from r in this.Context.Companies where r.Id == u.Id select r).SingleOrDefault();

    return u;
}

还有其他方法可以做到这一点吗?

编辑:解释生成的 SQL 数据模型和其他示例。

【问题讨论】:

  • 很难理解你的数据库模型。您能否更清楚地解释实体之间的关系,甚至显示模式?哪个是权限的关键?我认为您的问题来自模型本身,而不是您进行查询的方式。
  • @JotaBe 我已经编辑了我的问题。很抱歉不清楚 - 数据模型比我在这里记录的要复杂和遗留,但我试图掌握两个查询之间的潜在差异以及为什么添加连接会导致权限列表不被填充.
  • 现在清楚多了。没有理由道歉:但如果问题不清楚,你就是失去获得答案的机会的人:)。尝试一些东西,让我知道是否可以将其添加为答案(带有额外的解释)。将 r.Permissions.Company / p.User.Permissions.Company 投影到您的匿名类型投影中(在您的新 {} 内)

标签: c# asp.net sql linq entity-framework


【解决方案1】:

这个问题有两种可能的解决方案。

最简洁的方法是使用 Fluent API 指示模型在 User 和 Company 之间存在 1 对 1 的关系。

覆盖上下文的OnModelCreating(DbModelBuilder modelBuilder) 方法。

在其中配置1 ro 1关系如下:

 modelBuilder.Entity<User>()
             .HasOptional(u => u.DefaultCompany)
             .WithRequired();

注意:在此配置中,1 个用户与 0 个或 1 个默认公司之间存在关系(注意 HasOptional 中的 Optional)。并且默认公司必须在另一端有一个用户。当配置了 1 对 1(或 1 对 0..1)关系时,EF 会自动使用相关表的 PK 来创建它们之间的关系。您可以使用其他 Fluent API 函数微调关系

这样做之后,您可以使用Include() 包含DefaultCompany

User user = ctx.Users
               .Include(u => u.DefaultCompany)
               .SingleOrDefault(u => u.Id == userId);

另一个更丑陋的解决方案是使用您的第二个查询并在投影中包含缺少的权限,以强制 EF 从数据库中恢复它们。

// ...
select new { User = r, Company = company, Companies = r.Permissions } 
// ...

【讨论】:

  • 使用投影似乎可以工作,但在那之后我在保存记录时开始遇到一些难看的错误 (A relationship from the 'Permission_User' AssociationSet is in the 'Deleted' state. Given multiplicity constraints, a corresponding 'Permission_User_Source' must also in the 'Deleted' state.) 所以我不知道该怎么做。我将尝试 Fluent API 方法,但我不确定我是否理解您的建议如何定义 User.Id 和 Company.Id 之间的隐式关系。此外,如果不清楚,在我的模型中,可能并非每个用户都存在 DefaultCompany。
  • 我试图解释为什么选择两个表的 id 来创建关系。尽管您没有告诉默认公司是可选的,但我猜到了:)。至于其他故障,当您以“非标准”方式加载集合时,它一定是更改跟踪和关系修复的问题。 (我应该仔细分析一下,但没有时间。我以为你只是用来阅读)。
猜你喜欢
  • 1970-01-01
  • 2011-05-16
  • 1970-01-01
  • 2013-09-19
  • 1970-01-01
  • 2011-07-29
  • 1970-01-01
  • 1970-01-01
  • 2013-05-15
相关资源
最近更新 更多