Linq различная запись, содержащая ключевые слова
Мне нужно вернуть отдельный список записей на основе поиска ключевых слов в автомобиле, например: "Alfa 147"
Проблема в том, что, поскольку у меня есть 3 автомобиля "Альфа", он возвращает 1 + 3 записи (кажется, 1 для результата Alfa и 147, а 3 для результата Alfa)
EDIT:
Запрос 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"
Ответы
Ответ 1
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
Ответ 2
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
Это то, что вы хотите?
Ответ 3
Вы можете использовать метод Distinct().
var query = ...
var query = query.Distinct();
См. Этот код возвращает различные значения. Тем не менее, я хочу, чтобы получить более типичную коллекцию, а не анонимный тип для более подробной информации.
Ответ 4
Разделите строку запроса в массив и выполните итерацию с помощью запроса к базе данных для каждого ключевого слова и объединения наборов результатов с помощью union. Результирующим набором будет каждая отдельная запись, соответствующая любому из указанных ключевых слов.
Ответ 5
Может, это близко? По крайней мере, подзапросы открывают для вас немного работы.
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
};
Ответ 6
Итак, если я правильно понимаю необходимость, вы хотите, чтобы все подмножество слов соответствовало тексту, а не совпадению OR, которое вы сейчас получаете? Я вижу по крайней мере два параметра, первый из которых не может перевести split на SQL:
var query = from k in keywordQuery where !splitKeywords.Except(k.Name.split(' ')).Any()
Это делает следующие предположения:
- Ваши слова в ключевых словах ограничены пробелом.
- Вы ищете точные совпадения, а не частичные совпадения. (I.e. Test не будет соответствовать TestTest).
Другой вариант заключается в том, чтобы динамически генерировать предикат с использованием предиката-строителя (не делал этого через некоторое время, моя реализация может потребоваться настройка), но это более вероятно (и лучше на мой взгляд) решение):
var predicate = PredicateBuilder.True<keywordQuery>();
foreach (string s in splitKeywords) {
predicate.AND(s.Contains(k.Name));
}
query.Where(predicate);
Если кто-то может прокомментировать, если какой-то из моих синтаксисов выключен, я был бы признателен. EDIT: включение ссылки на хорошую ссылку на построитель предикатов: http://www.albahari.com/nutshell/predicatebuilder.aspx
UPDATE
Создатель Predicate для нескольких таблиц, если кто-то ищет здесь, как это сделать.
Может ли PredicateBuilder генерировать предикаты, охватывающие несколько таблиц?
Ответ 7
Одна из красивейших особенностей 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();
Ответ 8
Должно быть возможным запросить каждое ключевое слово, а затем объединить результирующие множества. Дублирующие значения будут удалены из объединения, и вы сможете выработать необходимые скопления.
Ответ 9
Попробуйте удалить класс, выбрав
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();
Ответ 10
привет, если я правильно понимаю вашу проблему
"Проблема в том, что, поскольку у меня есть 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; }
}
надеюсь, это поможет вам или кому-то еще
Изменить:
расширил мой метод 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();
Ответ 11
Вы делаете выделение в списке CategoryListByKeywordsDetailDto. Отдельный работает только с POCO и анонимными объектами. В вашем случае вам нужно реализовать IEqualitycomparer для выбора отличного для работы.
Ответ 12
Я пробовал это с помощью 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 и GroupBy и т.д. (не уверен, что это точно, er, "оптимально" )
Итак, используя вышеизложенное, если я ищу "Альфа", я получаю 3 объявления, и если я ищу "Alfa 147", я получаю всего 1 результат.
EDIT: я изменил код, чтобы представить два возможных результата (поскольку я не был уверен, что было необходимо по вашему вопросу)
ads
предоставит вам фактические Объявления, возвращенные поиском
catCounts
предоставит список анонимных типов, каждый из которых будет отображать результаты поиска в виде количества объявлений по категориям
Помогает ли это?