Linq-to-SQL Load 1: 1 Отношения в одном запросе - PullRequest
3 голосов
/ 18 ноября 2009

У нас есть несколько классов с несколькими отношениями 1: 1 для быстрых объединений, и хотя это хорошо работает для анонимных типов для табличного отображения, я не уверен, как полностью заполнить тип в одном запросе linq.

У нас есть эти свойства, потому что они отключены 1: 1, или мы не хотим запрашивать через дочернюю коллекцию, чтобы найти «первичное» каждое отображение, вместо этого мы берем на себя затраты, устанавливая эти Первичные идентификаторы при сохранении.

Урезанный пример для контекста этого поста:

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

Проблема в том, что при отображении списка контактов, PrimaryAddress и PrimaryEmail должны быть загружены с отложенной загрузкой. Если мы сделаем 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: 1) +1 запросов на количество отображаемых строк.

Ответы [ 4 ]

2 голосов
/ 01 января 2010

Обновление: Наконец-то дошли до обновления, ребята из devart исправили поведение в более поздних версиях, чтобы оно работало идеально. 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
             };

Это правильно выполняет одно левое внешнее соединение с соответствующими таблицами Address и Email. Теперь исправление является специфическим для ситуации получения этого анонимного типа ... но они также исправили поведение DataLoadOptions, когда оно нам делает , теперь правильно отключен тип внешнего ключа. Надеюсь, что это обновление поможет другим в более старой версии ... Я настоятельно рекомендую обновить, с версии 5.35 появилось много новых улучшений (многие из которых делают жизнь намного проще).


Оригинал:
То, что мы закончили, было другим подходом. Это может быть специфическое поведение поставщика devart: dotConnect для 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-entity, но ... в случае, если мы имеем дело, это нормально.


Товар:

  • Одиночный запрос, производящий левое соединение с адресом и электронной почтой
  • Облегченные объекты для адреса и вплоть до строки для электронной почты (они оба имеют некоторую обратную ссылку EntiySet в реальном проекте, что делает их более дорогими, чем необходимо для простых табличных нужд отображения)
  • Быстро / чисто, приведенный выше запрос гораздо проще, чем ручное соединение каждой дочерней таблицы, которую мы делали, более чистый код.
  • Производительность, создание более тяжелых объектов было довольно значительным: при переходе от Email к строке, Address к AddressLite и (в полном проекте) Phone to PhoneLite страницы просто отображали табличные данные с 300-500 мс до 50 -100ms.

Плохое:

  • Анонимный тип, есть случаи, когда нам нужен сильный тип, необходимость его создания (даже когда ReSharper выполняет эту задачу) добавляет много беспорядка.
  • Поскольку мы не можем изменять и сохранять анонимный тип или любой другой тип, который мы создаем, без большого количества аннотаций, которые необходимо обновить, если модель что-то изменит вокруг этого. (поскольку эти классы не генерируются)
1 голос
/ 24 ноября 2009

Соединение влево генерируется, если для IsForeignKey установлено значение false в атрибуте ассоциации для свойства типа EntityRef.

1 голос
/ 23 ноября 2009

Мы столкнулись с почти такой же проблемой с 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();

Что он делает, тем не менее, извлекает его в одном запросе.

0 голосов
/ 24 ноября 2009

Возможно, вы захотите взглянуть на реализацию 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;
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...