使用Entity Framework跨多个表创建动态查询?

时间:2012-07-16 14:30:48

标签: entity-framework linq-to-entities

我正在尝试按照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

2 个答案:

答案 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。可以从每个组中的第一个地址获取StreetNameStreetNumber,并从每个组中的人员投射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可以有很多人地址),但如果表格如下所示,上面的代码会按照预期产生三个查询的三个结果:

enter image description here

AddressMap表实际上并未在查询中使用,因为AddressesPeople表格是通过AddressMap_ID列直接加入的。