在EF查询中重用子查询

时间:2018-02-21 23:06:54

标签: c# entity-framework linq

我正在使用一个不幸设计的数据库,我需要将8列转换为行的“子列表”,我正在使用实体框架。我想在SQL / IQueryable中完成所有操作,因此可以在之后应用过滤等。

我现在有以下代码

 public static IQueryable<DTOs.Customer> ToDTO(this IQueryable<Customer> query, DBContext context)
    {
        return from c in query
            select new DTOs.Customer
            {
                CustomerID = c.CustomerID,
                AnalysisCodes = new List<AnalysisCodeWithValue>()
                {
                    context.AnalysisCodeMapping.Where(a => a.PropertyName == nameof(c.AnalysisCode1))
                        .Select(
                            a => new AnalysisCodeWithValue() {Name = a.AnalysisCode.Name, Value = c.AnalysisCode1})
                        .FirstOrDefault(),
                    context.AnalysisCodeMapping.Where(a => a.PropertyName == nameof(c.AnalysisCode2))
                        .Select(a => new AnalysisCodeWithValue {Name = a.AnalysisCode.Name, Value = c.AnalysisCode2})
                        .FirstOrDefault(),
                    context.AnalysisCodeMapping.Where(a => a.PropertyName == nameof(c.AnalysisCode3))
                        .Select(
                            a => new AnalysisCodeWithValue() {Name = a.AnalysisCode.Name, Value = c.AnalysisCode3})
                        .FirstOrDefault(),
                    context.AnalysisCodeMapping.Where(a => a.PropertyName == nameof(c.AnalysisCode4))
                        .Select(a => new AnalysisCodeWithValue {Name = a.AnalysisCode.Name, Value = c.AnalysisCode4})
                        .FirstOrDefault(),
                    context.AnalysisCodeMapping.Where(a => a.PropertyName == nameof(c.AnalysisCode5))
                        .Select(
                            a => new AnalysisCodeWithValue() {Name = a.AnalysisCode.Name, Value = c.AnalysisCode5})
                        .FirstOrDefault(),
                    context.AnalysisCodeMapping.Where(a => a.PropertyName == nameof(c.AnalysisCode6))
                        .Select(a => new AnalysisCodeWithValue {Name = a.AnalysisCode.Name, Value = c.AnalysisCode6})
                        .FirstOrDefault(),
                    context.AnalysisCodeMapping.Where(a => a.PropertyName == nameof(c.AnalysisCode7))
                        .Select(
                            a => new AnalysisCodeWithValue() {Name = a.AnalysisCode.Name, Value = c.AnalysisCode7})
                        .FirstOrDefault(),
                    context.AnalysisCodeMapping.Where(a => a.PropertyName == nameof(c.AnalysisCode8))
                        .Select(a => new AnalysisCodeWithValue {Name = a.AnalysisCode.Name, Value = c.AnalysisCode8})
                        .FirstOrDefault()
                }.Where(a => a != null && a.Value != string.Empty)
            };
    }

这是有效的,但是你可以看到每个'分析代码'都有很多重复。我尝试使子查询成为一个获取属性名称和值的方法,但EF抱怨它无法将方法调用转换为SQL。有什么办法可以整理一下以避免复杂的重复吗?

我也知道这可能很慢,所以我对任何其他建议持开放态度。遗憾的是,我们无法在服务器上创建任何视图,功能或SP

谢谢:)

1 个答案:

答案 0 :(得分:0)

您似乎拥有一系列客户和一系列客户的字符串属性。

对于每个客户,您需要一个新对象。这个新对象包含客户的ID和AnalysisCodeWithValues列表;每个属性一个AnalysisCodeWithValue,包含属性的名称和此属性的客户值

让我们使用扩展方法。见extension methods demystified

一个过程,它接受客户以及需要从该客户提取的一系列PropertyInfos。返回值是AnalysisCodeWithValues的序列;每个PropertyInfo一个AnalysisCodeWithValue,包含属性的名称和字符串值。

public static IEnumerable<AnalysisCodeWithValue> ExtractProperties(this Customer customer, 
    IEnumerable<PropertyInfo> properties)
{
    // TODO: add Argument NULL checks

    foreach (PropertyInfo property in properties)
    {
        yield return new AnalysisCodeWithValues()
        {
             Name = property.Name,
             Value = property.GetValue(customer).ToString(),
        };
    }
}

显然,如果GetValue返回null,这将导致问题。您可以使用null条件运算符来防止出现问题:

Value = property.GetValue(customer)?.ToString() ?? String.Empty,

但是,既然你对空值不感兴趣,我会选择:

foreach (PropertyInfo property in properties)
{
    string stringValue = property.GetValue(customer)?.ToString();
    if (!String.IsNullOrEmpty(stringValue))
    {
        yield return new AnalysisCodeWithValues()
        {
             Name = property.Name,
             Value = stringValue,
        };
    }
}

用法是:

Customer customer = ...
var allCustomerProperties = typeof(Customer).GetProperties()
     .Where(property => property.CanRead);
var customerPropertyValues = customer.ExtractProperties(allCustomerProperties);

同样,如果您有一系列Customers和一系列要提取的属性:

var result = myCustomers.Select(customer => new
{
    CustomerId = customer.Id,
    AnalysisCodes = customer.ExtractProperties(allCustomerProperties)
        .ToList();
}

如果您想要客户的大部分属性,这是一个很好的解决方案。您不能进行编译器无法检测到的任何输入错误,因为您没有键入属性的名称

如果您只想要客户的一些属性,这将无效。在那种情况下,我会去寻找提取价值的代表。额外奖励:您可以请求代理中的任何值,而不仅仅是属性。您可以调用任何函数,甚至可以组合属性。

缺点:您必须在最终结果中键入所需的每个值的名称。显然,因为你想创建自己的价值观。

输入:KeyValuePairs序列,其中Key是AnalysisCodeWithValue的名称,值是从Customer中提取值的委托。像这样:

var propertyToGet = new KeyValuePair<...>(
    "AnalysisCode1",
    customer => customer.AnalsysCode1);

或更难的价值:

var propertyToGet = new KeyValuePair<...>(
    "Full name",
    customer => customer.FirstName + " " + customer.LastName);

该功能将如下:

 public static IEnumerable<AnalysisCodeWithValue> ExtractProperties(this Customer customer, 
    IEnumerable<KeyValuePair<string, Func<Customer, object>> delegates)
{
    foreach (var requestedProperty in delegates)
    {
        var propertyValue = requestedProperty.Value(customer);
        string stringValue = propertyValue?.ToString();
        if (!String.IsNullOrEmpty(stringValue))
        {
            yield return new AnalysisCodeWithValues()
            {
                 Name = requestedProperty.Key,
                 Value = stringValue,
            };
        }
    }
}

用法:

var requestedProperties = new KeyValuePair<string, Func<Customer, object>>[]
{
    new KeyValuePair<string, Func<Customer, object>>(
        "Full customer name",
        customer => customer.FirstName + " " + customer.LastName),

    new KeyValuePair<string, Func<Customer, object>>(
        "Birthday",
        customer => customer.Birthday),

    new KeyValuePair<string, Func<Customer, object>>(
    {
         "Certificates after Year 2000",
         customer => customer.CalculateCertificates(new DateTime(2000, 1, 1,))
    },
}

优点:您可以使用您喜欢的描述;你可以使用任何你喜欢的功能。如果输入错误,编译器将检测到它。

缺点:要输入更多代码。

由您来决定优势是否具有劣势