如何编写EF Core Query来过滤多个表?

时间:2018-02-24 22:06:09

标签: c# asp.net-core entity-framework-core

将ASP Core 2与EF Core和SQL Server一起使用。我有,我认为这是一个直接的任务,检索给定分销商的制造商(或个别制造商)名单。

Users表提供经过身份验证的用户,每个用户都与一个分发服务器相关联(并在模型中表示为GetManufacturers())。因此,当在ManufacturersController上调用操作GetManufacturers(int id)时,它应该返回给定分发服务器的所有制造商。同样,await _context.Manufacturers .Include(a => a.Addresses) .Include(m => m.DistributorManufacturers) .Where (a => a.AddressesNavigation.State = "CA") .Where (m => m.Id == id) // Manufacturers Id .Where (d => d.DistributorManufacturers.DistributorId == _user.DistributorId) .AsNoTracking() .ToListAsyc() 如果与经过身份验证的分销商相关联,则应返回单个制造商。

Tables

要做到这一点,我尝试各种配方,如:

ICollection<DistributorManufacturers>

VS抱怨DistributorId不包含.ThenInclude的定义(即使我从课程中复制/粘贴了它)。它在概念上与我在地址上的过滤器不同。

我还尝试title='WordPress Gallery'><div style='display:block;width:180px;height:20px;text-align:center;border-radius:3px;-moz-border-radius:3px添加Distributors表但没有运气。

DistributorManufacturers表是使用Scaffold-DbContext创建的,并且定义了外键和导航属性。

2 个答案:

答案 0 :(得分:1)

所以,做了一些工作来重新创建你的模型。我唯一改变的是我在Distributor表中添加了userId而不是相反。这将是一个很长的答案..所以挂在

模型(省略了用户和地址实体,因为它们并没有什么特别之处)

public abstract class Entity
{
    public int Id { get; set; }
}

public class Distributor : Entity
{   
    public User User { get; set; }

    public int UserId { get; set; }

    public Address Address { get; set; }

    public int AddressId { get; set; }

    public ICollection<DistributorManufacturer> DistributorManufacturers { get; set; }
}

public class Manufacturer : Entity
{
    public Address Address { get; set; }

    public int AddressId { get; set; }

    public ICollection<DistributorManufacturer> DistributorManufacturers { get; set; }
}

public class DistributorManufacturer
{
    public Distributor Distributor { get; set; }

    public int DistributorId { get; set; }

    public Manufacturer Manufacturer { get; set; }

    public int ManufacturerId { get; set; }
}

配置如下:

modelBuilder.Entity<Distributor>()
    .HasOne(p => p.User)
    .WithMany()
    .HasForeignKey(p => p.UserId);

modelBuilder.Entity<Distributor>()
    .HasOne(p => p.Address)
    .WithMany()
    .HasForeignKey(p => p.AddressId);

modelBuilder.Entity<Manufacturer>()
    .HasOne(p => p.Address)
    .WithMany()
    .HasForeignKey(p => p.AddressId);

// many to many mapping
modelBuilder.Entity<DistributorManufacturer>()
    .HasKey(bc => new { bc.DistributorId, bc.ManufacturerId });

modelBuilder.Entity<DistributorManufacturer>()
    .HasOne(bc => bc.Distributor)
    .WithMany(b => b.DistributorManufacturers)
    .HasForeignKey(bc => bc.DistributorId)
    .OnDelete(DeleteBehavior.Restrict);

modelBuilder.Entity<DistributorManufacturer>()
    .HasOne(bc => bc.Manufacturer)
    .WithMany(c => c.DistributorManufacturers)
    .HasForeignKey(bc => bc.ManufacturerId)
    .OnDelete(DeleteBehavior.Restrict);

插入此值:

select * from Users
select * from Distributors
select * from Manufacturers
select * from DistributorManufacturers

enter image description here

然后,在GetManufacturers()操作中,您想要为登录的Manufacturers,AKA Distributor返回所有User。 (这是我对你的问题的假设。如果我错了,请纠正我)。所以,直到查询:

// Simulate getting the Id of the logged in User.
var userId = 1;

var query = (from m in _context.Manufacturers
             join dm in _context.DistributorManufacturers on m.Id equals dm.ManufacturerId
             join dist in _context.Distributors on dm.DistributorId equals dist.Id
             join adrs in _context.Addresses on m.AddressId equals adrs.Id
             where dist.UserId == userId
             select new
             {
                 ManufacturerId = m.Id,
                 ManufacturerName = m.Name,
                 DistributorId = dist.Id,
                 DistributorName = dist.Name,
                 Address = adrs
             }).ToList();

导致:

[
    {
        "manufacturerId": 1,
        "manufacturerName": "Manufacturer 1",
        "distributorId": 1,
        "distributorName": "Distributor 1",
        "address": {
            "street": "Street 1",
            "city": "New York",
            "state": "NY",
            "id": 1
        }
    },
    {
        "manufacturerId": 2,
        "manufacturerName": "Manufacturer 2",
        "distributorId": 1,
        "distributorName": "Distributor 1",
        "address": {
            "street": "Street 2",
            "city": "New York",
            "state": "NY",
            "id": 2
        }
    }
]

要使GetManufacturers(int id)正常工作,只需将制造商ID添加到where子句即可。由于它在DistributorManufacturer上进行内部联接,如果与登录用户没有任何关系,它将返回null。

注意:在EF Core中,当您拥有多对多关系时,您需要(至少现在......)将联合表作为实体。您可以在此处查看有关此问题的讨论:https://github.com/aspnet/EntityFrameworkCore/issues/1368

答案 1 :(得分:0)

在我看来,您希望在分销商和制造商之间配置多对多关系:每个分销商都有零个或多个制造商,每个制造商都会向零个或多个分销商提供。

如果您根据the entity framework code first many-to-many conventions,配置了这种多对多关系,您可能会遇到以下情况:

class Distributor
{
     public int Id {get; set;}
     public string Name {get; set;}

     // a distributor has exactly one Address using foreign key:
     public int AddressId {get; set;}
     public Address Address {get; set;}

     // a Distributor has zero or more Manufacturers: (many-to-many)
     public virtual ICollection<Manufacturer> Manufacturers {get; set;}

     // a Distirbutor has zero or more Users: (one-to-many)
     public virtual ICollection<User> Users {get; set;}
}

class Manufacturer
{
     public int Id {get; set;}
     public string Name {get; set;}

     // a Manufacturer has exactly one Address using foreign key:
     public int AddressId {get; set;}
     public Address Address {get; set;}

     // a Manufacturer has zero or more Distributors (many-to-many)
     public virtual ICollection<Distributor> Distributors {get; set;}
}

还有一个用户:每个用户都属于一个经销商

class User
{
     public int Id {get; set;}

     // a user belongs to exactly one Distributor, using foreign key:
     public int DistributorId {get; set;}
     public virtual Distributor Distributor {get; set;}

     ...
}

最后是DbContext

class MyDbContext : DbContext
{
    public DbSet<Distributor> Distributors {get; set;}
    public DbSet<Manufacturer> Manufacturers {get; set;}
    public DbSet<User> Users {get; set;}
    public DbSet<Address> Addresses {get; set;}
}

以上是实体框架需要了解的所有内容,以了解您希望分销商和ManuFacturers之间的多对多。实体框架将为您创建一个合适的联结表,尽管您在查询中不会需要它,我将在下面向您展示。如果您不喜欢实体框架为您创建的默认联结表,则可以使用Fluent API来定义表名和列名:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Distributor>()
            .HasMany(distributor=> distributor.Manufacturers)
            .WithMany(manufacturer => manufacturer.Distributors)
            .Map(map =>
            {
                map.MapLeftKey("DistributorId");
                map.MapRightKey("ManufacturerId");
                map.ToTable("DistributorsManufacturers");
            });

虽然内部实体框架将使用联结表,但您不会在查询中使用它,只需使用ICollections:

  

我有一个_user,我想要零或一个的几个属性   该用户的分销商,以及所有的几个属性   本经销商的制造商

尽管可以使用Include语句,但这样做很少明智。数据库查询较慢的部分之一是将所选数据传输到您的进程,因此您应该将传输的数据量限制为您实际计划使用的属性。 Include将传输所有属性,我高度怀疑你是否会使用它们,尤其是所有具有相同值的外键。

所以你的查询使用ICollection:

var _user = ... // I've got a User
var result = dbContext.Distributers
    .Where(distributor => distributor.Id == _user.DistributorId)
    .Select(distributor => new
    {
        // select only the distributor properties you plan to use
        Id = distributor.Id,
        Name = distributor.Name,
        Address = new
        {
             // again: only the properties you plan to use
             Street = distributor.Address.Street,
             City = distributor.Address.City,
             Zip = distributor.Address.Zip,
        }),

        // fetch (all or some) manufacturers of this distributor
        Manufacturers = distributor.Manufacturers
            .Where(manufacturer => manufacturer.Address.NavigationState == "CA")
            .Select(manufacturer => new
            {
                 // select only the properties you plan to use
                 // probably not the foreign key to the junction table
                 Name = manufacturer .Name,
                 Address = new {...},
                 ...
            })
            .ToList(),
    })
    .SingleOrDefault();

可能你想要一些不同的属性,但你得到了要点