C# Linq to Entities 方法,用于将属性投影到字符串

本文关键字:属性 投影 字符串 用于 Linq to Entities 方法 | 更新日期: 2023-09-27 18:25:01

我正在尝试重构一行到处使用的代码。 我们正在使用 EF6.1,并希望查找电话和电子邮件(字符串形式(。

    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( 并返回字符串值,或者如果字符串不是可能的情况,则返回联系点类。

我读得越多,我想我需要构建一些表达式树,但不确定如何构建。

C# Linq to Entities 方法,用于将属性投影到字符串

您需要构建一个表达式树。

首先,由于您需要引入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();

附注:

可能上面的一些代码需要重构,这只是给出了如何做到这一点的基本想法。

更新

还可以创建一个包装类来包含 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>的结果可以用另一个Select重新投影到SiteLabelDto中。

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

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

将扩展方法与 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();
相关文章: