如何通过由少数产品组确定的那些产品来扩展某个产品子集?

编程语言 2026-07-08

分配给用户的类别和产品线已被缩减的产品子集,应该通过来自明确命名分组的产品来扩展。

到目前为止,下面的代码可以工作:

products = products.Where(p => dbContext.UserProductCategories
                               .Any(m => m.Login == login
                                         && m.CategoryCode == p.CategoryCode))
                   .AsQueryable();

products = products.Where(p => dbContext.UserProductLines
                               .Any(m => m.Login == login
                                         && m.ProductLineCode == p.ProductLineCode))
                   .AsQueryable();

如何从命名的分组中添加产品?

我有一个 Product 模型类,像这样:

public class Product
{
    public string Code { get; set; }
    public string CategoryCode { get; set; }
    public string ProductLineCode { get; set; }

    public string Name { get; set; }
    //... other attributes
}

...

public DbSet<Product> Products { get; set; }

基于这个 UserProductCategory 模型类,用户被限制在分配的 CategoryCode 的子集产品上:

public class UserProductCategory
{
    public string Login { get; set; }
    public string CategoryCode { get; set; }
}

...

public DbSet<UserProductCategory> UserProductCategories { get; set; }

这也是一个 UserProductLine 模型类:

public class UserProductLine
{
    public string Login { get; set; }
    public string ProductLineCode { get; set; }
}

...

public DbSet<UserProductLine> UserProductLines { get; set; }

到目前为止,我一直按照上面两个Linq查询所示的方式来限制对产品的访问(类别与产品线的交集),效果很好。

新的需求是始终显式地添加某些产品,即使它们本来可能属于不可访问的类别或产品线。

用户账户包含一个用逗号分隔的分组代码字符串。真正使用的只有极少数。大多数情况为零,有时一个,可能两个,甚至三个。

因此,代码列表可以是:

var lst = "groupcode1,groupcode2".Split(',').ToList();

使用这个 ProductGroup 的模型类:

public class ProductGroup
{
    public string GroupCode { get; set; }
    public string ProductCode { get; set; }
}

public DbSet<ProductGroup> ProductGroups { get; set; }

如何结合分组的代码来添加 products

更新2026-06-10:尝试添加其中一个导航属性。它工作,但速度慢。为自己理清了问题。产品子集的大小取决于用户的角色。有些用户可以看到所有产品,有些只能看到分配给该用户账户的类别下的产品。后来机制被改为增加分配给用户账户的产品线。一些用户仍然可以访问所有产品,一些仅能看到类别与产品线的组合(交集)。机制的最新更新与应用限制的用户相关:增加对来自少数分组的产品的可见性。

我一直在为旧应用(纯SQL、存储过程)解决同样的问题。我尝试了类似的JOIN方法。最终的SQL方案分为:1)所有产品,2)构建一个可见产品的临时表(类别与产品线的并集,以及被显式设为可见的产品)。第二种方法快得多(也更便于未来的变更)。

也有可能的解决方案,是为这个确切用户构建一个(匿名的)可见产品代码的DbSet(若需要被限制)。然后2) 可以通过像下列那样进行过滤实现:

if (restrictedAccess)
{
    products = products.Where(p => dbContext.AvailableProductCodes
                                   .Any(a => a.Code == p.Code))
                       .AsQueryable();
}

你能就此发表评论吗?

解决方案

Oliver已经基本把要点说清楚,尽管UserProductLine和 UserProductCategory看起来是将这些与用户相关联的产品分组和类别连接起来的关联表。

有了导航属性,Product应该引用ProductLine和 ProductCategory实体,这些实体可以对有权访问任一项的用户暴露一个UserProductLines和 UserProductCategories的关联集合,以实现关联:

所以以ProductLine为例:

public class Product
{
    [Key]
    public string Code { get; set; }
    public string CategoryCode { get; set; }
    public string ProductLineCode { get; set; }

    public string Name { get; set; }
    //... other attributes

    // Navigation properties
    public ProductLine ProductLine { get; set; }
    public ProductCategory ProductCategory { get; set; }
     // If product always belongs to 0 or 1 group
    public ProductGroup ProductGroup { get; set; }

     // Otherwise, if product can belong to more than 1 group
    public List<ProductGroup> ProductGroups { get; set; }
}

public class ProductLine
{
    [Key]
    public string ProductLineCode { get; set; }
    public virtual Product Product { get; set; }
    public virtual ICollection<UserProductLine> UserProductLines { get; protected set; } = [];
}

public class UserProductLine
{
    // Note: the Key would be configured to be a composite between ProductLineCode and Login
    public string ProductLineCode { get; set; }
    public string Login { get; set; }
}

public class ProductCategory
{
    [Key]
    public string ProductCategory { get; set; }
    public virtual Product Product { get; set; }
    public virtual ICollection<UserProductCategory> UserProductCategories { get; protected set; } = [];
}

public class UserProductCategory
{
    // Note: the Key would be configured to be a composite between ProductCategory and Login
    public string ProductCategory { get; set; }
    public string Login { get; set; }
}

然后按用户查询产品:

var products = _context.Products
    .Where(p => p.ProductLine.UserProductLines.Any(upl => upl.Login == login)
       && p.ProductCategory.UserProductCategories.Any(upc => upc.Login == login);

如果选定的产品必须同时属于分配给用户的类别和产品线,则使用AND &&。如果是二选一,则将AND && 替换为OR ||

如果所有UserProductCategory/UserProductLine都是包含登录名和相应外键的连接表,那么EF可以在不需要加入实体的情况下简化这一步。在这种情况下,ProductCategory和 ProductLine只需要具备User实体的集合(由EF配置通过连接表来解析),而不需要一个连接实体。你可以在文档中查阅关于没有连接表的多对多关系的示例,了解这是什么样子以及如何配置关系表。

你也可以不使用导航属性,使用你现有的扁平结构来查询数据,但这可能会增加对数据库的往返次数。你的查询实际上不起作用,因为在重新给products变量赋值时,你把按产品类别进行的首次查询给丢弃了。

在你现有的模型中,这看起来更像:

var productCategoryIds = dbContext.UserProductCategories
    .Where(upc => upc.Login == login)
    .Select(upc => upc.ProductCategory)
    .ToList();

var productLineIds = dbContext.UserProductLines
    .Where(upl => upl.Login == login)
    .Select(upl => upl.ProductLine)
    .ToList();

var products = dbContext.Products
    .Where(p => productCategoryIds.Contains(p.ProductCategory)
        && productLineIds.Contains(p.ProductLine));

同样地,如果是二者择一,请将 && 替换为 ||。你可以在单一的 var products = 查询表达式中追加其他过滤条件,如ProductGroup等,放在 .Where() 子句中(若是AND还是OR),或作为额外的 .Where() 子句,只要这些条件彼此之间用AND连接即可。这样在获取产品时,会先获取与当前用户关联的产品类别和产品线,然后用它们作为筛选条件。

使用导航属性的好处是可以避免额外的往返查询,EF可以自动推断要使用的表和列。然而,在某些情况下,这可能会产生相当复杂且可能代价高昂的查询,因为会连接多张表。使用EF Core时,你可以利用自动查询拆分功能(.AsSplitQuery())或像上面那样进行手动拆分,使代码更易读、易于理解。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章