LINQ query with Multiple Tables? - c#

I am joining the two tables using LINQ to match model class like below
lstOptInInterest = new LinkedList<OptInInterestArea>
((from a in dbEntities.SUBCODE
from appCode in dbEntities.CODE.Where(
x => x.CODE == a.CODE && x.TYPE == a.TYPE)
select new OptInInterestArea()
{
Code = a.CODE,
SubCode = a.SUBCODE,
SubCodeDescription = a.DESCR,
CodeDescription = appCode.DESCR
}).ToList());
Model class
public class OptInInterestArea
{
[DisplayName("Code")]
public string Code { get; set; }
[DisplayName("Sub Code")]
public string SubCode { get; set; }
DisplayName("Sub Code Description")]
public string SubCodeDescription { get; set; }
[DisplayName("Code Description")]
public string CodeDescription { get; set; }
[DisplayName("Previous OptIn")]
public bool PrevOptIn { get; set; }
}
Table B
Now my question is I need to assign PrevOptIn values of lstOptInInterest from Table B (see above). Table B may or may not contains all of CODE and SUBCODE of lstOptInInterest.
If CODE and SUBCODE of lstOptInInterest exists on the table B assign to PrevOptIn in else PrevOptIn = N
How can I do LINQ to get this?

first, I suggest you use INNER JOIN instead of CROSS JOIN to query your lstOptInInterest :
lstOptInInterest = new LinkedList<OptInInterestArea>
((from a in dbEntities.SUBCODE
join appCode in dbEntities.CODE
on new {CODE=a.CODE,TYPE=a.TYPE} equals new {CODE=x.CODE,TYPE=x.TYPE}
select new OptInInterestArea()
{
Code = a.CODE,
SubCode = a.SUBCODE,
SubCodeDescription = a.DESCR,
CodeDescription = appCode.DESCR
}).ToList());
seconde, use LEFT OUT JOIN to assign PrevOptIn values of lstOptInInterest from Table B :
lstOptInInterest = new LinkedList<OptInInterestArea>
(
(from a in lstOptInInterest
join b in dbEntities.TableB
on new {CODE=a.Code,SUBCODE=a.SubCode} equals new {CODE=b.CODE,SUBCODE=b.SUBCODE}
into leftGroup
from b in leftGroup.DefaultIfEmpty()
select new OptInInterestArea()
{
Code = a.Code,
SubCode = a.SubCode,
SubCodeDescription = a.SubCodeDescription,
CodeDescription = a.CodeDescription,
PrevOptIn=b==null? false : b.OPTIN=="Y"
}).ToList()
);
UPDATE:
Try to test this only 1 steps to get your result:
lstOptInInterest = new LinkedList<OptInInterestArea>
((from a in dbEntities.SUBCODE
join appCode in dbEntities.CODE
on new {CODE=a.CODE,TYPE=a.TYPE} equals new {CODE=x.CODE,TYPE=x.TYPE}
join b in dbEntities.TableB
on new {CODE=a.CODE,SUBCODE=a.SUBCODE} equals new {CODE=b.CODE,SUBCODE=b.SUBCODE}
into leftGroup
from b in leftGroup.DefaultIfEmpty()
select new OptInInterestArea()
{
Code = a.CODE,
SubCode = a.SUBCODE,
SubCodeDescription = a.DESCR,
CodeDescription = appCode.DESCR,
PrevOptIn=b==null?false : b.OPTIN=="Y"
}).ToList());

Related

How to Get Last Record Using LINQ - Entity Framework in SQLite

I need to translate the following SQLite query to LINQ in C#
SELECT sup.SupplierName, sup.SupplierID, pr.Price, max(pr.AddedDate)
FROM Suppliers sup
LEFT JOIN ItemsPrices pr
USING(SupplierID)
WHERE pr.ItemID = '22'
GROUP BY sup.SupplierName
I've searched about all web site and tried the following LINQ query and it does group like what I want but doesn't select latest date. I'm newbie in LINQ please help me
internal List<LatestPriceDbo> GetLatestPrice(int _itemid)
{
using (dbContext context = new dbContext())
{
var groupedPrice = from a in context.ItemsPrices
where a.ItemId == _itemid
orderby a.Id descending
group a by new { a.ItemId, a.SupplierId } into g
select new ItemsPrice
{
SupplierId = g.Key.SupplierId,
ItemId = g.Key.ItemId,
Price = g.FirstOrDefault().Price,
AddedDate = g.Max(s => s.AddedDate)
};
var result = (from c in context.Suppliers
from k in groupedPrice
where k.ItemId == _itemid && c.SupplierId == k.SupplierId
select new LatestPriceDbo
{
supid = c.SupplierId,
supname = c.SupplierName,
price = k.Price,
addeddate = k.AddedDate
}).ToList();
return result;
}
}
internal class LatestPriceDbo
{
public int supid { get; set; }
public string supname { get; set; }
public decimal price { get; set; }
public string addeddate { get; set; }
}
I am using Database-First.
You should be able to use a LINQ Join I have mocked up something which might point you in the correct direction:
Notes
use the join first to get the set you looking for
you can then do a nested select for the max based on supplierId.
from a in context.ItemsPrices
join s in context.Suppliers on a.SupplierId equals s.supplierId
where a.ItemId == _itemid
orderby a.Id descending
select new ItemsPrice
{
SupplierName = s.SupplierName
SupplierId = a.SupplierId,
ItemId = a.ItemId,
Price = a.FirstOrDefault().Price,
AddedDate = context.ItemsPrices.Where(x => x.SupplierId == a.SupplierId).Max(s => s.AddedDate)
};
I solved the problem owing to Kevin's suggestion. I did need to be more search on web to improve the code block of Kevin's comment and I did.
internal List<LatestPriceDbo> GetLatestPrice(int _itemid)
{
using (dbContext context = new dbContext())
{
var result = (from a in context.ItemsPrices
join s in context.Suppliers on a.SupplierId equals s.SupplierId
where a.ItemId == _itemid
orderby a.Id descending
group new { a, s } by new { a.SupplierId, a.ItemId } into grb
select new LatestPriceDbo
{
supname = grb.FirstOrDefault().s.SupplierName,
supid = grb.Key.SupplierId,
itemid = grb.Key.ItemId,
price = context.ItemsPrices
.Where(x => x.ItemId == grb.FirstOrDefault().a.ItemId)
.OrderByDescending(z => z.Id).Select(z => z.Price)
.FirstOrDefault(),
addeddate = context.ItemsPrices
.Where(x => x.SupplierId == grb.FirstOrDefault().a.SupplierId)
.Max(s => s.AddedDate)
}).ToList();
return result;
}
}
internal class LatestPriceDbo
{
public int itemid { get; set; }
public int supid { get; set; }
public string supname { get; set; }
public decimal price { get; set; }
public string addeddate { get; set; }
public int recordid { get; set; }
}

Convert stored procedure to Linq statement

I am getting runtime errors when I try and convert this stored procedure to a Linq statement about missing }.I am not sure if I am doing the CASE statement correct in linq. Is there a better way to do this?
SQL
SELECT TOP 1
C.CustomerId,
Institution = (SELECT TOP 1
CASE Name
WHEN 'In1' THEN 'Institution 1'
ELSE Name
END
FROM [Group] G
JOIN CustomerXrefs X ON X.GroupId = G.GroupId AND G.GroupTypeId = 308
WHERE X.CustomerId = C.CustomerId),
G.GroupId
FROM
Customer C
JOIN
CustomerXrefs X ON C.CustomerId = X.CustomerId
JOIN
[Group] G ON X.GroupId = G.GroupId
WHERE
C.Email = #Email
AND G.GroupTypeId = 308
ORDER BY
G.GroupId
Linq
var query1 =
(from c in db.Customers
join cx in db.CustomerXrefs on c.CustomerId equals cx.CustomerId
join g in db.Groups on cx.GroupId equals g.GroupId
select new Customer
{
CustomerId = c.CustomerId,
Institution = (from ig in db.Groups
join icx in db.CustomerXrefs
on ig.GroupId equals icx.GroupId && ig.GroupTypeId == 308
where icx.CustomerId == c.CustomerId
select new Institution
{
Name = ig.Name == "In1 " ? "Institution 1" :
ig.Name == "In2" ? "Institution 2" :
ig.Name
}).FirstOrDefault();
}).FirstOrDefault();
I got rid of all errors by simulating with classes
class Program
{
static void Main(string[] args)
{
DataBase db = new DataBase();
var query1 = (from c in db.Customers
join cx in db.CustomerXrefs on c.CustomerId equals cx.CustomerId
join g in db.Groups on cx.GroupId equals g.GroupId
select new Customer
{
CustomerId = c.CustomerId,
Institution = (from ig in db.Groups
join icx in db.CustomerXrefs on ig.GroupId equals icx.GroupId
where icx.CustomerId == c.CustomerId && ig.GroupTypeId == 308
select new Institution()
{
Name = ig.Name == "In1 " ? "Institution 1" :
ig.Name == "In2" ? "Institution 2" :
ig.Name
}
).FirstOrDefault()
});
}
}
public class DataBase
{
public List<Customer> Customers { get; set; }
public List<CustomerXrefs> CustomerXrefs { get; set; }
public List<Group> Groups { get; set; }
}
public class Customer
{
public string CustomerId { get; set; }
public Institution Institution { get; set; }
}
public class CustomerXrefs
{
public string CustomerId { get; set; }
public string GroupId { get; set; }
}
public class Group
{
public string GroupId { get; set; }
public int GroupTypeId { get; set; }
public string Name { get; set; }
}
public class Institution
{
public string Name { get; set; }
}

Linq query with Average

I have 3 tables:
tblCompany : Id, Name, Location
tblRating : Id, CompanyId, Rate
tblImages : Id, CompanyId, ImagePath
I have a class
public class Company
{
public string Id { get; set; }
public string CompanyName { get; set; }
public string Location { get; set; }
public string AverageRate { get; set; }
public List<string> ImagePath { get; set; }
}
I want a LINQ query to produce a result to match the Company class.
I wrote this query but it does not work
List<Company> result = null;
using (DataContext dc = new DataContext())
{
result = (from a in dc.GetTable<tblCompany>()
join b in dc.GetTable<tblRating>()
on a.Id equals b.CompanyId
join c in dc.GetTable<tblImages>()
on a.Id equals c.CompanyId
select new SearchResult
{
CompanyName = a.Company,
Location = a.Location,
AverageRate = b.Rate.Average(),
ImagePath = c.ImagePath.ToList()
}).ToList<Company>();
}
Edited for whole Query:
(I've to say i'm sorry but i have no way of testing this query yet)
You can use the let clause instead of the joins:
var result = (from c in dc.GetTable<tblCompany>()
let r = (from re in dc.GetTable<tblRating>()
where re.CompanyId == c.Id && re.Rate != null
select re.Rate)
let i = (from im in dc.GetTable<tblImages>()
where im.CompanyId == c.Id
select im.ImagePath)
select new SearchResult
{
CompanyName = c.Name,
Location = c.Location,
AverageRate = r.Average(),
ImagePath = i.ToList()
}).ToList<Company>();
try this -
result = (from a in dc.GetTable<tblCompany>()
join b in dc.GetTable<tblRating>()
on a.Id equals b.CompanyId
join c in dc.GetTable<tblImages>()
on a.Id equals c.CompanyId
group new { b.Rate, c.ImagePath}
by new { a.Id, a.Location,a.Name} into groupList
select new Company
{
CompanyName = groupList.Key.Name,
Location = groupList.Key.Location,
AverageRate = groupList.Average(a=>a.Rate),
ImagePath = groupList.Select(i=>i.ImagePath).ToList()
}).ToList<Company>();

How to populate `ICollection<T>` property inside a `List<T>` linq

My Class
public partial class CTITLE_CHECKLIST : CError
{
public int Id { get; set; }
[Required(ErrorMessage = "Requerido")]
public int ID_Tipo_Checklist { get; set; }
[Required(ErrorMessage = "Requerido")]
public string Descripcion { get; set; }
public virtual CTipo_CheckList Tipo_CheckList { get; set; }
public ICollection<CSUBTITLE_CHECKLIST> Subtitulos { get; set; }
}
My method :
public List<CTITLE_CHECKLIST> GetCheked(string codigo, int tipoCheckList)
{
try
{
var result = (from a in db.TITLE_CHECKLIST
from t in db.Tipo_CheckList
where a.ID_Tipo_Checklist == t.ID
where a.ID_Tipo_Checklist == tipoCheckList
select new CTITLE_CHECKLIST
{
Descripcion = a.Descripcion,
Id = a.Id,
ID_Tipo_Checklist = a.ID_Tipo_Checklist,
Tipo_CheckList = new CTipo_CheckList
{
Descripcion = t.Descripcion,
ID = t.ID,
ID_Depto = t.ID_Depto
},
Subtitulos = (from s in db.SUBTITLE_CHECKLIST
where s.ID_Title == a.Id
select new CSUBTITLE_CHECKLIST
{
AmountCK = s.AmountCK,
Descripcion = s.Descripcion,
ID = s.ID,
ID_Title = s.ID_Title,
Numeracion = s.Numeracion,
checkList = (from ck in db.CheckList
where ck.ID_Subtitle == s.ID
&& ck.Codigo == codigo
select new CCheckList
{
CK = ck.CK,
Amount = ck.Amount,
Codigo = ck.Codigo,
Codigo_TFile = ck.Codigo_TFile,
Comentario = ck.Comentario,
ID = ck.ID,
ID_Subtitle = ck.ID_Subtitle,
UserCre = ck.UserCre,
UserMod = ck.UserMod
}).FirstOrDefault()
})//here I put ToList
}).ToList();
An my error this :
Error 2 Cannot implicitly convert type 'System.Linq.IQueryable<TROP.Areas.TRAFICO.Controllers.LOGICA.CSUBTITLE_CHECKLIST>' to 'System.Collections.Generic.ICollection<TROP.Areas.TRAFICO.Controllers.LOGICA.CSUBTITLE_CHECKLIST>'. An explicit conversion exists (are you missing a cast?) C:\Users\jmitchell\Documents\Visual Studio 2012\Projects\TROP\TROP\Areas\TRAFICO\Controllers\LOGICA\CTITLE_CHECKLIST.cs 136 49 TROP
And When I put .ToList, where say Here I put ToList it throw an error that say :
LINQ to Entities does not recognize the method 'System.Collections.Generic.List`1[TROP.Areas.TRAFICO.Controllers.LOGICA.CSUBTITLE_CHECKLIST] ToList[CSUBTITLE_CHECKLIST](System.Collections.Generic.IEnumerable`1[TROP.Areas.TRAFICO.Controllers.LOGICA.CSUBTITLE_CHECKLIST])' method, and this method cannot be translated into a store expression.
And I Know that if I change the property subtitles to IEnumerable<T> it will work, but, Im trying to bind this model from MVC4 post, And I doesn't bind with IEnumerable<T>, it bind with a ICollection<T>, List<T> , I am like 1 and a half day trying to figure out this problem.
So the choices are not "do the whole thing in the database" or "do the whole thing in memory". You want to do some of both. Do everything on the DB end that you can, and then when you've done all of that, finish up the remaining operations in memory. This general pattern (mostly leveraging AsEnumerable) will allow you to do this:
(from a in db.TITLE_CHECKLIST
from t in db.Tipo_CheckList
where a.ID_Tipo_Checklist == t.ID
where a.ID_Tipo_Checklist == tipoCheckList
select new //note were using an anonymous type here,
//as the real type can't take a non-list
{
Descripcion = a.Descripcion,
Id = a.Id,
ID_Tipo_Checklist = a.ID_Tipo_Checklist,
Tipo_CheckList = new CTipo_CheckList
{
Descripcion = t.Descripcion,
ID = t.ID,
ID_Depto = t.ID_Depto
},
Subtitulos = from s in db.SUBTITLE_CHECKLIST
where s.ID_Title == a.Id
select new CSUBTITLE_CHECKLIST
{
AmountCK = s.AmountCK,
Descripcion = s.Descripcion,
ID = s.ID,
ID_Title = s.ID_Title,
Numeracion = s.Numeracion,
checkList = (from ck in db.CheckList
where ck.ID_Subtitle == s.ID
&& ck.Codigo == codigo
select new CCheckList
{
CK = ck.CK,
Amount = ck.Amount,
Codigo = ck.Codigo,
Codigo_TFile = ck.Codigo_TFile,
Comentario = ck.Comentario,
ID = ck.ID,
ID_Subtitle = ck.ID_Subtitle,
UserCre = ck.UserCre,
UserMod = ck.UserMod
}).FirstOrDefault()
}//note no ToList
})
//This will ensure that all operators that follow
//are done in memory, not on the DB end
.AsEnumerable()
.Select(checklist => new CTITLE_CHECKLIST
{
Descripcion = checklist.Descripcion,
Id = checklist.Id,
ID_Tipo_Checklist = checklist.ID_Tipo_Checklist,
Subtitulos = Subtitulos.ToList(),
});

Linq distinct record containing keywords

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"
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?
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.
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.
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
};
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();
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:
Your words in the Keywords are space delimited.
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?
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.
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();
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.
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?
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();
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

Categories

Resources