2014-09-29 1 views
1

Я пытаюсь реорганизовать строку кода, которая используется повсюду. Мы используем EF6.1 и хотим найти телефон и электронную почту (как строки).C# Linq to Entities метод для свойства проекта для строки

public SiteLabelDto[] GetVendorSites(int vendorId) 
    { 
     return Context.Sites.Where(s => !s.IsDeleted && s.Vendor.Id == vendorId) 
      .Select(s => new SiteLabelDto 
      { 
       Id = s.Id, 
       Name = s.Name,      
       Address = s.Address.StreetAddress + ", " + s.Address.Locality + ", " + s.Address.Region + ", " + s.Address.Postcode, 
       Country = s.Address.Country.Name, 
       Email = s.ContactPoints.FirstOrDefault(x => x.Type == ContactPointType.Email) != null ? s.ContactPoints.FirstOrDefault(x => x.Type == ContactPointType.Email).Value : "", 
       Phone = s.ContactPoints.FirstOrDefault(x => x.Type == ContactPointType.Phone) != null ? s.ContactPoints.FirstOrDefault(x => x.Type == ContactPointType.Phone).Value : "",      
      }).ToArray(); 
    } 

Приведенный выше код переводит список контактных точек и пытается найти наилучшее соответствие для каждого типа.

public class ContactPointEntity 
{ 
    public int Id { get; set; } 
    public string Value { get; set; } 
    public ContactPointType Type { get; set; } 
    public bool IsDefault { get; set; } 
} 

Метод будет расширен, чтобы попытаться вернуть IsDefault первым в первую очередь.

Моя цель - попытаться включить это в метод или расширение, чтобы я мог сказать s.GetcontactPoint (ContactPointType.Email) или s.contactPoints.GetPoints (ContactPointType.Email) и вернуть значение строки, или вернуть класс контактной точки, если строка не является возможной ситуацией.

Чем больше я читал об этом, я думаю, мне нужно будет построить некоторое дерево выражений, но пока не знаю, как это сделать.

ответ

2

Вы должны построить дерево выражения.

Во-первых, так как вам нужно ввести IsDefault состояние, выражение может выглядеть следующим образом:

s.ContactPoints 
.Where(x => x.Type == ContactPointType.Email && x.IsDefault) 
.Select(x => x.Value) 
.DefaultIfEmpty(string.Empty) 
.FirstOrDefault() 

Затем перевести селектор контактную точку в выражение.

private static Expression<Func<Site, string>> GetContactPoint(ParameterExpression siteParam, ParameterExpression cpeParam, ContactPointType type) 
{ 
    // Where. 
    var typeCondition = Expression.Equal(Expression.Property(cpeParam, "Type"), Expression.Constant(type)); 
    var defaultCondition = Expression.Equal(Expression.Property(cpeParam, "IsDefault"), Expression.Constant(true)); 
    var condition = Expression.AndAlso(typeCondition, defaultCondition); 
    var predicateExp = Expression.Lambda<Func<ContactPointEntity, bool>>(condition, cpeParam); 
    var whereExp = Expression.Call(typeof(Enumerable), "Where", new[] { typeof(ContactPointEntity) }, Expression.Property(siteParam, "ContactPoints"), predicateExp); 

    // Select. 
    var valueExp = Expression.Lambda<Func<ContactPointEntity, string>>(Expression.Property(cpeParam, "Value"), cpeParam); 
    var selectExp = Expression.Call(typeof(Enumerable), "Select", new[] { typeof(ContactPointEntity), typeof(string) }, whereExp, valueExp); 

    // DefaultIfEmpty. 
    var defaultIfEmptyExp = Expression.Call(typeof(Enumerable), "DefaultIfEmpty", new[] { typeof(string) }, selectExp, Expression.Constant(string.Empty)); 

    // FirstOrDefault. 
    var firstOrDefaultExp = Expression.Call(typeof(Enumerable), "FirstOrDefault", new[] { typeof(string) }, defaultIfEmptyExp); 

    var selector = Expression.Lambda<Func<Site, string>>(firstOrDefaultExp, siteParam); 
    return selector; 
} 

А также создать ярлык сайта dto selector.

private static Expression<Func<Site, SiteLabelDto>> GetSite(ParameterExpression siteParam, ParameterExpression cpeParam) 
{ 
    var newExp = Expression.New(typeof(SiteLabelDto)); 
    var initExp = Expression.MemberInit(
     newExp, 
     Expression.Bind(typeof(SiteLabelDto).GetProperty("Id"), Expression.Lambda<Func<Site, int>>(Expression.Property(siteParam, "Id"), siteParam).Body), 
     Expression.Bind(typeof(SiteLabelDto).GetProperty("Name"), Expression.Lambda<Func<Site, string>>(Expression.Property(siteParam, "Name"), siteParam).Body), 
     /* other basic information */ 
     Expression.Bind(typeof(SiteLabelDto).GetProperty("Email"), GetContactPoint(siteParam, cpeParam, ContactPointType.Email).Body), 
     Expression.Bind(typeof(SiteLabelDto).GetProperty("Phone"), GetContactPoint(siteParam, cpeParam, ContactPointType.Phone).Body) 
     /* other types */ 
    ); 
    var selector = Expression.Lambda<Func<Site, SiteLabelDto>>(initExp, siteParam); 
    return selector; 
} 

Использование.

var siteParam = Expression.Parameter(typeof(Site), "s"); 
var cpeParam = Expression.Parameter(typeof(ContactPointEntity), "cpe"); 
var selector = GetSite(siteParam, cpeParam); 
return Context.Sites 
    .Where(s => !s.IsDeleted && s.Vendor.Id == vendorId) 
    .Select(selector) 
    .ToArray(); 

PS:

Возможно некоторый код выше, необходимо реструктурировать, это просто дает основную идею, как это сделать.

обновление

Вы можете также создать класс-оболочку, чтобы содержать экземпляр EF вместе со всеми контактными точками.

public class ContactPointExt<T> 
{ 
    public T Instance { get; set; } 
    public string Email { get; set; } 
    public string Phone { get; set; } 
} 

Затем измените GetSite в GetContactPoints вернуть результат как ContactPointExt<T>.

private static Expression<Func<Site, ContactPointExt<T>>> GetContactPoints<T>(ParameterExpression siteParam, ParameterExpression cpeParam) 
{ 
    var type = typeof(ContactPointExt<T>); 
    var newExp = Expression.New(type); 
    var initExp = Expression.MemberInit(
     newExp, 
     Expression.Bind(type.GetProperty("Instance"), siteParam), 
     Expression.Bind(type.GetProperty("Email"), GetContactPoint(siteParam, cpeParam, ContactPointType.Email).Body), 
     Expression.Bind(type.GetProperty("Phone"), GetContactPoint(siteParam, cpeParam, ContactPointType.Phone).Body) 
    ); 
    var selector = Expression.Lambda<Func<Site, ContactPointExt<T>>>(initExp, siteParam); 
    return selector; 
} 

Результат ContactPointExt<T> может быть повторно проецируется в SiteLabelDto с другим Select.

var siteParam = Expression.Parameter(typeof(Site), "s"); 
var cpeParam = Expression.Parameter(typeof(ContactPointEntity), "cpe"); 
var selector = GetContactPoints<Site>(siteParam, cpeParam); 
return Context.Sites 
    .Where(s => !s.IsDeleted && s.Vendor.Id == vendorId) 
    .Select(selector) 
    .Select(s => new SiteLabelDto 
    { 
     Id = s.Instance.Id, 
     Name = s.Instance.Name, 
     Email = s.Email, 
     Phone = s.Phone 
    }) 
    .ToArray(); 

EDIT от OP

мы создали метод обертку, чтобы сделать это немного проще для повторного использования, поставив его здесь, чтобы показать другим:

/// <summary> 
    /// Wraps up a each of a query's objects in a ContactPointExt&lt;T&gt; object, providing the default contact point of each type. 
    /// The original query object is accessed via the "Instance" property on each result. 
    /// Assumes that the query object type has a property called ContactPoints - if different, supply the property name as the first argument. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="query"></param> 
    /// <param name="contactPointsPropertyName"></param> 
    /// <returns></returns> 
    public static IQueryable<ContactPointExt<T>> WithContactPointProcessing<T>(this IQueryable<T> query, string contactPointsPropertyName = "ContactPoints") where T : class 
    { 
     var siteParam = Expression.Parameter(typeof(T), "s"); 
     var cpeParam = Expression.Parameter(typeof(ContactPointEntity), "cpe"); 
     var selector = ContactPointHelpers.GetContactPoints<T>(siteParam, cpeParam, contactPointsPropertyName); 
     return query.Select(selector); 
    } 
+0

Мне нравится ваш ответ, но мне нужно сделать это только на контактных точках, чтобы его можно было повторно использовать без необходимости делать сайт как дерево выражений, так как это означало бы необходимость выполнять много наших репо слой как выражение tress. Но ответ близок. Я не уверен, что если «posjigh» метод выражения getcontactpoint просто используется на контактных точках, как вы можете передать выражение выбора им всем в этой точке. Я бы подумал, что если бы я сделал s.contactpoints.select (exp()), он будет работать только на отдельной контактной точке, а не на весь список. – Jon

+0

@Jon, см. Мое обновление, я создаю другое расширение, чтобы контактные точки могли быть повторно использованы (и повторно спроецированы в другой класс). –

+1

Спасибо. это здорово работает как шарм. Я отредактировал ваш ответ, чтобы показать помощника, который мы написали, чтобы его немного обернуть, если кто-то еще столкнется с этим. Еще раз спасибо – Jon

0

Использование метода расширения с Linq-to-entites является сложным, поскольку не все провайдеры могут понимать и переводить на соответствующий бэкэнд-вызов. Относительно безопасная ставка должна принять IQueryable и вернуть IQueryable, который может решить:

public static IQueryable<SiteDTO> MapToSiteDTO(this IQueryable<Site> query) 
{ 
    return query.Select(s => new SiteLabelDto 
          { 
           Id = s.Id, 
           Name = s.Name,      
           Address = s.Address.StreetAddress + ", " + s.Address.Locality + ", " + s.Address.Region + ", " + s.Address.Postcode, 
           Country = s.Address.Country.Name, 
           Email = s.ContactPoints.FirstOrDefault(x => x.Type == ContactPointType.Email) != null ? s.ContactPoints.FirstOrDefault(x => x.Type == ContactPointType.Email).Value : "", 
           Phone = s.ContactPoints.FirstOrDefault(x => x.Type == ContactPointType.Phone) != null ? s.ContactPoints.FirstOrDefault(x => x.Type == ContactPointType.Phone).Value : "",      
          }); 
} 

И тогда вы называете это нравится:

return Context.Sites.Where(s => !s.IsDeleted && s.Vendor.Id == vendorId) 
      .Select(x => x) 
      .MapToSiteDTO() 
      .ToArray(); 
+0

Привет Mrchief, это все компиляции, но я получаю: INQ to Entities не распознает метод «System.String GetContactPoint (System.Collections.Generic.IEnumerable1) [Opus.Repository.Models.ContactPointEntity], Opus.Repository.Models.ContactPointType) и этот метод нельзя перевести в магазин выражение. – Jon

+0

также думаю, что есть опечатка до, вы проверяете src равно null. я думаю, вы имели в виду точку .. не исправил просто incase, я пропустил понимание этого. Спасибо – Jon

+0

Обновлен мой ответ. Если вы пытаетесь удалить дубликат кода отображения, то этот подход будет в порядке. Или если вы ищете общий способ устранения поиска ContactPointType здесь, а также другие запросы, вам может потребоваться настроить его. – Mrchief

0
public SiteLabelDto[] GetVendorSites(int vendorId) 
{ 
    return (from s in Context.Sites 
     where !s.IsDeleted && s.Vendor.Id == vendorId 
     let email = s.ContactPoints.FirstOrDefault(x => x.Type == ContactPointType.Email) 
     let phone = s.ContactPoints.FirstOrDefault(x => x.Type == ContactPointType.Phone) 
     select new SiteLabelDto 
     { 
      Id = s.Id, 
      Name = s.Name,      
      Address = s.Address.StreetAddress + ", " + s.Address.Locality + ", " + s.Address.Region + ", " + s.Address.Postcode, 
      Country = s.Address.Country.Name, 
      Email = email != null ? email.Value : "", 
      Phone = phone != null ? phone .Value : "", 
     }).ToArray(); 
} 
+0

Привет, Джеймс. Я стараюсь контролировать то, как контактные точки выходят в разных местах по всему домену и через множество разных POCO, вместо того, чтобы явно использовать его, как описано выше, поскольку потребуется многократно переписать этот код. С уважением – Jon