2016-11-20 4 views
10

Я пытаюсь использовать CTE с Dapper и multi-mapping, чтобы получить выгружаемые результаты. Я столкнулся с неудобством с дублирующимися столбцами; 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

Есть более одного вопросов, давайте покрывать их один за другим.

КТР повторяющихся имен столбцов:

КТР не допускает повторяющиеся имена столбцов, так что вы должны решить их с помощью псевдонимов, предпочтительно использовать некоторые именования, как и в вашей попытке запроса.

По какой-то причине у меня есть в голове, что существует соглашение об именах, которое будет обрабатывать этот сценарий для меня, но я не могу найти упоминания об этом в документах.

вероятно Вы имели в устанавливающем DefaultTypeMap.MatchNamesWithUnderscores свойства true ума, но как код документация государств собственности:

Должна ли имена столбцов, как user_id разрешено соответствовать свойствам/поля, как UserId?

Очевидно, это не решение. Но проблему можно легко решить, введя условное соглашение об именах, например "{prefix}{propertyName}" (где по умолчанию префикс "{className}_") и реализуется через Dapper's 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 позволяет разделить одну строку до нескольких отдельных объектов (например, LINQ Join) в то время как вам нужно Site с Locationсписок (например, LINQ GroupJoin).

Это может быть достигнуто с помощью метода 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 открыт SqlConnection и sql является string содержащий вышеуказанный запрос.

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.

См. Мой первый комментарий к Gist для получения дополнительной информации.

Вы можете сделать отображение как

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 Framework> = 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

Смежные вопросы