为什么选择N+;1没有外键和LINQ?

为什么选择N+;1没有外键和LINQ?,linq,foreign-keys,select-n-plus-1,Linq,Foreign Keys,Select N Plus 1,不幸的是,我有一个数据库没有真正的外键(我计划稍后添加它,但不希望现在就这样做以使迁移更容易)。我已经手动编写了映射到数据库的域对象来建立关系(在本教程之后),并且最终使代码正常运行。但是,我注意到我现在有选择N+1的问题。不是选择所有产品,而是使用以下SQL逐个选择: SELECT [t0].[id] AS [ProductID], [t0].[Name], [t0].[info] AS [Description] FROM [products] AS [t0] WHERE [t0].[i

不幸的是,我有一个数据库没有真正的外键(我计划稍后添加它,但不希望现在就这样做以使迁移更容易)。我已经手动编写了映射到数据库的域对象来建立关系(在本教程之后),并且最终使代码正常运行。但是,我注意到我现在有选择N+1的问题。不是选择所有产品,而是使用以下SQL逐个选择:

SELECT [t0].[id] AS [ProductID], [t0].[Name], [t0].[info] AS [Description] 
FROM [products] AS [t0] 
WHERE [t0].[id] = @p0 
-- @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [65] 
控制器:

    public ViewResult List(string category, int page = 1)
    {
        var cat = categoriesRepository.Categories.SelectMany(c => c.LocalizedCategories).Where(lc => lc.CountryID == 1).First(lc => lc.Name == category).Category;
        var productsToShow = cat.Products;
        var viewModel = new ProductsListViewModel
        {
            Products = productsToShow.Skip((page - 1) * PageSize).Take(PageSize).ToList(),
            PagingInfo = new PagingInfo
            {
                CurrentPage = page,
                ItemsPerPage = PageSize,
                TotalItems = productsToShow.Count()
            },
            CurrentCategory = cat
        };
        return View("List", viewModel);
    }
因为我不确定我的LINQ表达式是否正确,所以我尝试使用它,但仍然得到N+1:

var cat = categoriesRepository.Categories.First();
域对象:

[Table(Name = "products")]
public class Product
{
    [Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
    public int ProductID { get; set; }

    [Column]
    public string Name { get; set; }

    [Column(Name = "info")]
    public string Description { get; set; }

    private EntitySet<ProductCategory> _productCategories = new EntitySet<ProductCategory>();
    [System.Data.Linq.Mapping.Association(Storage = "_productCategories", OtherKey = "productId", ThisKey = "ProductID")]
    private ICollection<ProductCategory> ProductCategories
    {
        get { return _productCategories; }
        set { _productCategories.Assign(value); }
    }

    public ICollection<Category> Categories
    {
        get { return (from pc in ProductCategories select pc.Category).ToList(); }
    }
}

[Table(Name = "products_menu")]
class ProductCategory
{
    [Column(IsPrimaryKey = true, Name = "products_id")]
    private int productId;
    private EntityRef<Product> _product = new EntityRef<Product>();
    [System.Data.Linq.Mapping.Association(Storage = "_product", ThisKey = "productId")]
    public Product Product
    {
        get { return _product.Entity; }
        set { _product.Entity = value; }
    }

    [Column(IsPrimaryKey = true, Name = "products_types_id")]
    private int categoryId;
    private EntityRef<Category> _category = new EntityRef<Category>();
    [System.Data.Linq.Mapping.Association(Storage = "_category", ThisKey = "categoryId")]
    public Category Category
    {
        get { return _category.Entity; }
        set { _category.Entity = value; }
    }
}

[Table(Name = "products_types")]
public class Category
{
    [Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
    public int CategoryID { get; set; }

    private EntitySet<ProductCategory> _productCategories = new EntitySet<ProductCategory>();
    [System.Data.Linq.Mapping.Association(Storage = "_productCategories", OtherKey = "categoryId", ThisKey = "CategoryID")]
    private ICollection<ProductCategory> ProductCategories
    {
        get { return _productCategories; }
        set { _productCategories.Assign(value); }
    }

    public ICollection<Product> Products
    {
        get { return (from pc in ProductCategories select pc.Product).ToList(); }
    }

    private EntitySet<LocalizedCategory> _LocalizedCategories = new EntitySet<LocalizedCategory>();
    [System.Data.Linq.Mapping.Association(Storage = "_LocalizedCategories", OtherKey = "CategoryID")]
    public ICollection<LocalizedCategory> LocalizedCategories
    {
        get { return _LocalizedCategories; }
        set { _LocalizedCategories.Assign(value); }
    }
}

[Table(Name = "products_types_localized")]
public class LocalizedCategory
{
    [Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
    public int LocalizedCategoryID { get; set; }

    [Column(Name = "products_types_id")]
    private int CategoryID;
    private EntityRef<Category> _Category = new EntityRef<Category>();
    [System.Data.Linq.Mapping.Association(Storage = "_Category", ThisKey = "CategoryID")]
    public Category Category
    {
        get { return _Category.Entity; }
        set { _Category.Entity = value; }
    }

    [Column(Name = "country_id")]
    public int CountryID { get; set; }

    [Column]
    public string Name { get; set; }
}
ProductSummary.cshtml:

@model MaxFPS.Domain.Entities.Product

<div class="item">
    <h3>@Model.Name</h3>
    @Model.Description
    @if (Model.ProductSubs.Count == 1)
    {
        using(Html.BeginForm("AddToCart", "Cart")) {
            @Html.HiddenFor(x => x.ProductSubs.First().ProductSubID);
            @Html.Hidden("returnUrl", Request.Url.PathAndQuery);
            <input type="submit" value="+ Add to cart" />
        }
    }
    else
    {
        <p>TODO: länk eller dropdown för produkter med varianter</p>
    }
    <h4>@Model.LowestPrice.ToString("c")</h4>
</div>
对我来说,查询顺序的不同表明DataLoadOptions实际上在做一些事情,但不是我所期望的。我希望它能产生如下结果:

SELECT [t0].[products_id] AS [ProductID], [t0].[id] AS [ProductSubID], [t0].[Name], [t0].[Price]
FROM [products_sub] AS [t0] 
WHERE [t0].[products_id] = @x1 OR [t0].[products_id] = @x2 OR [t0].[products_id] = @x3 ... and so on
它是
第一个()
。它触发在它前面的部分的执行,在单独的查询中通过延迟加载获取它后面的部分。狡猾的,难以发现的

这是您可以采取的措施,以防止它并一次性获取所有内容:

LocalizedCategories.Where(lc => lc.CountryID == 1 && lc.Name == category)
    .Take(1)
    .SelectMany(lc => lc.Category.ProductCategories)
    .Select (pc => pc.Product)

您应该将成员
ProductCategories
公开。我认为最好删除派生属性
Category.Products
Product.Categories
,因为我认为它们将在其所有者被物化或寻址时触发查询。

“我计划稍后添加[外键],但不希望现在就添加,以简化迁移。”听起来你在用迁移问题来换取编程问题。咬紧牙关,现在就添加外键。我可能不得不这么做,但我不知道该怎么做,因为数据库目前正在使用,所以如果我必须更改影响实时网站的内容,这可能会很棘手。然而,我仍然需要关于这个问题的反馈。添加外键能解决我的问题吗?据我所知,无论FK是否存在,LINQ To SQL的行为都没有任何不同(要做到这一点,它必须查询一组系统表来了解它们),所以这不会有什么不同。谢谢。另一件事是,它加载类别中的所有产品,而不仅仅是显示的4个。这似乎也错了。谢谢你的回复。我认为你的思路是对的。但是,对于您的代码,我得到了3个错误:1
Category不包含ProductCategories的定义,并且找不到接受Category类型的第一个参数的扩展方法ProductCategories(缺少using指令或程序集引用?)
2
Category.ProductCategories由于其保护级别而无法访问
3
属性或索引器类别。ProductCategories无法在此上下文中使用,因为get访问器无法访问
我尝试稍微更改代码,但仍然无法使其正常工作。当然。它想在
cat.Products
后面复制代码,但走错了路线。修改过了,谢谢。这段代码有效,但遗憾的是,我仍然遇到同样的问题。如果我的域对象没有问题(我不确定),我想可能是由于延迟加载造成的。在一个查询中获取所有内容的唯一方法是使用“真实”导航属性。在linq查询中,像
Category.Products
这样的成员会出现类似“成员的Product.Categories不支持转换为SQL”这样的异常。因此,为
LocalizedCategories
创建存储库似乎是一种可行的方法。因为这是linq to SQL,您应该使用。
SELECT [t0].[products_id] AS [ProductID], [t0].[id] AS [ProductSubID], [t0].[Name], [t0].[Price]
FROM [products_sub] AS [t0] 
WHERE [t0].[products_id] = @x1
SELECT [t0].[products_id] AS [ProductID], [t0].[id] AS [ProductSubID], [t0].[Name], [t0].[Price]
FROM [products_sub] AS [t0] 
WHERE [t0].[products_id] = @x1 OR [t0].[products_id] = @x2 OR [t0].[products_id] = @x3 ... and so on
LocalizedCategories.Where(lc => lc.CountryID == 1 && lc.Name == category)
    .Take(1)
    .SelectMany(lc => lc.Category.ProductCategories)
    .Select (pc => pc.Product)