Linq-to-SQL在单个查询中加载1:1关系

时间:2009-11-18 02:44:39

标签: c# linq linq-to-sql

我们有几个具有多个1:1关系的类用于快速连接,虽然这适用于表格显示的匿名类型,但我不确定如何在单个linq查询中完全填充该类型。

我们有这些属性,因为它是1:1的关闭,或者我们不希望通过子集查询每个显示器找到“主要”,我们通过在保存时设置这些主要ID来产生成本。

这篇文章背景的精简示例:

public class Contact
{
  public long Id { get; set; }

  public EntitySet<Address> Addresses { get; set; }
  public EntityRef<Address> PrimaryAddress { get; set; }
  public long? PrimaryAddressId { get; set; }

  public EntitySet<Email> Emails { get; set; }
  public EntityRef<Email> PrimaryEmail { get; set; }
  public long? PrimaryEmailId { get; set; }

  public string FirstName { get; set; }
  public string LastName { get; set; }
}

public class Address
{
  public long Id { get; set; }
  public EntitySet<Contact> Contacts { get; set; }

  public bool IsPrimary { get; set; }
  public string Street1 { get; set; }
  public string Street2 { get; set; }
  public string City { get; set; }
  public string State { get; set; }
  public string Country { get; set; }
}

public class Email
{
  public long Id { get; set; }
  public EntitySet<Contact> Contacts { get; set; }

  public bool IsPrimary { get; set; }
  public string Address { get; set; }
}

问题是在显示联系人列表时,PrimaryAddressPrimaryEmail必须延迟加载。如果我们DataLoadOptions它没有产生预期的效果,因为它是1:1,例如:

var DB = new DataContext();
var dlo = new DataLoadOptions();
dlo.LoadWith<Contact>(c => c.PrimaryAddress);
dlo.LoadWith<Contact>(c => c.PrimaryEmail);
DB.LoadOptions = dlo;

var result = from c in DB.Contacts select c;
result.ToList();

上面的代码导致 INNER JOIN ,因为它将它视为父关系,它不尊重可空的FK关系,并且左连接1:1属性。所需的查询类似于:

Select t1.*, t.2*, t3.*
From Contact t1
Left Join Address t2 On t1.PrimayAddressId = t2.Id
Left Join Email On t1.PrimaryEmailId = t3.Id

有没有办法做到这一点并获得一个IQueryable与这些可以为空的1:1属性填充,甚至列表?由于其他约束,我们需要类型为Contact,因此匿名类型将不起作用。对选项非常开放,对于我们显示的行数,任何事情都会比延迟加载n *(1:1s的数量)+1查询更好。

4 个答案:

答案 0 :(得分:2)

更新:最后解决了这个问题,开发人员已经修复了以后版本中的行为以完美地工作。完全不需要DataLoadOptions,只需使用表格外的字段,例如:

var DB = new DataContext();
var result = from c in DB.Contacts
             select new {
               c.Id
               c.FirstName,
               c.LastName,
               Address = c.PrimaryAddress.Street1 + " " + c.PrimaryAddress.Street2 //...
               Email = c.PrimaryEmail.Address
             };

这正确地对相关的AddressEmail表执行单个左外连接。现在,修复程序特定于此处获取此匿名类型的情况......但它们还修复了我们需要它的DataLoadOptions行为,现在正确地键入了外键类型。希望这个更新可以帮助其他人使用旧版本...我强烈建议升级,自5.35以来版本中有许多新的增强功能(许多使生活很多更容易)。


原件:
我们最终得到的是一种不同的方法。这可能是 devart: dotConnect for Oracle 提供商的特定行为(从版本5.35.62开始,如果此行为发生更改,我会尝试更新此问题)。

var DB = new DataContext();
var result = from c in DB.Contacts
             select new {
               c.Id
               c.FirstName,
               c.LastName,
               Address = new AddressLite { 
                               Street1 = c.PrimaryAddress.Street1, 
                               Street2 = c.PrimaryAddress.Street2, 
                               City = c.PrimaryAddress.City,
                               State = go.PrimaryAddress.State,
                               Country = go.PrimaryAddress.Country },
               Email = c.PrimaryEmail.Address
             };
result.ToList();

这导致单个查询。在select中调用子对象时,例如c.PrimaryAddress 导致联接发生(导致大量select ... from address where id = n延迟加载,我们正在显示每行表格数据一个),在其上调用属性但是,例如c.PrimaryAddress.Street1 DOES 在查询查询的地址表中导致正确的左连接。上面的linq只能在linq-to-sql中运行,它会在linq-to-entities上使用null引用失败,但是......如果我们正在处理那个就好了。


好处:

  • 单个查询,生成左连接到地址和电子邮件
  • 用于地址的轻量级对象和仅用于电子邮件的字符串(它们在实际项目中都有一些反向引用的EntiySet,这使得它们比简单的表格显示需要更昂贵)
  • 快速/干净,上面的查询比手动加入我们正在做的每个子表更简单,更清晰的代码。
  • 性能,更重的对象的创建非常受欢迎,从Email变为string,Address to AddressLite和(在完整项目中)Phone to PhoneLite导致页面显示表格数据从300-500ms下降到50 -100ms。

糟糕:

  • 匿名类型,有些情况下我们需要一个强类型,必须创建它们(即使像ReSharper一样快速完成此任务)也会增加很多混乱。
  • 由于我们无法修改和保存匿名类型,或者我们在没有大量注释工作的情况下创建的任何类型,如果模型在此周围发生任何变化,则必须更新。 (因为没有生成这些类)

答案 1 :(得分:1)

我们遇到了与DataLoadOptions,延迟加载和您的主要记录相同的问题。

老实说,我对我们提出的解决方案并不完全满意,因为它不是很整洁,它产生的SQL查询可能很复杂,但基本上我们创建了包含我们想要的字段副本的包装类强制加载和使用子查询加载记录。对于上面的例子:

public class ContactWithPrimary
{
    public Contact Contact { get; set; }
    public Email PrimaryEmail { get; set; }
    public Address PrimaryAddress { get; set; }
}

然后一个示例LINQ查询将是:

List<ContactWithPrimary> Contacts = DataContext.Contacts
    .Select(con => new ContactWithPrimary 
    { 
        Contact = con, 
        PrimaryEmail = con.PrimaryEmail, 
        PrimaryAddress = con.PrimaryAddress 
    }).ToList();

它的作用是在单个查询中将其拉出来。

答案 2 :(得分:1)

如果在EntityRef类型属性的关联属性中将IsForeignKey设置为false,则会生成左连接。

答案 3 :(得分:0)

您可能想看一下Rob Conery的Lazy List实现。

http://blog.wekeroad.com/blog/lazy-loading-with-the-lazylist/

它基本上隐藏了你的整个延迟加载实现,你不需要指定任何加载选项。

唯一的缺点是它只适用于列表。但是,也可以为属性编写实现。这是我的努力。

public class LazyProperty<TEntityType> where TEntityType : class
{
    private readonly IQueryable<TEntityType> source;
    private bool loaded;
    private TEntityType entity;

    public LazyProperty()
    {
        loaded = true;
    }

    public LazyProperty(IQueryable<TEntityType> source)
    {
        this.source = source;
    }

    public TEntityType Entity
    {
        get 
        {
            if (!loaded)
            {
                entity = source.SingleOrDefault();
                loaded = true;
            }
            return entity;
        }
        set 
        { 
            entity = value;
            loaded = true;
        }
    }
}