【问题标题】:How do I split on the second instance of a column如何拆分列的第二个实例
【发布时间】:2021-08-16 10:56:07
【问题描述】:

对于大多数查询,我正在尝试从 EntityFramework Core 转换为 Dapper。我有一个 sql 表(树结构)定义为:

[Table("bma_ec_categories")]
public class Category
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
    [Column("category_id")]
    public int CategoryId { get; set; }

    [Column("parent_category_id")]
    public int ParentId { get; set; }

    [Required]
    [Column("category_name")]
    [StringLength(50)]
    public string Name { get; set; }

    public ICollection<Category> Children { get; set; }

    [Column("title")] 
    [StringLength(150)] 
    public string Title { get; set; }

    [Column("description")]
    [StringLength(250)]
    public string Description { get; set; }
    
    [Column("keywords")]
    [StringLength(250)]
    public string Keywords { get; set; }

    [Column("page_content", TypeName = "text")]
    public string PageContent { get; set; }

    [Column("banner_group_id")] 
    public int? BannerGroupId { get; set; }
    
    [Column("inactive")] 
    public bool? Inactive { get; set; }
    
    [Column("issitecategory")] 
    public bool Issitecategory { get; set; }
    
    [Column("sort_order")] 
    public int? SortOrder { get; set; }
}

这是一个tree。我还有一个dto 定义为:

public class CategoryDto
{
    [Key]
    public int CategoryId { get; set; }

    public int ParentId { get; set; }

    public string Name { get; set; }

    public string Title { get; set; }

    public string Description { get; set; }

    public string Keywords { get; set; }

    public string PageContent { get; set; }

    public int? BannerGroupId { get; set; }

    public bool? Inactive { get; set; }

    public bool Issitecategory { get; set; }

    public int? SortOrder { get; set; }

    public ICollection<CategoryDto> Children { get; set; }
}

我的Item Service 有这个 Dapper 查询,大部分是我从 EF Core 复制的:

    public IEnumerable<CategoryDto> GetCategoriesWithDapperUnbuffered()
    {
        var plainResult =  _dapper.GetCategoriesUnbuffered();

        var categoryDtos = plainResult.ToList();

        var lookup = categoryDtos.Where(x => x.ParentId != 0).ToLookup(x => x.ParentId);

        foreach (var c in categoryDtos)
        {
            if (lookup.Contains(c.CategoryId))
            {
                c.Children = lookup[c.CategoryId]
                    .OrderBy(x => x.SortOrder)
                    .ThenBy(x => x.Name)
                    .ToList();
            }
        }

        var dto = categoryDtos
            .Where(x => x.ParentId == 0)
            .OrderBy(x => x.SortOrder)
            .ThenBy(x => x.Name)
            .ToList();

        return dto;
    }

...GetCategoriesUnbuffered 是:

    public IEnumerable<CategoryDto> GetCategoriesUnbuffered()
    {
        string sql = "SELECT * FROM [dbo].[bma_ec_categories]";

        using (DbConnection db = _connectionFactory.CreateConnection())
        {
            var categories = db.Query<CategoryDto>(sql, buffered: false).ToList();

            return categories;
        }
    }

我还可以展示一个 Dapper Fluent 映射。这个查询确实有效。但我试图通过在原始查询中使用 Dapper 的多映射功能来进一步优化。这是我目前所拥有的:

    public IEnumerable<CategoryDto> GetCategoriesMultiMapping()
    {
        string sql = "SELECT * FROM [dbo].[bma_ec_categories] AS [A] INNER JOIN [dbo].[bma_ec_categories] AS [B] ON [A].[category_id] = [B].[parent_category_id];";

        using (DbConnection db = _connectionFactory.CreateConnection())
        {
            var categoryDictionary = new Dictionary<int, CategoryDto>();

            var list = db.Query<CategoryDto, CategoryDto, CategoryDto>(
                    sql,
                    (category, children) =>
                    {
                        if (!categoryDictionary.TryGetValue(category.ParentId, out var categoryEntry))
                        {
                            categoryEntry = category;
                            categoryEntry.Children = new List<CategoryDto>();
                            categoryDictionary.Add(categoryEntry.ParentId, categoryEntry);
                        }

                        categoryEntry.Children.Add(children);
                        return categoryEntry;
                    },
                    splitOn: "category_id,category_id", buffered: false)
                .Distinct()
                .ToList();

            return list;
        }
    }

这将返回第一个类别,并将所有其他类别作为其子类别。我在尝试splitOn category_id 的第二个实例时遇到了麻烦。我尝试改编 Dapper 的 Example - Query Multi-Mapping (One to Many) 中的一对多示例。任何帮助将不胜感激。

【问题讨论】:

  • 我强烈建议您只指定您需要的列,并且不要在 Multi-map 查询中重复列
  • @Charlieface 你的意思是SELECT *
  • 是的,明确指定列,参见sqlblog.org/2009/10/10/…
  • @Charlieface 谢谢,已在源代码中修复。你还说do not duplicate columns,你建议我怎么做才能避免重复树中的列?

标签: c# sql-server tree dapper


【解决方案1】:

如果我真的理解您想将此表作为树来阅读,其中一种方法是使用 SQL 服务器上的递归查询,我就是这样做的。

我在同样的情况下使用了一个存储过程,你可以在 SQL server 上使用 sp 或 function,运行命令来运行你的 sp。

我为你创建一个简单的例子;

 DECLARE @CatTree TABLE(category_id int,parent_category_id int,category_name varchar(50) )

 INSERT INTO @CatTree (category_id,parent_category_id,category_name) 
 VALUES (1,null,'shoes'),(2,1,'outdoor shoes'),(3,1,'winter shoes'),(4,3,'water proof shoes'),(5,3,'snow shoes')




 ;WITH TopCat AS (
                SELECT 
                    CAST(category_id AS VARCHAR(MAX))  ID_Path,
                    parent_category_id,
                    category_id,
                    category_name
                
                FROM @CatTree where parent_category_id is null

            UNION ALL

             SELECT  
                r.ID_Path + ' > ' + CAST(t.category_id AS VARCHAR(MAX)),
                t.parent_category_id,
                t.category_id,
                t.category_name
            
            FROM @CatTree t 
            INNER JOIN TopCat r ON  t.parent_category_id = r.category_id
            ) 
            
  select * from TopCat

第二个解决方案;如果您想在后端创建树,自引用表 是您的关键。你必须为你的模型添加一个关系,指向自己,然后流利的 api 或 migraiton 都是有用的

public class Category: BaseEntity
{
    public Guid Id{ get; set; }

    public string Name { get; set; }

    public Guid? TopCategoryId{ get; set; }

    public virtual ICollection<Category> TopCat{ get; set; }
}

这可能是错误的我没有尝试,我只是写在我的脑海里。那么您必须将您的关系添加到您的数据注释或 fluentapi

builder.HasMany(pt => pt.Category).WithOne(p => p.TopCat)

Entity Framework Core self referencing table

【讨论】:

  • 感谢您的评论。请注意,我有一个有效的 EF Core 解决方案。我试图让 Dapper 以同样的方式工作。
  • 第二种解决方案没用,是吗?我在我的博客页面上使用了同样的东西,有 cmets 和 parent cmets。如果您回答某人的评论,它将成为您评论的父母。所以我自己引用了表,带有 collection,然后我用一个评论定义了有子 cmets,每个评论都有一个父评论
【解决方案2】:

经过大量搜索,我得出结论,这不是 Multi-Mapper 的本意。但是,我确实在 SO 上找到了这个 answer,它解决了我的实际问题。

如果有人想知道这里是我的基准测试结果:

Method Mean Error StdDev StdErr Min Max Q1 Q3 Median Op/s Ratio RatioSD Rank Gen 0 Gen 1 Allocated
'EF Core (async)' 96.87 ms 3.920 ms 11.309 ms 1.154 ms 77.79 ms 126.71 ms 87.62 ms 105.04 ms 95.68 ms 10.32 1.00 0.00 4 - - 979 KB
'EF Core (compiled)' 83.69 ms 1.674 ms 4.910 ms 0.494 ms 73.57 ms 96.66 ms 79.87 ms 86.79 ms 83.52 ms 11.95 0.88 0.11 3 142.8571 - 1,297 KB
'Dapper (async, buffered)' 77.34 ms 1.646 ms 4.828 ms 0.485 ms 65.97 ms 88.00 ms 74.04 ms 80.87 ms 76.98 ms 12.93 0.81 0.12 2 166.6667 - 686 KB
'Dapper (unbuffered)' 66.64 ms 1.322 ms 3.551 ms 0.387 ms 57.91 ms 74.21 ms 64.25 ms 68.98 ms 66.69 ms 15.01 0.70 0.09 1 125.0000 - 593 KB
'Dapper Test (unbuffered)' 66.16 ms 1.322 ms 2.902 ms 0.381 ms 60.57 ms 72.30 ms 63.87 ms 67.97 ms 65.93 ms 15.11 0.67 0.07 1 285.7143 142.8571 1,746 KB
  • Dapper Test 基于我链接到的 SO 答案

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-26
    • 2016-04-29
    • 2014-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多