C#Linq to Entities方法将属性投影到字符串
我试图重构遍布各处使用的一行代码。我们正在使用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)并返回字符串值,或者如果字符串不是可能的情况,则返回联系点类。
我越读它,我想我需要建立一些表达式树,不知道现在怎么样。
你需要建立一个表达式树。
首先,因为你需要引入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选择器。
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
。从OP
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我们创建了一个包装方法,使这个一点点简单的重复使用,把它在这里只是为了证明别人:
/// <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);
}
对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();
Mrchief嗨,这一切编译但我得到:INQ实体不识别方法'System.String GetContactPoint(System.Collections.Generic.IEnumerable'1 [Opus.Repository.Models.ContactPointEntity],Opus.Repository.Models.ContactPointType)'方法,和此方法无法转换为商店表达。 – Jon 2014-09-29 16:02:28
也认为有一个输入错误,你检查src是否为空。我认为你的意思是......没有正确的意思,我很想理解它。谢谢 – Jon 2014-09-29 16:03:17
更新了我的答案。如果您试图重复您的映射代码,那么这种方法将会很好。或者,如果您正在寻找一种通用的方法来消除此处的ContactPointType查询以及其他查询,那么您可能需要对此进行调整。 – Mrchief 2014-09-29 17:05:25
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();
}
嗨,詹姆斯,我的目标是控制接触点在整个域内以及跨越多个不同POCO的各个位置出现的方式,而不是如上所述明确使用它,因为需要多次重写该代码。问候 – Jon 2014-09-29 19:11:36
我确实喜欢你的答案,但我需要在接触点上这样做,以便它可以重复使用,而不必将网站作为表达树来使用,因为这意味着必须做很多我们的回购层作为表达发束。但答案很接近。我不确定是否可以“重新调整”getcontactpoint表达式方法以便在接触点上使用,因为如何在这一点上将选择表达式传递给它们。我会认为,如果我做了s.contactpoints.select(exp())它只能在单个接触点上工作,而不是在整个列表中。 – Jon 2014-09-30 08:30:20
@Jon,看到我的更新,我创建了另一个扩展,以便联系点可以重用(并重新投影到另一个类) – 2014-09-30 11:28:40
谢谢。这很棒,就像一个魅力..我已经编辑了你的答案,以显示我们写的帮手,如果其他人遇到这个问题,它会多一点。再次感谢 – Jon 2014-09-30 14:49:12