2

残念ながら、実際の外部キーがないデータベースがあります(これは後で追加する予定ですが、移行を容易にするために今は追加しないことをお勧めします)。関係を設定するためにデータベースにマップするドメインオブジェクトを手動で作成しました(このチュートリアルhttp://www.codeproject.com/Articles/43025/A-LINQ-Tutorial-Mapping-Tables-to-Objectsに従って)。ついにコードが正しく実行されるようになりました。しかし、SELECT N+1の問題があることに気づきました。すべての製品を選択する代わりに、次のSQLで1つずつ選択されます。

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; }
}

私は自分のビューからすべてをコメントアウトしようとしたので、これに影響を与えるものは何もないようです。ViewModelは見た目と同じくらい単純なので、そこに何も存在しないはずです。

これを読んだとき(http://www.hookedonlinq.com/LinqToSQL5MinuteOVerview.ashx)、データベースに実際の外部キーがなく、コードで手動結合を使用する必要があるのではないかと考え始めました。あれは正しいですか?どうすればいいですか?ドメインモデルからマッピングコードを削除する必要がありますか、それとも追加/変更する必要がありますか?

注:この質問をよりクリーンにするために、関連性がないと思われるコードの一部を削除しました。足りないものがあれば教えてください。

編集:Gert Arnoldは、1つずつ照会されることProductsからすべての問題を解決しました。CategoryただしProducts、ページに表示されているすべてのものが1つずつ照会されるという問題がまだ発生しています。

これは私のビューコードから起こります:

List.cshtml:

@model MaxFPS.WebUI.Models.ProductsListViewModel

@foreach(var product in Model.Products) {
    Html.RenderPartial("ProductSummary", product);
}

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>

もう一度.First()を使ったものですか?.Take(1)を試しましたが、とにかくIDを選択できませんでした...

編集:DataContextにアクセスするためのコードとDataLoadOptionsを作成するためのこのコードをリポジトリに追加しようとしました。ただし、ProductSubごとにクエリが生成されます。

var dlo = new System.Data.Linq.DataLoadOptions();
dlo.LoadWith<Product>(p => p.ProductSubs);
localizedCategoriesRepository.DataContext.LoadOptions = dlo;
var productsInCategory = localizedCategoriesRepository.LocalizedCategories.Where(lc => lc.CountryID == 1 && lc.Name == category)
    .Take(1)
    .SelectMany(lc => lc.Category.ProductCategories)
    .Select(pc => pc.Product);

ただし、生成されるSQLはわずかに異なり、クエリの順序も異なります。

ProductSubを選択するクエリの場合、DataLoadOptionsコードはという名前の変数を生成@x1し、それらがない場合、変数はという名前になり@p0ます。

SELECT [t0].[products_id] AS [ProductID], [t0].[id] AS [ProductSubID], [t0].[Name], [t0].[Price]
FROM [products_sub] AS [t0] 
WHERE [t0].[products_id] = @x1

私へのクエリの順序の違いは、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
4

1 に答える 1

1

ですFirst()。前の部分とそれに続く部分の実行がトリガーされ、個別のクエリで遅延読み込みによってフェッチされます。トリッキーで、見つけるのは難しい。

これは、それを防ぎ、すべてを1回で取得するためにできることです。

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思います。なぜなら、所有者が具体化またはアドレス指定されるたびにクエリがトリガーされると思うからです。

于 2012-09-27T11:19:22.417 に答える