简体   繁体   English

在 EF 查询中重用子查询

[英]Reusing subquery in EF query

I'm working with an unfortunately designed database where I need to essentially convert 8 columns to a 'sublist' for the row, and I'm using Entity Framework.我正在使用一个不幸设计的数据库,我需要将 8 列基本上转换为该行的“子列表”,并且我正在使用实体框架。 I want to do it all in SQL/IQueryable so filtering etc can be applied afterwards.我想在 SQL/IQueryable 中完成所有操作,以便之后可以应用过滤等。

I have the below code at the moment我现在有以下代码

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

This works but as you can see thee's a lot of repetition for each 'analysis code'.这是有效的,但正如您所看到的,每个“分析代码”都有很多重复。 I tried making the subquery a method that takes the property name and value, but EF complains that it can't convert the method call to SQL.我尝试使子查询成为采用属性名称和值的方法,但 EF 抱怨它无法将方法调用转换为 SQL。 Is there any way I can tidy this up to avoid the complicated repetition?有什么办法可以整理一下以避免复杂的重复吗?

I'm also aware this may be slow, so I'm open to any other suggestions.我也知道这可能会很慢,所以我愿意接受任何其他建议。 Unfortunately we are unable to create any views, functions or SP's on the server很遗憾,我们无法在服务器上创建任何视图、函数或 SP

Thanks :)谢谢 :)

It seems you have a sequence of Customers and a sequence of string Properties of the customer.看来您有一个客户序列和客户的字符串属性序列。

For every customer you want one new object.对于每个客户,您都需要一个新对象。 This new object contains the ID of the customer and a list of AnalysisCodeWithValues;这个新对象包含客户的 ID 和 AnalysisCodeWithValues 列表; one AnalysisCodeWithValue per property, containing the name of the property and the customer's value of this property每个属性一个 AnalysisCodeWithValue,包含属性名称和该属性的客户价值

Let's do this using extension methods.让我们使用扩展方法来做到这一点。 See extension methods demystified查看扩展方法的神秘面纱

A procedure that takes a Customer, and a sequence of PropertyInfos that needs to be extracted from this customer.一个接受客户的过程,以及需要从该客户中提取的一系列 PropertyInfo。 The return value is a sequence of AnalysisCodeWithValues;返回值是一个 AnalysisCodeWithValues 序列; one AnalysisCodeWithValue per PropertyInfo, containing the name of the property and the string value.每个 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(),
        };
    }
}

Obviously this would lead to problems if GetValue would return null.显然,如果 GetValue 返回 null,这会导致问题。 You could use the null conditional operator for this to prevent problems:您可以为此使用 null 条件运算符来防止出现问题:

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

But since you are not interested in empty values anyway I'd go for:但既然你对空值不感兴趣,我会去:

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

Usage would be:用法是:

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

Similarly if you have a sequence of Customers and a sequence of Properties you want to extract:同样,如果您有一系列客户和一系列要提取的属性:

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

This is a nice solution if you want most properties of a Customer.如果您想要 Customer 的大多数属性,这是一个不错的解决方案。 You can't make any typing errors that the compiler won't detect, because you don't type the names of the properties你不能犯任何编译器检测不到的输入错误,因为你没有输入属性的名称

Alas this won't work if you only want a few properties of the customers.唉,如果您只想要客户的一些属性,这将不起作用。 In that case I'd go for delegates that extract the value.在那种情况下,我会选择提取价值的代表。 Added bonus: you can request for any value in the delegate, not only properties.额外的好处:您可以请求委托中的任何值,而不仅仅是属性。 You can call any function, even combine properties.您可以调用任何函数,甚至组合属性。

Disadvantage: you'll have to type the names per value that you want in your end result.缺点:您必须在最终结果中输入您想要的每个值的名称。 Obviously, because you want to create your own values.很明显,因为你想创造自己的价值观。

Input: a sequence of KeyValuePairs, where the Key is the Name of the AnalysisCodeWithValue and the value is the delegate that extracts the value from the Customer.输入:KeyValuePairs 序列,其中 Key 是 AnalysisCodeWithValue 的名称,value 是从 Customer 中提取值的委托。 Something like this:像这样的东西:

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

or for more difficult value:或者更难的价值:

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

The function will be like:该功能将类似于:

 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,
            };
        }
    }
}

usage:用法:

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,))
    },
}

Advantages: you can use the descriptions you like;优点:可以使用自己喜欢的描述; you can use any function you like.你可以使用任何你喜欢的功能。 The compiler will detect it if you make any typing errors.如果您输入任何错误,编译器会检测到它。

Disadvantage: more code to type.缺点:要输入的代码较多。

It is up to you to decide whether the advantages weigh up to the disadvantages由您决定利与弊

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM