2015-04-15 4 views
4

Есть ли способ постепенного/условного добавления объединений в запрос? Я создаю пользовательский инструмент отчетности для клиента, и клиенту предоставляется список объектов, которые он может выбрать для запроса. В запросе всегда будет использоваться базовый объект («FWOBid»).Условные соединения с Linq

Так, например, если клиент выбирает объекты «FWOBid», «FWOItem», и «FWOSellingOption», я хотел бы сделать это:

var query = from fb in fwoBids 

// if "FWOSellingOption", add this join 
join so in sellingOptions on fb.Id equals so.BidId 

// if "FWOItem", add this join 
join i in fwoItems on fb.Id equals i.FWOBidSection.BidId 

// select "FWOBid", "FWOItem", and "FWOSellingOption" (everything user has selected) 
select new { FWOBid = fb, FWOSellingOption = so, FWOItem = i }; 

Хитрость заключается в том, клиент может выбрать о 6 объектов, которые все связаны друг с другом, что приводит к множеству различных комбинаций объединений. Я хотел бы избегать жесткого кодирования, если это возможно.

+1

Почему бы не просто запрограммировать запросы на основе пользовательского ввода? –

+0

@PeterDuniho, который звучит как хорошее решение, можете ли вы указать на пример этого? – logeyg

+0

Я отредактировал ваш заголовок. Пожалуйста, смотрите: «Если вопросы включают« теги »в их названиях?] (Http://meta.stackexchange.com/questions/19190/), где консенсус« нет, они не должны ». –

ответ

2

Опция - это сделать какое-то пользовательское соединение в сочетании с левыми соединениями.

Достойный TSQL-сервер не должен иметь каких-либо недостатков с точки зрения производительности для всегда использования всех объединений, поскольку оптимисты просто удалили бы соединение, если условие всегда ложно. Но это нужно проверить.

bool joinA = true; 
bool joinB = false; 
bool joinC = true; 

var query = from fb in fwoBids 
      join so in sellingOptions on new { fb.Id, Select = true } equals new { Id = so.BidId, Select = joinA } into js 
      from so in js.DefaultIfEmpty() 
      join i in fwoItems on new { fb.Id, Select = true } equals new { Id = i.FWOBidSection.BidId, Select = joinB } into ji 
      from i in ji.DefaultIfEmpty() 
      join c in itemsC on new { fb.Id, Select = true } equals new { Id = c.BidId, Select = joinC } 
      select new 
      { 
       FWOBid = fb, 
       FWOSellingOption = so, 
       FWOItem = i, 
       ItemC = c 
      };    
+0

Как я вижу это, это не сработает. Случается, что будет проведено сравнение анонимных объектов в вашем соединении. Они не сопоставимы, потому что они оба разные, не для нас, а для компилятора. Они всегда будут ложными. –

+1

@AlexEndris при использовании анонимных типов в соединениях фактически там сравниваются свойства –

+0

действительно вы правы. Еще одна вещь, изученная сегодня. Не беспокойся в таком случае! –

2

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

var query = from x in dba select new { A = x, B = (B)null, C = (C)null }; 

if ((joinType & JoinType.B) != 0) 
{ 
    query = from x in query 
      join y in dbb on x.A.Id equals y.Id 
      select new { A = x.A, B = y, C = x.C }; 
} 

if ((joinType & JoinType.C) != 0) 
{ 
    query = from x in query 
      join y in dbc on x.A.Id equals y.Id 
      select new { A = x.A, B = x.B, C = y }; 
} 

То есть, на основании соответствующего состояния, запрос предыдущего результата с другим присоединиться. Обратите внимание, что для этого каждый запрос должен иметь тот же тип. В противном случае невозможно назначить новый запрос предыдущей переменной результата запроса.

Обратите внимание, что в то время как в приведенном выше, я просто отдельное свойство для каждого возможного типа входа, я мог бы иметь вместо того, чтобы имело типа просто свойство для входных столбцов, Id, Name, а затем Text свойства от B и C (которые должны быть названы по-разному в типе результата запроса, например TextB и TextC). Это будет выглядеть следующим образом:

var query = from x in dba select new { Id = x.Id, Name = x.Name, 
    TextB = (string)null, TextC = (string)null }; 

if ((joinType & JoinType.B) != 0) 
{ 
    query = from x in query 
      join y in dbb on x.Id equals y.Id 
      select new { Id = x.Id, Name = x.Name, TextB = y.Text, TextC = x.TextC }; 
} 

if ((joinType & JoinType.C) != 0) 
{ 
    query = from x in query 
      join y in dbc on x.Id equals y.Id 
      select new { Id = x.Id, Name = x.Name, TextB = x.TextB, TextC = y.Text }; 
} 

Вот полный пример кода, который включает в себя вышеупомянутую логику в работающую программу:

class A 
{ 
    public string Name { get; private set; } 
    public int Id { get; private set; } 

    public A(string name, int id) 
    { 
     Name = name; 
     Id = id; 
    } 

    public override string ToString() 
    { 
     return "{" + Name + ", " + Id + "}"; 
    } 
} 

class B 
{ 
    public int Id { get; private set; } 
    public string Text { get; private set; } 

    public B(int id, string text) 
    { 
     Id = id; 
     Text = text; 
    } 

    public override string ToString() 
    { 
     return "{" + Id + ", " + Text + "}"; 
    } 
} 

class C 
{ 
    public int Id { get; private set; } 
    public string Text { get; private set; } 

    public C(int id, string text) 
    { 
     Id = id; 
     Text = text; 
    } 

    public override string ToString() 
    { 
     return "{" + Id + ", " + Text + "}"; 
    } 
} 

[Flags] 
enum JoinType 
{ 
    None = 0, 
    B = 1, 
    C = 2, 
    BC = 3 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     A[] dba = 
     { 
      new A("A1", 1), 
      new A("A2", 2), 
      new A("A3", 3) 
     }; 
     B[] dbb = 
     { 
      new B(1, "B1"), 
      new B(2, "B2"), 
      new B(3, "B3") 
     }; 
     C[] dbc = 
     { 
      new C(1, "C1"), 
      new C(2, "C2"), 
      new C(3, "C3") 
     }; 

     JoinType joinType; 

     while ((joinType = _PromptJoinType()) != JoinType.None) 
     { 
      var query = from x in dba select new { A = x, B = (B)null, C = (C)null }; 

      if ((joinType & JoinType.B) != 0) 
      { 
       query = from x in query 
         join y in dbb on x.A.Id equals y.Id 
         select new { A = x.A, B = y, C = x.C }; 
      } 

      if ((joinType & JoinType.C) != 0) 
      { 
       query = from x in query 
         join y in dbc on x.A.Id equals y.Id 
         select new { A = x.A, B = x.B, C = y }; 
      } 

      foreach (var item in query) 
      { 
       Console.WriteLine(item); 
      } 
      Console.WriteLine(); 
     } 
    } 

    private static JoinType _PromptJoinType() 
    { 
     JoinType? joinType = null; 

     do 
     { 
      Console.Write("Join type ['A' for all, 'B', 'C', or 'N' for none]"); 
      ConsoleKeyInfo key = Console.ReadKey(); 
      Console.WriteLine(); 

      switch (key.Key) 
      { 
      case ConsoleKey.A: 
       joinType = JoinType.BC; 
       break; 
      case ConsoleKey.B: 
       joinType = JoinType.B; 
       break; 
      case ConsoleKey.C: 
       joinType = JoinType.C; 
       break; 
      case ConsoleKey.N: 
       joinType = JoinType.None; 
       break; 
      default: 
       break; 
      } 
     } while (joinType == null); 

     return joinType.Value; 
    } 
} 
+1

Возможно, было бы легче понять, если бы вы не выполняли условие по-флагов, просто «Condition1» и т. Д. Возможно, это просто отвлекает от того, что этот ответ действительно есть. –

+0

@AlexEndris: да? Вы всерьез предполагаете, что, поскольку мое состояние не соответствует вашей идее о том, как должно выглядеть «условие», ответ, таким образом, «не полезен» и поэтому заслуживает проголосования? Это интересная, гм ... «теория». –

+1

Это было предложение сделать более простые условия, чтобы не отвлекать от реальной вещи. Не нужно лично обижаться, если кто-то просто дает вам какой-то вклад в этом, особенно с этим обвинением ... Ouch ... –

4

В синтаксисе запроса Linq это не представляется возможным, или глядя на другие ответы вряд ли удобочитаемы. Не намного более читаемым, но другая возможность будет использовать методы расширения (вроде псевдо-код):

 bool condition1; 
     bool condition2; 

     List<Bid> bids = new List<Bid>(); 
     List<SellingOption> sellingOptions = new List<SellingOption>(); 
     List<Item> items = new List<Item>(); 

     var result = bids.Select(x => new {bid = x, sellingOption = (SellingOption)null, item = (Item)null}); 

     if (condition1) 
      result = result.Join(
       sellingOptions, 
       x => x.bid.Id, 
       x => x.BidId, 
       (x, sellingOption) => new { x.bid, sellingOption, item = (Item)null }); 

     if (condition2) 
      result = result.Join(
       items, 
       x => x.bid.Id, 
       x => x.BidId, 
       (x, item) => new { x.bid, x.sellingOption, item }); 

Просто видеть это как своего рода концепции. По сути, это то же самое, что и Питер Дунихо.

Дело в том, что если вы не хотите сразу же присоединяться ко всем параметрам, если это не необходимо, то это будет выглядеть не так хорошо. Возможно, вам стоит попробовать присоединиться ко всем и не беспокоиться о производительности. Вы когда-нибудь измеряли, как медленно или быстро это может быть? Подумайте об этом как «мне это не нужно сейчас!». Если производительность действительно является проблемой, вы можете действовать на нее. Но если это не так, и вы не будете знать, не пробовали ли вы, тогда оставьте это как шесть объединений, о которых вы упомянули.

1

Надеюсь, это улучшение по сравнению с предыдущими ответами.

public class Bids 
{ 
    public int Id { get; set; } 
    public double Price { get; set; } 
} 

public class BidSection 
{ 
    public int BidId { get; set; } 
} 

public class SellingOptions 
{ 
    public int BidId { get; set; } 
    public int Quantity { get; set; } 
} 

public class Item 
{ 
    public int ItemId { get; set; } 
    public BidSection FWOBidSection { get; set; } 
} 

public class ConditionalJoin 
{ 
    public bool jOpt1 { get; set; } 
    public bool jOpt2 { get; set; } 

    public ConditionalJoin(bool _joinOption1, bool _joinOption2) 
    { 
     jOpt1 = _joinOption1; 
     jOpt2 = _joinOption2; 
    } 

    public class FBandSo 
    { 
     public Bids FWOBids { get; set; } 
     public SellingOptions FWOSellingOptions { get; set; } 
    } 

    public class FBandI 
    { 
     public Bids FWOBids { get; set; } 
     public Item FWOItem { get; set; } 
    } 

    public void Run() 
    { 
     var fwoBids = new List<Bids>(); 
     var sellingOptions = new List<SellingOptions>(); 
     var fwoItems = new List<Item>(); 

     fwoBids.Add(new Bids() { Id = 1, Price = 1.5 }); 
     sellingOptions.Add(new SellingOptions() { BidId = 1, Quantity = 2 }); 
     fwoItems.Add(new Item() { ItemId = 10, FWOBidSection = new BidSection() { BidId = 1 } }); 

     IQueryable<Bids> fb = fwoBids.AsQueryable(); 
     IQueryable<SellingOptions> so = sellingOptions.AsQueryable(); 
     IQueryable<Item> i = fwoItems.AsQueryable(); 

     IQueryable<FBandSo> FBandSo = null; 
     IQueryable<FBandI> FBandI = null; 

     if (jOpt1) 
     { 
      FBandSo = from f in fb 
         join s in so on f.Id equals s.BidId 
         select new FBandSo() 
         { 
          FWOBids = f, 
          FWOSellingOptions = s 
         }; 
     } 

     if (jOpt2) 
     { 
      FBandI = from f in fb 
        join y in i on f.Id equals y.FWOBidSection.BidId 
        select new FBandI() 
        { 
         FWOBids = f, 
         FWOItem = y 
        }; 
     } 

     if (jOpt1 && jOpt2) 
     { 
      var query = from j1 in FBandSo 
         join j2 in FBandI 
         on j1.FWOBids.Id equals j2.FWOItem.FWOBidSection.BidId 
         select new 
         { 
          FWOBids = j1.FWOBids, 
          FWOSellingOptions = j1.FWOSellingOptions, 
          FWOItems = j2.FWOItem 
         }; 

     } 
    } 
} 
+0

На самом деле довольно ясно, что происходит в каком сценарии, поскольку вы полностью их разделяете. Самая большая проблема, которую я вижу здесь, заключается в том, что если вы добавите еще один вариант (и OP говорит о 6, так что 2^6 - 1 сценарии), это становится немного огромным. Но мне очень нравится ваш ответ, потому что это ничего «умного» или чего-то еще и, следовательно, гораздо яснее! –

+0

Это то, о чем я думал, когда речь идет о выборе N элементов> 2. На самом деле может быть проще создать собственный метод для генерации SQL Query на основе пользовательских настроек и запустить его как обычный вызов базы данных вместо использования Linq. Я уверен, что должен быть способ написать хранимую процедуру, чтобы сделать это тоже. – gmlacrosse

+0

Это, однако, предполагается, что мы имеем дело с такой базой данных, как SqlServer, где хранятся процедуры. Если это даже вариант, кто знает. Я действительно не думаю, что это имеет смысл думать о производительности (таким образом, не всегда делайте эти 6 объединений, например OP хочет здесь). Я лично, и я подразумеваю, что OP еще не сделал, просто сделает самое простое -> Все присоединяется. Если это окажется проблемой производительности, я бы подумал о других способах этого. –