Linq不同记录包含关键字

时间:2013-01-14 18:32:09

标签: c# sql-server asp.net-mvc linq entity-framework

我需要根据汽车关键字搜索返回不同的记录列表,例如:“Alfa 147”

问题是,因为我有3辆“Alfa”汽车,它会返回1 + 3条记录(阿尔法似乎为1,结果为147,Alfa结果为3)

编辑:

SQL-Server查询如下所示:

SELECT DISTINCT c.Id, c.Name /*, COUNT(Number of Ads in the KeywordAdCategories table with those 2 keywords) */
FROM Categories AS c
INNER JOIN KeywordAdCategories AS kac ON kac.Category_Id = c.Id
INNER JOIN KeywordAdCategories AS kac1 ON kac.Ad_Id = kac1.Ad_Id AND kac1.Keyword_Id = (SELECT Id FROM Keywords WHERE Name = 'ALFA')
INNER JOIN KeywordAdCategories AS kac2 ON kac1.Ad_Id = kac2.Ad_Id AND kac2.Keyword_Id = (SELECT Id FROM Keywords WHERE Name = '147')

我的LINQ查询是:

       var query = from k in keywordQuery where splitKeywords.Contains(k.Name) 
                    join kac in keywordAdCategoryQuery on k.Id equals kac.Keyword_Id
                    join c in categoryQuery on kac.Category_Id equals c.Id
                    join a in adQuery on kac.Ad_Id equals a.Id
                    select new CategoryListByKeywordsDetailDto
                    {
                        Id = c.Id,
                        Name = c.Name,
                        SearchCount = keywordAdCategoryQuery.Where(s => s.Category_Id == c.Id).Where(s => s.Keyword_Id == k.Id).Distinct().Count(),
                        ListController = c.ListController,
                        ListAction = c.ListAction
                    };

        var searchResults = new CategoryListByBeywordsListDto();

        searchResults.CategoryListByKeywordsDetails = query.Distinct().ToList();

实体是:

public class Keyword
{
    // Primary properties
    public int Id { get; set; }
    public string Name { get; set; }
}
// Keyword Sample Data:
// 1356 ALFA
// 1357 ROMEO
// 1358 145
// 1373 147

public class Category
{
    // Primary properties
    public int Id { get; set; }
    public string Name { get; set; }
}
// Category Sample Data
// 1    NULL    1   Carros
// 2    NULL    1   Motos
// 3    NULL    2   Oficinas
// 4    NULL    2   Stands
// 5    NULL    1   Comerciais
// 8    NULL    1   Barcos
// 9    NULL    1   Máquinas
// 10   NULL    1   Caravanas e Autocaravanas
// 11   NULL    1   Peças e Acessórios
// 12   1   1   Citadino
// 13   1   1   Utilitário
// 14   1   1   Monovolume

public class KeywordAdCategory
{
    [Key]
    [Column("Keyword_Id", Order = 0)]
    public int Keyword_Id { get; set; }

    [Key]
    [Column("Ad_Id", Order = 1)]
    public int Ad_Id { get; set; }

    [Key]
    [Column("Category_Id", Order = 2)]
    public int Category_Id { get; set; }
}
// KeywordAdCategory Sample Data
// 1356 1017    1
// 1356 1018    1
// 1356 1019    1
// 1357 1017    1
// 1357 1018    1
// 1357 1019    1
// 1358 1017    1
// 1373 1019    1

 public class Ad
{
    // Primary properties
    public int Id { get; set; }
    public string Title { get; set; }
    public string TitleStandard { get; set; }
    public string Version { get; set; }
    public int Year { get; set; }
    public decimal Price { get; set; }

    // Navigation properties
    public Member Member { get; set; }
    public Category Category { get; set; }
    public IList<Feature> Features { get; set; }
    public IList<Picture> Pictures { get; set; }
    public IList<Operation> Operations { get; set; }
}

public class AdCar : Ad
{
    public int Kms { get; set; }
    public Make Make { get; set; }
    public Model Model { get; set; }
    public Fuel Fuel { get; set; }
    public Color Color { get; set; }
}
// AdCar Sample Data
// 1017 Alfa Romeo 145 1.6TDI 2013  ALFA ROMEO 145 1.6TDI 2013  12  2       1.6TDI  1000    1   2013    1   20000,0000  2052    AdCar
// 1018 Alfa Romeo 146 1.6TDI 2013  ALFA ROMEO 146 1.6TDI 2013  12  2   5   1.6TDI  1000    2   2013    1   20000,0000  2052    AdCar
// 1019 Alfa Romeo 147 1.6TDI 2013  ALFA ROMEO  147 1.6TDI 2013 12  2   6   1.6TDI  1000    3   2013    1   20000,0000  2052    AdCar

我期望搜索“ALFA”的结果是“汽车:3”,搜索“ALFA 147”的结果是“汽车:1”,实际上我得到的结果是“汽车:1 \ n汽车: 3"

12 个答案:

答案 0 :(得分:6)

kac没有过滤单词...所以这个kac,kac1和kac2的连接将返回3行,因为这是此广告的关键字数量

你应该删除它..

试试这个:

SELECT DISTINCT 
    c.Id, c.Name /*, COUNT(Number of Ads in the KeywordAdCategories table    with those 2 keywords) */
FROM 
    Categories AS c
INNER JOIN 
    KeywordAdCategories AS kac1 ON kac1.Keyword_Id = (SELECT Id 
                                                      FROM Keywords 
                                                      WHERE Name = 'ALFA')
                                AND kac1.Category_Id = c.Id
INNER JOIN 
    KeywordAdCategories AS kac2 ON kac1.Ad_Id = kac2.Ad_Id 
                                AND kac2.Keyword_Id = (SELECT Id 
                                                       FROM Keywords 
                                                       WHERE Name = '147')
                                AND kac2.Category_Id = c.Id

我做了一个测试...

将环境设置为

    declare @Keywords table(id int,name varchar(max))
    insert into @Keywords(id,name)
    values (1356,'ALFA')
    ,(1357,'ROMEO')
    ,(1358,'145')
    ,(1373,'147')

    declare @Categories table(id int, name varchar(max))
    insert into @Categories(id,name)
    values (1,'Carros')
    ,(2,'Motos')


    declare @KeywordAdCategories table(Keyword_Id int, ad_Id int,Category_Id int)
    insert into @KeywordAdCategories (Keyword_Id , ad_Id,Category_Id)
    values (1356, 1017,1)
    ,(1356, 1018,1)
    ,(1356, 1019,1)
    ,(1357, 1017,1)
    ,(1357, 1018,1)
    ,(1357, 1019,1)
    ,(1358, 1017,1)
    ,(1373, 1019,1)

我运行这两个查询:

--query 1
SELECT 
    c.Id, c.Name,COUNT(*) as [count]
FROM 
    @Categories AS c
INNER JOIN 
    @KeywordAdCategories AS kac1 ON kac1.Keyword_Id = (SELECT Id 
                                                       FROM @Keywords 
                                                       WHERE Name = 'ALFA')
                                 AND kac1.Category_Id = c.Id
GROUP BY 
    c.Id, c.Name

我得到了这个结果集:

  Id          Name       count
  ----------- ---------- -----------
  1           Carros     3

和第二个查询两个单词......

--query 2
SELECT 
    c.Id, c.Name,COUNT(*) as [count]
FROM 
    @Categories AS c
INNER JOIN 
    @KeywordAdCategories AS kac1 ON kac1.Keyword_Id = (SELECT Id 
                                                       FROM @Keywords 
                                                       WHERE Name = 'ALFA')
                                 AND kac1.Category_Id = c.Id
INNER JOIN 
    @KeywordAdCategories AS kac2 ON kac1.Ad_Id = kac2.Ad_Id 
                                 AND kac2.Keyword_Id = (SELECT Id 
                                                        FROM @Keywords 
                                                        WHERE Name = '147')
                                 AND kac2.Category_Id = c.Id
GROUP BY
    c.Id, c.Name

结果集是:

 Id          Name       count
 ----------- ---------- -----------
 1           Carros     1

这是你想要的吗?

答案 1 :(得分:4)

您可以使用Distinct()方法。

var query = ...
var query = query.Distinct();

有关详细信息,请参阅This code returns distinct values. However, what I want is to return a strongly typed collection as opposed to an anonymous type

答案 2 :(得分:3)

将查询字符串拆分为数组,并通过查询每个关键字的数据库并使用联合连接结果集进行迭代。结果集将是与任何给定关键字匹配的每个不同记录。

答案 3 :(得分:2)

也许这很接近?至少子查询打开了一点供你使用。

var query =
  from c in categoryQuery
  let keywords =
  (
    from k in keywordQuery where splitKeywords.Contains(k.Name)
    join kac in keywordAdCategoryQuery on k.Id equals kac.Keyword_Id
    where kac.Category_Id == c.Id
    join a in adQuery on kac.Ad_Id equals a.Id
    select k.Id
  ).Distinct()
  where keywords.Any()
  select new CategoryListByKeywordsDetailDto
  {
    Id = c.Id,
    Name = c.Name,
    SearchCount =
    (
      from kac in keywordAdCategoryQuery
      where kac.Category_Id == c.Id
      join kId in keywords on kac.Keyword_Id equals kId
      select kac.Id
    ).Distinct().Count(),
    ListController = c.ListController,
    ListAction = c.ListAction
  };

答案 4 :(得分:2)

所以,如果我理解正确的需要,你想要在文本中匹配所有单词的子集而不是你现在得到的OR匹配吗?我看到至少有两个选项,第一个可能无法将拆分转换为SQL:

var query = from k in keywordQuery where !splitKeywords.Except(k.Name.split(' ')).Any()

这做出以下假设:

  1. 您在关键字中的字词是以空格分隔的。
  2. 您正在寻找完全匹配而非部分匹配。 (即,测试与TestTest不匹配)。
  3. 另一种选择是使用谓词构建器动态生成谓词(暂时没有这样做,我的实现可能需要调整 - 但这更可能(并且在我看来更好)解决方案):

    var predicate = PredicateBuilder.True<keywordQuery>();
    foreach (string s in splitKeywords) {
        predicate.AND(s.Contains(k.Name));
    }
    
    query.Where(predicate);
    

    如果有人可以评论我的某些语法是否已关闭,我将不胜感激。 编辑:包含指向谓词构建器的良好引用的链接:http://www.albahari.com/nutshell/predicatebuilder.aspx

    <强>更新

    跨多个表格的谓词构建器,如果有人到这里寻找如何做到这一点。 Can PredicateBuilder generate predicates that span multiple tables?

答案 5 :(得分:2)

linq的一个漂亮功能是,您可以在更小更简单的步骤中构建复杂的查询,让linq弄清楚如何将它们连接在一起。

以下是获取此信息的一种方法。我不确定这是否是最好的,当你选择多个关键字时,你需要检查它是否表现良好。

假设关键字的定义类似于

var keywords = "Alfa 147";
var splitKeywords = keywords.Split(new char[] {' '});

第1阶段

获取按广告和类别分组的关键字列表

var subQuery = (from kac in keywordAdCategoryQuery  
    join k in keywordQuery  on kac.Keyword_Id equals k.Id 
    select new 
    {        
        kac.Ad_Id, 
        kac.Category_Id, 
        KeyWord = k.Name, 
    }); 

var grouped = (from r in subQuery 
    group r by new { r.Ad_Id, r.Category_Id}  into results
    select new 
    { 
        results.Key.Ad_Id , 
        results.Key.Category_Id , 
        keywords = (from r in results select r.KeyWord) 
    });

请注意,您发布的类表明您的数据库没有在表之间定义外键关系。如果他们这样做,那么这个阶段写起来会稍微简单一点。

第2阶段

过滤掉没有每个关键字的任何群组

foreach(var keyword in splitKeywords)
{
    var copyOfKeyword = keyword ;   // Take copy of keyword to avoid closing over loop
    grouped = (from r in grouped where r.keywords.Contains(copyOfKeyword) select r) ;
}

第3阶段

按类别分组并计算每个类别的结果

var groupedByCategories = (from r in grouped 
    group r by r.Category_Id into results 
    join c in categoryQuery  on results.Key equals c.Id 
    select new 
    { 
        c.Id , 
        c.Name , 
        Count = results.Count()
    });

第4阶段

现在从sql中检索信息。这应该在一个查询中完成。

var finalResults = groupedByCategories.ToList();

答案 6 :(得分:2)

应该可以查询每个关键字然后联合结果集。重复值将从联合中删除,您可以计算出所需的聚合。

答案 7 :(得分:1)

选择

时尝试删除课程
 var query = (from k in keywordQuery where splitKeywords.Contains(k.Name) 
                        join kac in keywordAdCategoryQuery on k.Id equals kac.Keyword_Id
                        join c in categoryQuery on kac.Category_Id equals c.Id
                        join a in adQuery on kac.Ad_Id equals a.Id
                        select new
                        {
                            Id = c.Id,
                            Name = c.Name,
                            SearchCount = keywordAdCategoryQuery.Where(s => s.Category_Id == c.Id).Where(s => s.Keyword_Id == k.Id).Distinct().Count(),
                            ListController = c.ListController,
                            ListAction = c.ListAction
                        }).Distinct().ToList();

        var searchResults = new CategoryListByBeywordsListDto();



searchResults.CategoryListByKeywordsDetails = (from q in query select new           CategoryListByKeywordsDetailDto
{
                        Id = q.Id,
                        Name = q.Name,
                        SearchCount = q.SearchCount,
                        ListController = q.ListController,
                        ListAction = q.ListAction
                    }).ToList();

答案 8 :(得分:1)

嗨,如果我理解你的问题

  

“问题是,因为我有3辆”阿尔法“汽车,它会返回1 + 3   记录(阿尔法似乎是1,结果是147,阿尔法则是3   结果)“

并且Linq并不是真的需要我可能有你需要的只是测试它作为新项目

    public Linqfilter()
    {
        //as Note: I modified a few classes from you because i doesn'T have your Member, Operation, Make,... classes

        #region declaration
        var originalAdCarList = new List<AdCar>() 
        {
            new AdCar(){Id=1017, Title= "Alfa Romeo 145 1.6TDI 2013", Category= new Category(){Id =12}} ,
            new AdCar(){Id=1018, Title= "Alfa Romeo 146 1.6TDI 2013", Category= new Category(){Id =11}} ,
            new AdCar(){Id=1019, Title= "Alfa Romeo 147 1.6TDI 2013", Category= new Category(){Id =12}} 
        };

        var originalKeywordAdCategoryList = new List<KeywordAdCategory>() 
        {
            new KeywordAdCategory() { Keyword_Id=1356, Ad_Id=1017,Category_Id=1},
            new KeywordAdCategory() { Keyword_Id=1356, Ad_Id=1018,Category_Id=1},
            new KeywordAdCategory() { Keyword_Id=1356, Ad_Id=1019,Category_Id=1},
            new KeywordAdCategory() { Keyword_Id=1357, Ad_Id=1017,Category_Id=1},
            new KeywordAdCategory() { Keyword_Id=1357, Ad_Id=1018,Category_Id=1},
            new KeywordAdCategory() { Keyword_Id=1357, Ad_Id=1019,Category_Id=1},
            new KeywordAdCategory() { Keyword_Id=1358, Ad_Id=1017,Category_Id=1},
            new KeywordAdCategory() { Keyword_Id=1373, Ad_Id=1019,Category_Id=1}            
        };

        var originalCategoryList = new List<Category>()
        {
            new Category(){Id=1,    Name="NULL    1   Carros"},
            new Category(){Id=2,    Name="NULL    1   Motos"},
            new Category(){Id=3,    Name="NULL    2   Oficinas"},
            new Category(){Id=4 ,   Name="NULL    2   Stands"},
            new Category(){Id=5 ,   Name="NULL    1   Comerciais"},
            new Category(){Id=8,    Name="NULL    1   Barcos"},
            new Category(){Id=9 ,   Name="NULL    1   Máquinas"},
            new Category(){Id=10 ,  Name="NULL    1   Caravanas e Autocaravanas"},
            new Category(){Id=11 ,  Name="NULL    1   Peças e Acessórios"},
            new Category(){Id=12 ,  Name="1   1   Citadino"},
            new Category(){Id=13 ,  Name="1   1   Utilitário"},
            new Category(){Id=14 ,  Name="1   1   Monovolume"}
        };


        var originalKeywordList = new List<Keyword>() 
        {
             new Keyword(){Id=1356 ,Name="ALFA"},
             new Keyword(){Id=1357 ,Name="ROMEO"},
             new Keyword(){Id=1358 ,Name="145"},
             new Keyword(){Id=1373 ,Name="147"}
        };
        #endregion declaration

        string searchText = "ALFA";

        // split the string searchText in an Array of substrings
        var splitSearch = searchText.Split(' '); 


        var searchKeyList =new List<Keyword>();

        // generate a list of Keyword based on splitSearch
        foreach (string part in splitSearch)
            if(originalKeywordList.Any(key => key.Name == part))
                searchKeyList.Add(originalKeywordList.First(key => key.Name == part));

        // generate a list of KeywordAdCategory  based on searchKList
        var searchKACList = new List<KeywordAdCategory>();
        foreach(Keyword key in searchKeyList)
            foreach (KeywordAdCategory kAC in originalKeywordAdCategoryList.Where(kac => kac.Keyword_Id == key.Id))
                searchKACList.Add(kAC);


        var groupedsearchKAClist = from kac in searchKACList group kac by kac.Keyword_Id;

        var listFiltered = new List<AdCar>(originalAdCarList);

        //here starts the real search part
        foreach (IGrouping<int, KeywordAdCategory> kacGroup in groupedsearchKAClist)
        {

            var listSingleFiltered = new List<AdCar>();
            //  generate a list of AdCar that matched the current KeywordAdCategory filter
            foreach (KeywordAdCategory kac in kacGroup)
                foreach (AdCar aCar in originalAdCarList.Where(car => car.Id == kac.Ad_Id))
                    listSingleFiltered.Add(aCar);

            var tempList = new List<AdCar>(listFiltered);
            // iterrates over a temporary copie of listFiltered and removes items which don't match to the current listSingleFiltered
            foreach (AdCar aC in tempList)
                if (!listSingleFiltered.Any(car => car.Id == aC.Id))
                    listFiltered.Remove(aC);
        }

        var AdCarCount = listFiltered.Count; // is the count of the AdCar who match

        var CatDic =new  Dictionary<Category, int>(); // will contain the Counts foreach Categorie > 0

        foreach(AdCar aCar in listFiltered)
            if(originalCategoryList.Any(cat => cat.Id ==aCar.Category.Id))
            {
                var selectedCat = originalCategoryList.First(cat => cat.Id == aCar.Category.Id);
                if (!CatDic.ContainsKey(selectedCat))
                {
                    CatDic.Add(selectedCat, 1);//new Category Countvalue
                }
                else
                {
                    CatDic[selectedCat]++; //Category Countvalue +1
                }
            }
    }
}

public class Keyword
{
    // Primary properties
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Category
{
    // Primary properties
    public int Id { get; set; }
    public string Name { get; set; }
}

public class KeywordAdCategory
{
    //[Key]
    //[Column("Keyword_Id", Order = 0)]
    public int Keyword_Id { get; set; }

    //[Key]
    //[Column("Ad_Id", Order = 1)]
    public int Ad_Id { get; set; }

    //[Key]
    //[Column("Category_Id", Order = 2)]
    public int Category_Id { get; set; }
}

public class Ad
{
    // Primary properties
    public int Id { get; set; }
    public string Title { get; set; }
    public string TitleStandard { get; set; }
    public string Version { get; set; }
    public int Year { get; set; }
    public decimal Price { get; set; }

    // Navigation properties
    public string Member { get; set; }
    public Category Category { get; set; }
    public IList<string> Features { get; set; }
    public IList<int> Pictures { get; set; }
    public IList<string> Operations { get; set; }
}

public class AdCar : Ad
{
    public int Kms { get; set; }
    public string Make { get; set; }
    public int Model { get; set; }
    public int Fuel { get; set; }
    public int Color { get; set; }
}

希望它会帮助你或其他人

编辑:

扩展了我的Methode Linqfilter()以回复请求

EDIT2:

我认为这应该是你正在寻找的

        var selectedKWLinq = from kw in originalKeywordList
                             where splitSearch.Contains(kw.Name) 
                             select kw;

        var selectedKACLinq = from kac in originalKeywordAdCategoryList
                              where selectedKWLinq.Any<Keyword>(item => item.Id == kac.Keyword_Id) 
                              group kac by kac.Keyword_Id into selectedKAC
                              select selectedKAC;

        var selectedAdCar = from adC in originalAdCarList
                           where (from skAC in selectedKACLinq
                                      where skAC.Any(kac => kac.Ad_Id == adC.Id)
                                  select skAC).Count() == selectedKACLinq.Count()
                           select adC;


        var selectedCategorys = from cat in originalCategoryList
                                join item in selectedAdCar
                                on cat.Id equals item.Category.Id
                                group cat by cat.Id into g
                                select g;

        //result part
        var AdCarCount = selectedAdCar.Count(); 

        List<IGrouping<int, Category>> list = selectedCategorys.ToList(); 
        var firstCategoryCount = list[0].Count();
        var secoundCategoryCount = list[1].Count();

答案 9 :(得分:1)

您正在对CategoryListByKeywordsDetailDto列表执行select distinct。 Distinct仅适用于POCO和匿名对象。在您的情况下,您需要实现IEqualitycomparer以使select distinct起作用。

答案 10 :(得分:1)

我在内存集合中使用LINQ直接尝试了这一点(例如,而不是通过SQL) - 似乎对我有用(我认为主要的一点是你要搜索适用于所有指定关键字的广告,而不是什么,对吗? 无论如何,下面的一些示例代码(一点点评论,并不一定是最有效的,但希望说明一点......)

使用以下“数据集”:

private List<AdCar> AdCars = new List<AdCar>();
private List<KeywordAdCategory> KeywordAdCategories = new List<KeywordAdCategory>();
private List<Category> Categories = new List<Category>();
private List<Keyword> Keywords = new List<Keyword>();

使用您提供的数据在测试方法中填充...

搜索方法看起来有点像这样:

var splitKeywords = keywords.Split(' ');

var validKeywords = Keywords.Join(splitKeywords, kwd => kwd.Name.ToLower(), spl => spl.ToLower(), (kwd, spl) => kwd.Id).ToList();

var groupedAdIds = KeywordAdCategories
                .GroupBy(kac => kac.Ad_Id)
                .Where(grp => validKeywords.Except(grp.Select(kac => kac.Keyword_Id)).Any() == false)
                .Select(grp => grp.Key)
                .ToList();

var foundKacs = KeywordAdCategories
    .Where(kac => groupedAdIds.Contains(kac.Ad_Id))
    .GroupBy(kbc => kbc.Category_Id, kac => kac.Ad_Id);

//Results count by category
var catCounts = Categories
    .Join(foundKacs, cat => cat.Id, kacGrp => kacGrp.Key, (cat, kacGrp) => new { CategoryName = cat.Name, AdCount = kacGrp.Distinct().Count() })
    .ToList();

//Actual results set
var ads = AdCars.Join(groupedAdIds, ad => ad.Id, grpAdId => grpAdId, (ad, grpAdId) => ad);

正如我所说,这更多是为了说明,请不要过分关注Joins&amp; amp; GroupBy等(不确定它的确切,呃,“最佳”)

所以,使用上面的内容,如果我搜索“Alfa”,我会得到3个广告结果,如果我搜索“Alfa 147”,我只得到1个结果。

编辑:我已将代码更改为代表两种可能的结果(因为我不确定您的问题需要哪些结果)

ads会为您提供搜索返回的实际广告

catCounts会列出一个匿名类型列表,每个类型代表搜索结果,按类别划分广告次数

这有帮助吗?

答案 11 :(得分:0)

Fiuu,这是脑力残骸。我将查询拆分成几个部分,但它最后作为一个整体执行(var结果)。我回到了匿名课堂,但意图很明确。

以下是解决方案:

var keywordIds = from k in keywordQuery
                    where splitKeywords.Contains(k.Name)
                    select k.Id;

var matchingKac = from kac in keywordAdCategories
            where keywordIds.Contains(kac.Keyword_Id)
            select kac;

var addIDs = from kac in matchingKac
                group kac by kac.Ad_Id into d
                where d.Count() == splitKeywords.Length
                select d.Key;

var groupedKac = from kac in keywordAdCategoryQuery
                where addIDs.Contains(kac.Ad_Id)
                group kac by new { kac.Category_Id, kac.Ad_Id };

var result = from grp in groupedKac
                group grp by grp.Key.Category_Id into final
                join c in categoryQuery on final.Key equals c.Id
                select new
                {
                    Id = final.Key,
                    Name = c.Name,
                    SearchCount = final.Count()
                };

// here goes result.ToList() or similar