在同一查询中两次联接一个表是否安全?

时间:2018-08-17 14:07:17

标签: c# linq linq-to-sql

我需要为搜索页面编写一些linq(linq-to-sql),该页面允许用户搜索汽车,并可以选择包括汽车零件的搜索条件。这两个表是CARCAR_PARTS。这是我到目前为止的内容:

var query = db.CAR;

//if the user provides a car name to search by, filter on car name (this works)
if(model.CarName != "")
{
   query = from c in query
           where c.Name == model.CarName
           select c;
}

//if the user provides a car part name to filter on, join the CAR_PART table
if(model.CarPartName != "")
{
   query = from c in query
           join parts in db.CAR_PARTS on c.ID equals parts.CarID
           where parts.PartName == model.CarPartName
           select c;
}

//if the user provides a car part code to filter on, join the CAR_PART table
if(model.CarPartCode != "")
{
   query = from c in query
           join parts in db.CAR_PARTS on c.ID equals parts.CarID
           where parts.PartCode == model.CarPartCode 
           select c;
}

如果用户决定要同时在CarPartNameCarPartCode上进行搜索,则此逻辑将导致CAR_PART表被连接两次。这对我来说是错的,但这是处理此问题的正确方法吗?

你怎么写的?

3 个答案:

答案 0 :(得分:1)

这样做是合法的,但是是否有意义,取决于您的数据模型和所需的结果。

通常,如果定义了partnamepartcode,则您的代码会执行以下操作

  1. 使用cars作为连接条件,将parts表与partname表一起连接
  2. 使用parts作为联接条件的partcode表再次联接第一次联接的结果。

因此,这等于具有连接条件car.partname = part.name and car.partcode = part.code的连接。我不知道这是否是您想要的行为。

有些情况需要区分

加入 AND 条件

案例1.1:零件的名称和代码是零件表中的键

在这种情况下,零件表中每个namecode都是唯一的,因此,每个name都只有一个code。双重联接不是必需的,甚至可能导致错误的结果,因为

  1. 如果选中的namecode标识相同的部分,则这是第一个联接将已经获得期望的结果

  2. 如果namecode标识了不同的部分,则由于条件无法满足,您的结果将为空。

在这种情况下,我建议写如下

if (!string.IsNullOrEmpty(model.CarPartName)){
    // your join on partname
} else if (!string.IsNullOrEmpty(model.CarPartCode)) {
    // your join on partcode
}

案例1.2:零件名称和代码不是零件表中的键

在这种情况下,namecode都不是唯一的,并且对于一个name,可能有不同的code,反之亦然。在这里双重联接是必要的,并且只会返回包含namecode

都匹配的部分的结果

加入条件为 OR

另一方面,如果您希望加入条件像car.partname = part.name and car.partcode = part.code一样,则必须考虑以下情况

案例2.1的名称和代码是密钥

此处与情况1.1相同。

CASE 2.2的名称和代码不是密钥

在这里您不能使用逐步方法,因为第一次联接的结果将仅包含name匹配的汽车。可能有些部分只匹配code条件,但是如果它们不包含在第一个匹配结果中,则它们永远不能包含在最终结果中。因此,在这种情况下,您将必须定义这样的查询

if (!string.IsNullOrEmpty(model.CarPartName) && !string.IsNullOrEmpty(model.CarPartCode)) {
    query = from c in query
        join parts in db.CAR_PARTS on c.ID equals parts.CarID
        where parts.PartName == model.CarPartName || parts.PartCode == model.CarPartCode
        select c;
} else if (!string.IsNullOrEmpty(model.CarPartName)) {
    query = from c in query
        join parts in db.CAR_PARTS on c.ID equals parts.CarID
        where parts.PartName == model.CarPartName
        select c;    
} else if (!string.IsNullOrEmpty(model.CarPartCode)) {
    query = from c in query
        join parts in db.CAR_PARTS on c.ID equals parts.CarID
        where parts.PartCode == model.CarPartCode
        select c;    
}

答案 1 :(得分:1)

实际上存在什么错,有了适当的关系,您根本不需要联接。添加LinqToSQL的行为,您可以将其编写为:

var query = db.CAR
       .Where( c => 
          ( string.IsNullOrEmpty(model.CarName) 
          || c.Name == model.CarName ) &&
          ( string.IsNullOrEmpty(model.CarPartName) 
          || c.Parts.Any( p => p.PartName == model.CarPartName )) &&
          ( string.IsNullOrEmpty(model.CarPartCode)
          || c.Parts.Any( p => p.PartCode == model.CarPartCode )));

只要查询是IQueryable(db.CAR.AsQueryable()),您就可以工作。两种Linq方法相似但不相同。根据您的实际需要,您可能是正确的,也可能是错误的。您的将产生两个内部联接,而这一内部联接仅创建2个存在检查。假设您有:

Car, Id:5, Name: Volvo

以及类似的部分:

CarID:5, PartName:HeadLights, PartCode:1 ... other details
CarID:5, PartName:HeadLights, PartCode:2 ... other details
CarID:5, PartName:HeadLights, PartCode:3 ... other details

如果用户使用model.CarName =“ Volvo”和model.PartName =“ HeadLights”询问,您将获得相同的Volvo 3次。在第二种方法中,您将获得一辆沃尔沃汽车。

HTH

答案 2 :(得分:-1)

我对流利的语法感到更自在,但是我确信类似于以下内容的内容将对您有用。我将在Select语句中检查模型中的字段,然后有条件地使用一个字段或另一个字段进行联接。如果两者均未设置,则将其保留为空。

var query = db.CAR;
if (!string.IsNullOrWhitespace(model.CarName))
{
    query = query.Where(car => car.Name == model.CarName);
}

var items = query.Select(car => new
{
     Car = car, // maybe better to split this up into different fields, but I don't know what the car object looks like

     // I assume your Car entity model has a navigation property to parts:
     CarPart = !string.IsNullOrWhitespace(model.CarPartName) 
               ? car.Parts.FirstOrDefault(part => part.PartName == model.CarPartName)
               : !string.IsNullOrWhitespace(model.CarPartCode)
               ? car.Parts.FirstOrDefault(part => part.PartCode == model.CarPartCode)
               : null
})
.ToList();

这确实意味着如果填写名称,则将忽略代码。如果需要相反的处理,则将其反转。或者,如果您想同时使用这两个字段,则可以将字符串空检查放在“ Where”子句中。