我正在尝试按照Creating dynamic queries with entity framework
中的模式在几个表中创建搜索功能我有3张桌子:
People:
pk ID
varchar FirstName
varchar LastName
fk AddressMap_ID
AddressMap:
pk ID
Address:
pk ID
varchar StreetName
varchar StreeNumber
fk AddressMap_ID
多人可以住在一个地址。我传入一个搜索模型,并填充结果属性:
public class Search
{
public string streetname { get; set; }
public string streetnumber { get; set; }
public string fname { get; set; }
public string lname { get; set; }
public IEnumerable<Results> results { get; set; }
}
public class Results
{
public int AddressID { get; set; }
public string StreetNumber { get; set; }
public string StreetName { get; set; }
public IEnumerable<PeopleResults> people { get; set; }
}
public class PeopleResults
{
public int personID { get; set; }
public string First { get; set; }
public string Last { get; set; }
}
如果我过滤地址或名称+地址,则此方法有效:
public void GetResults(Search model)
{
Entities _context;
_context = new Entities();
var addr = from a in _context.Addresses
select a;
addr = addr.Where(filter => filter.StreetNumber == model.streetnumber);
addr = addr.Where(filter => filter.StreetName == model.streetname);
addr = from a in addr
group a by a.AddressMap_ID into addrs
select addrs.FirstOrDefault();
var ppl = from p in addr.SelectMany(p => p.AddressMap.People) select p;
ppl = ppl.Where(filter => filter.FirstName.StartsWith(model.fname));
ppl = ppl.Where(filter => filter.LastName.StartsWith(model.lname));
model.results = from a in addr
select new Results
{
AddressID = a.ID,
StreetName = a.StreetName,
StreetNumber = a.StreetNumber,
people = from p in ppl
select new PeopleResults
{
First = p.FirstName,
Last = p.LastName
}
};
}
但是,如果我只是尝试过滤名称,它会返回一个笛卡尔联接 - 每个地址都包含所有匹配的人。
有三种搜索方式:仅对地址进行过滤,对地址+名称进行过滤,或仅对名称进行过滤。
因此,如果有人搜索“123 Main”,结果应为
123 Main St SticksVille Joe Smith
Jane Smith
Mary Smith
123 Main St Bedrock Fred Flintstone
Wilma Flintstone
搜索“J Smith 123 Main”应该只返回:
123 Main St SticksVille Joe Smith
Jane Smith
只搜索“J Smith”应该返回:
123 Main St SticksVille Joe Smith
Jane Smith
456 Another St Sometown Jerry Smith
答案 0 :(得分:1)
看起来像这样的方法可能会起作用:
IQueryable<Person> ppl = _context.People;
ppl = addr.Where(filter=>filter.First.StartsWith(model.fname));
ppl = addr.Where(filter=>filter.Last.StartsWith(model.lname));
var pplIds = ppl.Select(p => p.PersonId);
model.results = from a in addr
where a.AddressMap.People.Any(p => pplIds.Contains(p.PersonId))
select new Results {
AddressID = a.ID,
StreetName = a.StreetName,
StreetNumber = a.StreetNumber,
people = from p in a.People
select new PeopleResults {
First = p.FirstName,
Last = p.LastName
}
};
不是将people
属性基于匹配的人,而是希望将整个地址集建立在匹配的人身上。
答案 1 :(得分:1)
对于人和地址,您的查询对我来说是“对称的”,它只会在最终预测结果中变得“不对称”。所以,我的想法是尽可能在查询中表达这种对称性:
获取按街道名称和街道号码过滤的地址(IQueryable<Address>
,一次不执行)
获取按名字和姓氏开头过滤的人(IQueryable<Person>
,不立即执行)
按AddressMap_ID
加入两套。生成的人员和地址集仅包含满足地址和人员的过滤条件的对。如果未提供人员或地址的过滤条件之一(问题底部的第一个和第三个示例),则联接发生在未过滤的所有人/地址集上,即联合对包含所有人已过滤的地址(或已过滤的人的所有地址)
按Address.ID
将群组投射到Results
集合中。组密钥是AddressID
。可以从每个组中的第一个地址获取StreetName
和StreetNumber
,并从每个组中的人员投射people
。
执行查询
以下代码并未详细说明未提供四个过滤条件的情况。它适用于这种情况,但只会加载所有这些地址的人的地址。也许你想在这种情况下抛出异常。或者不返回任何内容(model.Results = null
左右),然后跳出方法。
public void GetResults(Search model)
{
using (var _context = new Entities())
{
// "All" addresses
IQueryable<Address> addresses = _context.Addresses;
// "All" people
IQueryable<Person> people = _context.People;
// Build a Queryable with filtered Addresses
if (!string.IsNullOrEmpty(model.streetname))
addresses = addresses.Where(a => a.StreetName
.StartsWith(model.streetname));
if (!string.IsNullOrEmpty(model.streetnumber))
addresses = addresses.Where(a => a.StreetNumber
.StartsWith(model.streetnumber));
// Build a Queryable with filtered People
if (!string.IsNullOrEmpty(model.fname))
people = people.Where(p => p.FirstName == model.fname);
if (!string.IsNullOrEmpty(model.lname))
people = people.Where(p => p.LastName == model.lname);
// Join the two Queryables with AddressMap_ID
// and build group by Address.ID containing pairs of address and person
// and project the groups into the Results collection
var resultQuery = from a in addresses
join p in people
on a.AddressMap_ID equals p.AddressMap_ID
group new { a, p } by a.ID into g
select new Results
{
AddressID = g.Key,
StreetName = g.Select(ap => ap.a.StreetName)
.FirstOrDefault(),
StreetNumber = g.Select(ap => ap.a.StreetNumber)
.FirstOrDefault(),
people = g.Select(ap => new PeopleResults
{
First = ap.p.FirstName,
Last = ap.p.LastName
})
};
// Execute query (the whole code performs one single query)
model.results = resultQuery.ToList();
}
}
我不确定我是否正确地将AddressMap
表解释为多对多关系的一种联接表(Address
可以有很多人,Person
可以有很多人地址),但如果表格如下所示,上面的代码会按照预期产生三个查询的三个结果:
AddressMap
表实际上并未在查询中使用,因为Addresses
和People
表格是通过AddressMap_ID
列直接加入的。