15

I need to return a distinct list of records based on a car keywords search like: "Alfa 147"

The problem is that, as I have 3 "Alfa" cars, it returns 1 + 3 records (it seems 1 for the Alfa and 147 result, and 3 for the Alfa result)

EDIT:

The SQL-Server Query look something like this:

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')

My LINQ query is:

       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();

The entities are:

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

The result I expect for the search of "ALFA" is "Cars: 3" and for the search of "ALFA 147" is "Cars: 1" and actually the result I get is "Cars: 1 \n Cars: 3"

Patrick
  • 2,995
  • 14
  • 64
  • 125
  • 2
    I'm not sure what you're looking for - What do you want the returned data to look like? – Bobson Jan 14 '13 at 18:40
  • Hi, the query is returning 2 records for the search "Alfa 147", 1st: Cars - SearchCount = 1 / 2st: Cars - SearchCount = 3 – Patrick Jan 14 '13 at 23:23
  • I still don't know what you mean. Please add an example of your actual data, and what you want to have in `searchResults.CategoryListByKeywordsDetails` after running your code. – Bobson Jan 15 '13 at 14:23
  • Hi, I have Ads, Keywords related to the Ads to make a search, and Categories for the Ads, and I make the relationship in the KeywordAdCategory, so I know the Ads "Alfa Romeo 147", "Alfa Romeo 149" have the keywords to search it "ALFA" "ROMEO" "147" for the first and "ALFA" "ROMEO" "149" to search the second (The keywords are not repeated, if they already exist I use the same Id) in the Category "Cars". So if I search "ALFA 147", I will get the result "Cars: 1", and if search "ALFA", I will get "Cars: 2". – Patrick Jan 15 '13 at 15:27
  • 1
    I think you need to include some sample data and what your query is currently returning and more importantly what you want it to return. – sgmoore Jan 21 '13 at 19:31
  • I had sample data and the result expected and actual result. Thanks – Patrick Jan 21 '13 at 23:35
  • 1
    Can you provide data as a `new XX[] { new XX { YY = ... } }`, we could just copy paste it in VS ;) – Guillaume86 Feb 08 '13 at 15:25
  • Just added Array format of sample data to the question. – Patrick Feb 08 '13 at 17:40

12 Answers12

6

The kac is not filtering words... so this joins of kac, kac1 and kac2 will return 3 lines, cause this is the numbers of keywords for this ad

You should remove it..

Try this:

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

I did a test...

Setting the ambient as

    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)

I run these two queries:

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

I get this result set:

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

and the second query for two words...

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

Result set is:

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

Is this what you want?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Frederic
  • 1,018
  • 6
  • 11
  • Hi, thanks for your answer. It's almost that, but your queries only return 1 Category. So if I have Ads in more then one category, it will only return 1. I need to get all the categories including those keywords with the Ads count in it. – Patrick Feb 06 '13 at 11:28
  • it would return the count for each category.. if you need the count for all categories you can just cut off the "c.Id, c.Name" from the select field and the group by... if you need both count for each category and the total count.. should include "sum(count(*)) over () as [total count]" in select fields – Frederic Feb 06 '13 at 17:16
4

You can use the Distinct() method.

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

See This code returns distinct values. However, what I want is to return a strongly typed collection as opposed to an anonymous type for more details.

Community
  • 1
  • 1
Richard Schneider
  • 34,944
  • 9
  • 57
  • 73
  • Hi, I have completed the code with the return result that already include the Distinct, but it's not working. The problem maybe that there are keywords associated with more then one KeywordAdCategories record, and I need to make a distinct over the Keyword field, and I'm not able to. – Patrick Jan 14 '13 at 23:26
3

Split the query string into an array and iterate through querying the database for each keyword and joining the result sets using unions. The resultant set will be every distinct record that matches any of the given keywords.

GP24
  • 867
  • 2
  • 13
  • 28
2

Maybe this is close? At least the subqueries open it up a little for you to work with.

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
  };
Amy B
  • 108,202
  • 21
  • 135
  • 185
  • Hi, Thanks but it's not working, it gives all the categories and the SUM is not correct. I have completed the question with the SQL query that might match what I what to do with Link. I just was not able to find out the COUNT of the Ads for each Category. – Patrick Jan 15 '13 at 14:56
  • Added filtering, messed with Count. The sample Sql query would have the same problem as the sample Linq query (extra rows from extra matches). – Amy B Jan 15 '13 at 15:03
2

So, if I understand the need correctly, you want all of the subset of words to be matched in the text and not the OR matching you are getting right now? I see at least two options, the first of which may not translate the split to SQL:

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

This makes the following assumptions:

  1. Your words in the Keywords are space delimited.
  2. You are looking for exact matches and not partial matches. (I.e. Test will not match TestTest).

The other option being to dynamically generate a predicate using predicate builder (haven't done this in a while, my implementation might need tweaking - but this is the more likely (and better in my mind) solution):

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

query.Where(predicate);

If someone can comment if some of my syntax is off I would appreciate it. EDIT: Including link to a good reference on predicate builder: http://www.albahari.com/nutshell/predicatebuilder.aspx

UPDATE

Predicate builder across multiple tables, if anyone gets here looking for how to do that. Can PredicateBuilder generate predicates that span multiple tables?

Community
  • 1
  • 1
Matthew
  • 9,851
  • 4
  • 46
  • 77
  • Hi thanks, sorry but this way, I get no results. The result must include categories that include Ads with all the keywords specified in the search. – Patrick Jan 23 '13 at 10:51
  • Thank you for your help. I hope you get a positive result. – Patrick Jan 23 '13 at 15:03
2

One of the beautiful features of linq is that you can build up complicated queries in smaller and simpler steps and let linq figure out how to join them all together.

The following is one way to get this information. I'm not sure whether this is the best and you would need to check it performs well when multiple keywords are selected.

Assuming keywords is defined something like

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

Stage 1

Get a list of keywords grouped by Ad and Category and

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

Note, the classes you posted would suggest that your database does not have foreign key relationships defined between the tables. If they did then this stage would be slightly simpler to write.

Stage 2

Filter out any groups that do not have each of the keywords

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

Stage 3

Group by Category and count the results per category

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

Stage 4

Now retrieve the information from sql. This should be done all in one query.

var finalResults = groupedByCategories.ToList();
sgmoore
  • 15,694
  • 5
  • 43
  • 67
  • Hi, I get no result and debuging I get the error in Stage 3: InnerException = {"The nested query is not supported. Operation1='GroupBy' Operation2='MultiStreamNest'"} – Patrick Jan 23 '13 at 15:12
  • Do you get the exception when you are not debugging? – sgmoore Jan 23 '13 at 17:03
  • No, I get this by debuging and check using the immediate window – Patrick Jan 23 '13 at 18:22
  • Not exactly sure what the error means, but I have seen weird errors when continuing after queries are partly enumerated whilst debugging. I would suggest running Stage 1 alone to see if it gives you what you expect at that stage. Then restart and run Stage 1 and 2 together and finally restart and run all three. You could also try to see what SQL commands are reaching the sql-server. – sgmoore Jan 23 '13 at 19:54
  • You can use SQL Profiler. Alternatively see http://stackoverflow.com/questions/2659249/how-to-log-entity-framework-sql – sgmoore Jan 24 '13 at 11:01
2

Should be possible to query for each keyword then union the result sets. The duplicate values will be removed from the union and you can work out the required aggregations.

GP24
  • 867
  • 2
  • 13
  • 28
  • I think INTERSECT only returns those rows which appeared in both result sets being joined, which is not what is needed here as you might have more than one keyword, each returning different records. This can definitely be solved using unions, unfortunately I do not have time to investigate the exact syntax. – GP24 Jan 26 '13 at 07:57
1

Try removing the class while select

 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();
Nowshath
  • 832
  • 7
  • 14
  • Hi, thanks but the problem remains, for 1 keyword "ALFA" everything works fine "Cars: 3", but when I had 2 keywords "ALFA 147", the result is "Cars: 1, Cars: 3". It seems that it makes a correct result in the first line and ad the total in the second one. – Patrick Jan 23 '13 at 10:44
  • Sry Dude... I cant understand this "It seems that it makes a correct result in the first line and ad the total in the second one" – Nowshath Jan 24 '13 at 04:31
  • If you search "ALFA" the result is correct "cars: 3", if you search "ALFA 147" the result is 2 records "cars: 1" (this is correct) and "cars: 3". – Patrick Jan 24 '13 at 09:51
  • the splitKeywords contains the Name "ALFA 147" i think so. when we query it with contains it returns true for both ALFA 147 & ALFA since the string is present. Please change the query with where ...... from k in keywordQuery where splitKeywords.Contains(k.Name) to from k in keywordQuery where splitKeywords = k.Name – Nowshath Jan 25 '13 at 05:01
  • How do you compare a String (k.Name) with an array of strings (splitKeywords) ? – Patrick Feb 06 '13 at 11:45
  • when we search for "ALFA 147" Does the splitKeywords contains [ALFA,147] ?? – Nowshath Feb 07 '13 at 06:55
  • did you get the correct record when u search for "ALFA 147" (i.e) only the Cars:1.... y do u want to split the keywords can u explain??? – Nowshath Feb 07 '13 at 13:22
  • The search is like google, I take the words from a search, standerized them, and search them in keywords table where resides standarized keywords to find the match – Patrick Feb 07 '13 at 16:45
  • K Fine.. Then it works correct right it brings 2 records for "ALFA 147" (i.e) Cars:1 and Cars:3(ALFA) – Nowshath Feb 08 '13 at 04:46
  • Sorry but it's not working. When you search 1 keyword everything works great but when you search 2 keywords, and for an example where I only have 2 ads in database (Alfa 145 and Alfa 147), I get cars=2 for the search "ALFA" but I get "cars=1, cars=2" for the search "ALFA 147". I think the problem resides in eliminates from the KeywordAdCategory filter in the query every records that don't match every keyword. – Patrick Feb 08 '13 at 10:29
  • No because the Keywords table only store individual keywords like "ALFA" and "147" – Patrick Feb 11 '13 at 10:46
  • Store in table as that.. bt y don't u try the query without splitting juz to make sure whether it works or not.. – Nowshath Feb 11 '13 at 10:51
  • Tried this way ???? from k in keywordQuery join s in splitKeywords on k.Name equals s – Nowshath Feb 15 '13 at 06:30
  • Hi, thank you but I already solve the problem with Nenad's solution, but thanks again for your time. Regards. – Patrick Feb 15 '13 at 11:04
1

hi if i understand your problem correctly

"The problem is that, as I have 3 "Alfa" cars, it returns 1 + 3 records (it seems 1 for the Alfa and 147 result, and 3 for the Alfa result)"

and Linq isn't really required i maybe have what you need just test it as new project

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

hopefully it will help you or someone else

Edit:

extended my Methode Linqfilter() to answer the request

Edit2:

i think that should be exactly what you are looking for

        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();
WiiMaxx
  • 5,322
  • 8
  • 51
  • 89
  • Hi Thanks, but from where do I extract the Category Name and the Ads Count for each category in your code? – Patrick Feb 11 '13 at 16:55
  • `listFiltered.Count` will return the Counts. And which `Category` do you wan't from your `Ad` or from `KeywordAdCategory` the corresponding `Category`? – WiiMaxx Feb 12 '13 at 07:32
  • Each Ad has only one Category, so I want the count of Ads for each category that have all the keyword's search – Patrick Feb 12 '13 at 13:26
  • added some lines to `Linqfilter()` to answer your question – WiiMaxx Feb 12 '13 at 13:58
1

You are doing a select distinct on a list of CategoryListByKeywordsDetailDto. Distinct only works on POCO and anonymous objects. In your case you need to implement the IEqualitycomparer for select distinct to work.

developer747
  • 15,419
  • 26
  • 93
  • 147
1

I tried this using LINQ directly against in memory collections (as in, not through SQL) - seems to work for me (I think the main point being that you want to search for Ads that apply to ALL the keywords specified, not ANY, correct? Anyway, some sample code below (a little comment-ish and not necessarily the most efficient, but hopefully illustrates the point...)

Working with the following "data sets":

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>();

which are populated in a test method using the data you provided...

Search method looks a little like this:

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

As I said, this is more to illustrate, please don't look too closely at the use of Joins & GroupBy etc (not sure its exactly, er, "optimal")

So, using the above, if I search for "Alfa", I get 3 Ad results, and if I search for "Alfa 147" I get just 1 result.

EDIT: I've changed the code to represent two possible outcomes (as I wasn't sure which was needed by your question)

ads will give you the actual Ads returned by the search

catCounts will give a list of anonymous types each representing the find results as a count of Ads by category

Does this help?

Kevin Versfeld
  • 710
  • 5
  • 18
  • In your question you say you need to "return a distinct list of records...", but in several comments thus far you seem to be needing a count of Ads per category? Could you clarify which is important please? If the latter, I will be able to update my answer fairly quickly to achieve this, I believe. – Kevin Versfeld Feb 13 '13 at 11:40
0

Fiuu, this was brain-wreck. I splited query in several pieces, but it's executed as a whole at the end (var result). And I returned anonymous class, but intention is clear.

Here is the solution:

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
Nenad
  • 24,809
  • 11
  • 75
  • 93
  • 1
    Well, I don't know how to say it, but....IT'S FINALLY WORKING!! Thanks Nenad ;) I just had to change the splitKeywords.Length that it's not supported in Linq to splitKeywords.Count() and had the return model: var searchResults = new CategoryListByKeywordsListDto(); searchResults.CategoryListByKeywordsDetails = result.ToList(); . Thanks again. – Patrick Feb 14 '13 at 10:33
  • Hi Nenad, can you help me on this one: http://stackoverflow.com/questions/14853840/ ? Thanks – Patrick Mar 20 '13 at 17:59
  • Hi Nenad, I need your help in an upgrade I have done to your answer. Now I use a KeywordSearch Table to have related words search for a keyword, please check: http://stackoverflow.com/q/19796132/1480877 – Patrick Nov 05 '13 at 18:51