繁体   English   中英

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

[英]Linq-to-SQL Load 1:1 Relations in a single query

我们有几个具有多个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查询更好。

更新:最后解决了这个问题,开发人员已经修复了以后版本中的行为以完美地工作。 根本不需要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();

这导致单个查询。 同时呼吁在选择的子对象,如c.PrimaryAddress 不会导致连接发生(导致很多的select ... from address where id = n延迟加载,每次我们显示表格数据的行之一) ,但是要求它的属性,如c.PrimaryAddress.Street1 确实会导致正确的左侧在地址表中查询查询加盟。 上面的linq只能在linq-to-sql中运行,它会在linq-to-entities上使用null引用失败,但是......如果我们正在处理这个问题就好了。


好处:

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

坏:

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

我们遇到了与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();

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

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

您可能想看一下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;
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM