Вы должны построить дерево выражения.
Во-первых, так как вам нужно ввести 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<T> 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);
}
Мне нравится ваш ответ, но мне нужно сделать это только на контактных точках, чтобы его можно было повторно использовать без необходимости делать сайт как дерево выражений, так как это означало бы необходимость выполнять много наших репо слой как выражение tress. Но ответ близок. Я не уверен, что если «posjigh» метод выражения getcontactpoint просто используется на контактных точках, как вы можете передать выражение выбора им всем в этой точке. Я бы подумал, что если бы я сделал s.contactpoints.select (exp()), он будет работать только на отдельной контактной точке, а не на весь список. – Jon
@Jon, см. Мое обновление, я создаю другое расширение, чтобы контактные точки могли быть повторно использованы (и повторно спроецированы в другой класс). –
Спасибо. это здорово работает как шарм. Я отредактировал ваш ответ, чтобы показать помощника, который мы написали, чтобы его немного обернуть, если кто-то еще столкнется с этим. Еще раз спасибо – Jon