C# 与Linq的条件连接

C# 与Linq的条件连接,c#,linq,C#,Linq,有没有一种方法可以逐步/有条件地向查询添加连接?我正在为客户创建一个自定义报告工具,并向客户提供了一个他/她可以选择查询的对象列表。查询中将始终使用一个基本对象(“FWOBid”) 因此,例如,如果客户选择对象“FWOBid”、“FWOItem”和“FWOSellingOption”,我会这样做: var query = from fb in fwoBids // if "FWOSellingOption", add this join join so in sellingOptions on

有没有一种方法可以逐步/有条件地向查询添加连接?我正在为客户创建一个自定义报告工具,并向客户提供了一个他/她可以选择查询的对象列表。查询中将始终使用一个基本对象(“FWOBid”)

因此,例如,如果客户选择对象“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个彼此相关的对象,从而产生许多不同的连接组合。如果可能的话,我希望避免硬编码

一个选项是结合左连接进行一些自定义连接

一个好的TSQL后端应该不会因为总是使用所有连接而在性能方面有任何缺点,因为如果条件总是false,那么优化程序只会删除连接。但这应该被检查出来

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

如果没有真正好的示例问题,很难提供真正好的示例解决方案。然而,我所说的“查询链”是这样的:

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 };
}
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 };
}
也就是说,根据适当的条件,使用另一个联接查询先前的结果。请注意,要成功执行此操作,每个查询必须生成相同的类型。否则,无法将新查询分配给前一个查询结果变量

请注意,在上面的示例中,我只是为每个可能的输入类型提供了一个单独的属性,相反,我可以让该类型为输入列提供属性,
Id
Name
,然后是
B
C
类型中的
Text
属性(必须在查询结果类型中使用不同的名称,例如
TextB
TextC
)。如下所示:

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

在Linq查询语法中,这是不可能的,或者查看其他答案几乎不可读。可读性不强,但另一种可能是使用扩展方法(类似于伪代码):

bool条件1;
布尔条件2;
列表出价=新列表();
List sellingOptions=新列表();
列表项=新列表();
var result=bids.Select(x=>new{bid=x,sellingOption=(sellingOption)null,item=(item)null});
如果(条件1)
result=result.Join(
销售选项,
x=>x.bid.Id,
x=>x.BidId,
(x,sellingOption)=>new{x.bid,sellingOption,item=(item)null});
如果(条件2)
result=result.Join(
项目,
x=>x.bid.Id,
x=>x.BidId,
(x,项目)=>新的{x.bid,x.sellingOption,项目});
这只是一个概念,本质上和彼得·杜尼霍一样


问题是,如果你不想在没有必要的情况下立即加入所有选项,那么它看起来就不那么好了。也许你应该尝试现在加入所有选项,而不必担心性能。你有没有衡量过它可能有多慢或多快?可以将其视为“我现在不需要它!”。如果性能确实是一个问题,那么你可以采取行动。但如果不是,并且你不知道自己是否从未尝试过,那么就按照你提到的六个连接来处理。

我希望这是对以前答案的改进

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

        }
    }
}
公共类投标
{
公共int Id{get;set;}
公共双价{get;set;}
}
公开课投标组
{
public int BidId{get;set;}
}
公共类销售选项
{
public int BidId{get;set;}
公共整数数量{get;set;}
}
公共类项目
{
公共int ItemId{get;set;}
公共BidSection fObjidSection{get;set;}
}
公共类条件连接
{
公共bool jOpt1{get;set;}
公共bool jOpt2{get;set;}
公共条件连接(bool_joinOption1,bool_joinOption2)
{
jOpt1=_joinOption1;
jOpt2=_joinOption2;
}
公营班车
{
公开出价FWOBids{get;set;}
公开出售选项FWOSellingOptions{get;set;}
}
公共类FBandI
{
公开出价FWOBids{get;set;}
公共项FWOItem{get;set;}
}
公开募捐
{
var fwoBids=新列表();
var sellingOptions=新列表();
var fwoItems=新列表();
Add(新出价(){Id=1,Price=1.5});
添加(新的sellingOptions(){BidId=1,Quantity=2});
Add(new Item(){ItemId=10,FWOBidSection=new BidSection(){BidId=1}});
IQueryable fb=fwoBids.AsQueryable();
IQueryable so=sellingOptions.AsQueryable();
IQueryable i=fwoItems.AsQueryable();
IQueryable FBandSo=null;
IQueryable FBandI=null;
if(jOpt1)
{
FBandSo=从fb中的f开始
按f.Id等于s.BidId的顺序加入s
选择新FBandSo()
{
FWOBids=f,
fvosellingoptions=s
};
}
if(jOpt2)
{
FBandI=从fb中的f开始
在f上的i中加入y。Id等于y.fvobidSection.BidId
选择新FBandI()
{
FWOBids=f,
FWOItem=y
};
}
if(jOpt1和&jOpt2)
{
var query=来自FBandSo中的j1
在FBandI中加入j2
在j1.FWOBids.Id上等于j2.FWOItem.FWOBidSection.BidId
选择新的
{
FWOBids=j1.FWOBids,
fvosellingoptions=j1.fvosellingoptions,
FWOItems=j2.FWOItem
};
}
}
}
为什么不呢