Dapper

时间:2016-11-20 10:34:28

标签: c# .net sql-server dapper

我正在尝试使用带有Dapper和多映射的CTE来获取分页结果。我对重复的列感到不便;例如,CTE阻止我使用Name列。

我想将以下查询映射到以下对象,而不是列名和属性之间的不匹配。

查询:

WITH TempSites AS(
    SELECT
        [S].[SiteID],
        [S].[Name] AS [SiteName],
        [S].[Description],
        [L].[LocationID],
        [L].[Name] AS [LocationName],
        [L].[Description] AS [LocationDescription],
        [L].[SiteID] AS [LocationSiteID],
        [L].[ReportingID]
    FROM (
        SELECT * FROM [dbo].[Sites] [1_S]
        WHERE [1_S].[StatusID] = 0
        ORDER BY [1_S].[Name]
        OFFSET 10 * (1 - 1) ROWS
        FETCH NEXT 10 ROWS ONLY
    ) S
        LEFT JOIN [dbo].[Locations] [L] ON [S].[SiteID] = [L].[SiteID]
),
MaxItems AS (SELECT COUNT(SiteID) AS MaxItems FROM Sites)

SELECT *
FROM TempSites, MaxItems

物件:

public class Site
{
    public int SiteID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public List<Location> Locations { get; internal set; }
}

public class Location
{
    public int LocationID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public Guid ReportingID { get; set; }
    public int SiteID { get; set; }
}

出于某种原因,我知道存在一个命名约定,它将为我处理这个场景,但我在文档中找不到它。

2 个答案:

答案 0 :(得分:16)

有多个问题,请逐一介绍。

CTE重复列名:

CTE不允许重复的列名,因此您必须使用别名来解析它们,最好使用一些命名约定,例如在查询尝试中。

  

出于某种原因,我知道存在一个命名约定,它将为我处理这个场景,但我无法在文档中找到它。

您可能已将DefaultTypeMap.MatchNamesWithUnderscores属性设置为true,但作为属性的代码文档说明:

  

是否应允许像User_Id这样的列名匹配UserId等属性/字段?

显然这不是解决方案。但是,通过引入自定义命名约定可以轻松解决该问题,例如"{prefix}{propertyName}"(默认前缀为"{className}_")并通过Dapper的CustomPropertyTypeMap实现。这是一个帮助方法:

public static class CustomNameMap
{
    public static void SetFor<T>(string prefix = null)
    {
        if (prefix == null) prefix = typeof(T).Name + "_";
        var typeMap = new CustomPropertyTypeMap(typeof(T), (type, name) =>
        {
            if (name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                name = name.Substring(prefix.Length);
            return type.GetProperty(name);
        });
        SqlMapper.SetTypeMap(typeof(T), typeMap);
    }
}

现在您只需要调用它(一次):

CustomNameMap.SetFor<Location>();

将命名约定应用于您的查询:

WITH TempSites AS(
    SELECT
        [S].[SiteID],
        [S].[Name],
        [S].[Description],
        [L].[LocationID],
        [L].[Name] AS [Location_Name],
        [L].[Description] AS [Location_Description],
        [L].[SiteID] AS [Location_SiteID],
        [L].[ReportingID]
    FROM (
        SELECT * FROM [dbo].[Sites] [1_S]
        WHERE [1_S].[StatusID] = 0
        ORDER BY [1_S].[Name]
        OFFSET 10 * (1 - 1) ROWS
        FETCH NEXT 10 ROWS ONLY
    ) S
        LEFT JOIN [dbo].[Locations] [L] ON [S].[SiteID] = [L].[SiteID]
),
MaxItems AS (SELECT COUNT(SiteID) AS MaxItems FROM Sites)

SELECT *
FROM TempSites, MaxItems

你完成了那部分。当然你可以使用更短的前缀,例如&#34; Loc _&#34;如果你愿意的话。

将查询结果映射到提供的类:

在这种特殊情况下,您需要使用Query方法重载,允许您传递Func<TFirst, TSecond, TReturn> map委托并将splitOn参数单元化以指定LocationID作为拆分列。然而,这还不够。 Dapper的Multi Mapping功能允许您将单行拆分为多个单个对象(如LINQ Join),同时需要Site { {1}} 列表(如LINQ Location)。

可以使用GroupJoin方法投影到临时匿名类型,然后使用常规LINQ生成所需的输出,如下所示:

Query

其中var sites = cn.Query(sql, (Site site, Location loc) => new { site, loc }, splitOn: "LocationID") .GroupBy(e => e.site.SiteID) .Select(g => { var site = g.First().site; site.Locations = g.Select(e => e.loc).Where(loc => loc != null).ToList(); return site; }) .ToList(); 被打开cnSqlConnection是持有上述查询的sql

答案 1 :(得分:0)

以下代码可以正常工作,以便您加载具有关联位置的网站列表

var conString="your database connection string here";
using (var conn =   new SqlConnection(conString))
{
    conn.Open();
    string qry = "SELECT S.SiteId, S.Name, S.Description, L.LocationId,  L.Name,L.Description,
                  L.ReportingId
                  from Site S  INNER JOIN   
                  Location L ON S.SiteId=L.SiteId";
    var sites = conn.Query<Site, Location, Site>
                     (qry, (site, loc) => { site.Locations = loc; return site; });
    var siteCount = sites.Count();
    foreach (Site site in sites)
    {
        //do something
    }
    conn.Close(); 
}