2016-11-20 228 views
10

我正在尝试使用CTE与Dapper和多重映射来获取分页结果。我用重复的栏目造成不便。例如,CTE阻止我必须命名列。Dapper中的自定义映射

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

查询:

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

出于某种原因,我在我的头上,一个命名规则存在,这将处理这种情况对我,但我不能找到提它在文档中。

+0

我试图关闭这个问题,但它有一个开放的赏金。这是重复的。您需要使用ITypeMap接口和属性。阅读这里:http://stackoverflow.com/questions/8902674/manually-map-column-names-with-class-properties –

回答

11

有超过一个的问题,让他们盖一个接一个。

CTE重复列名:

CTE不允许重复的列名,所以你必须使用别名来解决这些问题,最好使用一些命名约定就像你查询的尝试。

出于某种原因,我有一个命名约定存在,它将处理这种情况,但我无法在文档中找到它。

你可能想到的DefaultTypeMap.MatchNamesWithUnderscores属性设置为true,但作为财产状态代码文档:

像USER_ID是否应列名被允许匹配诸如用户ID属性/字段?

显然这不是解决方案。但是,通过引入自定义命名约定,例如"{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 

,你完成了这部分。当然,如果你喜欢,你可以使用更短的前缀,如“Loc_”。

映射查询结果中所提供的类:

在这个特定的情况下,需要使用Query方法重载允许你传递Func<TFirst, TSecond, TReturn> map委托和unitilize的splitOn参数指定LocationID为分割塔。但这还不够。精致小巧的Multi Mapping功能,您可以单行,而你需要一个SiteLocation列表(如LINQ GroupJoin)被划分为几个对象(如LINQ Join)。

它可以通过使用Query方法投射到一个临时匿名类型,然后使用常规的LINQ,以产生这样的所需的输出来实现:

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(); 

其中cn打开SqlConnectionsqlstring持有上述查询。

1

下面的代码应该工作正常,为您加载网站的列表与相关位置

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(); 
} 
+0

感谢您的回答,但您对查询所做的更改已完全忽略分页的需要并且还要求返回可用项目网站的最大数量。 –

5

您可以使用ColumnAttributeTypeMapper将列名与另一个属性进行映射。

查看我的第一条评论,了解更多细节。

你可以这样做

public class Site 
{ 
    public int SiteID { get; set; } 
    [Column("SiteName")] 
    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; } 
    [Column("LocationName")] 
    public string Name { get; set; } 
    [Column("LocationDescription")] 
    public string Description { get; set; } 
    public Guid ReportingID { get; set; } 
    [Column("LocationSiteID")] 
    public int SiteID { get; set; } 
} 

映射映射可以使用任一下列3种方法

方法1

手动设置你的模型定制TypeMapper一次作为完成:

Dapper.SqlMapper.SetTypeMap(typeof(Site), new ColumnAttributeTypeMapper<Site>()); 
Dapper.SqlMapper.SetTypeMap(typeof(Location), new ColumnAttributeTypeMapper<Location>()); 

方法2

对于.NET框架> = V4.0的类库,你可以使用PreApplicationStartMethod注册您的类自定义类型映射。

using System.Web; 
using Dapper; 

[assembly: PreApplicationStartMethod(typeof(YourNamespace.Initiator), "RegisterModels")] 

namespace YourNamespace 
{ 
    public class Initiator 
    { 
     private static void RegisterModels() 
     { 
      SqlMapper.SetTypeMap(typeof(Site), new ColumnAttributeTypeMapper<Site>()); 
      SqlMapper.SetTypeMap(typeof(Location), new ColumnAttributeTypeMapper<Location>()); 
      // ... 
     } 
    } 
} 

方法3

或者你可以找到类到ColumnAttribute通过反射应用和设置类型映射。这可能会稍微慢一点,但它会自动为您完成装配中的所有映射。一旦装配完成,请致电RegisterTypeMaps()

public static void RegisterTypeMaps() 
    { 
     var mappedTypes = Assembly.GetAssembly(typeof (Initiator)).GetTypes().Where(
      f => 
      f.GetProperties().Any(
       p => 
       p.GetCustomAttributes(false).Any(
        a => a.GetType().Name == ColumnAttributeTypeMapper<dynamic>.ColumnAttributeName))); 

     var mapper = typeof(ColumnAttributeTypeMapper<>); 
     foreach (var mappedType in mappedTypes) 
     { 
      var genericType = mapper.MakeGenericType(new[] { mappedType }); 
      SqlMapper.SetTypeMap(mappedType, Activator.CreateInstance(genericType) as SqlMapper.ITypeMap); 
     } 
    } 
+0

我想方法1,但我得到_error CS0535:'FallbackTypeMapper'没有实现接口成员'SqlMapper.ITypeMap.FindExplicitConstructor()'_。有什么建议么? – Joe