简体   繁体   English

实体框架:如何使用ExpressionVisitor替换c#中的lambda查询的重要部分

[英]Entity Framework: how to replace a significant part of a lambda query in c# by using an ExpressionVisitor

This is the idea: 这是个主意:

Our application has a table of Products, which have translatable names. 我们的应用程序有一个具有可翻译名称的Products表。 Because the amount of supported languages can expand, every product has a collection of ProductTranslation, which contains a culture (for instance 'en-US') and every translatable property. 因为支持的语言数量可以扩展,所以每个产品都有一个ProductTranslation集合,其中包含一种文化(例如“ en-US”)和每个可翻译的属性。

The domain looks like this: 域如下所示:

/// <summary>
///     Marks a class as translatable, i.e. there are properties that need to be different per language, such as a name or description. 
/// </summary>
/// <typeparam name="TTranslation"></typeparam>
public interface ITranslatable<TTranslation> where TTranslation: ITranslation
{
    /// <summary>
    ///     Gets or sets the translations
    /// </summary>
    TranslationCollection<TTranslation> Translations { get; set; }
}

/// <summary>
///    Marks this class as a translation of another class. 
/// </summary>
public interface ITranslation
{
    /// <summary>
    ///     Gets or sets the culture
    /// </summary>
    string Culture { get; set; }
}

public class Product : ITranslatable<ProductTranslation>
{
    private TranslationCollection<ProductTranslation> _translations;

    /// <summary>
    ///     Gets or sets the code.
    /// </summary>
    public virtual string Code { get; set; }

    /// <summary>
    ///     Gets or sets the price.
    /// </summary>
    public virtual decimal? Price { get; set; }

    public virtual TranslationCollection<ProductTranslation> Translations
    {
        get { return _translations ?? (_translations = new TranslationCollection<ProductTranslation>()); }
        set { _translations = value; }
    }
}

/// <summary>
///     Contains the translatable properties for <see cref="Product"/>
/// </summary>
public class ProductTranslation: ITranslation
{
    /// <summary>
    ///     Gets or sets the culture of this translation
    /// </summary>
    public string Culture { get; set; }

    /// <summary>
    ///     Gets or sets the name.
    /// </summary>
    public virtual string Name { get; set; }
}

As you may have noticed, I'm using a custom collection class for the translations. 您可能已经注意到,我正在使用自定义集合类进行翻译。 (TranslationCollection instead of the default ICollection) (TranslationCollection而不是默认的ICollection)

This class extends Collection but adds a utility property 'Current' that returns the translation that matches the current UI culture: 此类扩展了Collection,但添加了实用程序属性“ Current”,该属性返回与当前UI文化匹配的翻译:

/// <summary>
///     Contains specific methods to work with translations
/// </summary>
/// <typeparam name="TTranslation"></typeparam>
public class TranslationCollection<TTranslation>: Collection<TTranslation> where TTranslation: ITranslation
{
    /// <summary>
    ///     Initializes an empty <see cref="TranslationCollection{TTranslation}"/>
    /// </summary>
    public TranslationCollection()
    {
    }

    /// <summary>
    ///     Initializes a new <see cref="TranslationCollection{TTranslation}"/> with the given <paramref name="list"/> as its contents
    /// </summary>
    /// <param name="list"></param>
    public TranslationCollection(IList<TTranslation> list) : base(list)
    {
    }

    /// <summary>
    ///     Returns the translation that has the same culture as the current UI culture.
    /// </summary>
    public TTranslation Current
    {
        get
        {
            return this.SingleOrDefault(t => t.Culture == CultureInfo.CurrentUICulture.Name);
        }
    }
}

As you can see, there's very little going on here, but I like the idea of having a custom collection class for this, as it may come in handy later when we want to make some custom HTML components for display and forms. 如您所见,这里几乎没有发生任何事情,但是我喜欢为此创建一个自定义集合类的想法,因为稍后我们想要为显示和表单创建一些自定义HTML组件时可能会派上用场。

Now the question: 现在的问题是:

When we query the products table, a search by its name would look something like this: 当我们查询产品表时,按其名称进行的搜索将类似于以下内容:

var products = dbContext.Products.Where(p => p.Translations.Where(t => t.Culture == CultureInfo.CurrentUICulture).Any(t => t.Name.ToLower().Contains("abc")))

However, seeing as there will be a lot of translatable tables in the future (it's a rather large application), it would be very interesting if we could write: 但是,鉴于将来会出现很多可翻译的表(这是一个相当大的应用程序),如果我们可以编写以下内容,将非常有趣:

var products = dbContext.Products.Where(p => p.Translations.Current.Name.ToLower().Contains("abc"))

Of course, that 'Current' property is unmapped and Entity Framework will throw an exception when you run this code. 当然,“当前”属性是未映射的,并且在运行此代码时,Entity Framework将引发异常。 However, would it be possible to automatically convert the 'Current' call to something else, using an ExpressionVisitor (or anything else) 但是,可以使用ExpressionVisitor(或其他任何方法)将“当前”调用自动转换为其他调用

I've made a first attempt, but am a bit stuck: 我已经做了第一次尝试,但是有点卡住了:

public class CurrentTranslationVisitor: ExpressionVisitor
{
    protected override Expression VisitMember(MemberExpression node)
    {
        if(node.Member.MemberType != MemberTypes.Property)
            return base.VisitMember(node);
        var propertyInfo = node.Member as PropertyInfo;
        if (propertyInfo == null)
            return base.VisitMember(node);
        if (!typeof (ITranslation).IsAssignableFrom(propertyInfo.PropertyType))
            return base.VisitMember(node);
        if (!string.Equals(propertyInfo.Name, "Current"))
            return base.VisitMember(node);

        // at this point we can be confident that the property is [someTranslatableObject].Translations.Current

    }
}

How can I access the code that is written against the Current property at this point? 我现在如何访问针对Current属性编写的代码?

For instance, when the expression is 例如,当表达式为

p => p.Translations.Current.Name.ToLower().Contains("abc")

how can I gain access to 我如何获得

.Name.ToLower().Contains("abc")

Suggestions and help would be much appreciated! 建议和帮助将不胜感激!

So, to start off with we'll use the following helper method to combine expressions. 因此,首先,我们将使用以下辅助方法来组合表达式。 It allows expressions to be composed without that composition being visible externally. 它允许在不从外部可见该组成的情况下组成表达式。 This Compose method will take a LambadExpression and another who's input is the same type as the output of the first. Compose方法将使用LambadExpression,另一个输入的类型与第一个输入的类型相同。 If these were functions we would just call one and pass it's result as the input of the other. 如果这些是函数,我们只需调用其中一个并将其结果作为另一个的输入即可。 Since these are expressions it's a tad more complex than that though. 由于这些是表达式,因此要比这复杂得多。 We'll need to use an expression visitor to replace all instances of the parameter of one with the body of another. 我们将需要使用表达式访问者将一个参数的所有实例替换为另一个的主体。

The visitor that this helper function needs: 该助手功能需要的访客:

public class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

The method itself: 方法本身:

public static Expression<Func<TFirstParam, TResult>>
    Compose<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = new ReplaceVisitor(first.Parameters.First(), param)
        .Visit(first.Body);
    var newSecond = new ReplaceVisitor(second.Parameters.First(), newFirst)
        .Visit(second.Body);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

Note that this is taken from this previous answer of mine 请注意,这取自我的先前答案

Now we can use this Compose method to create a method that will take in a LambdaExpression of something to a TranslationCollection and return a LambdaExpression of that same object mapped to a single ITranslation object representing the current culture. 现在,我们可以使用此Compose方法创建一个方法,该方法将某个东西的LambdaExpression TranslationCollection ,并将该对象的LambdaExpression返回映射到表示当前区域性的单个ITranslation对象的同一对象。 At this point most of the work has already been done for us: 至此,大部分工作已经为我们完成:

public static Expression<Func<T, TTranslation>> SelectCurrent<T, TTranslation>
    (Expression<Func<T, TranslationCollection<TTranslation>>> expression)
    where TTranslation : ITranslation
{
    return expression.Compose(collection => 
        collection.FirstOrDefault(t => t.Culture == CultureInfo.CurrentUICulture.Name));
}

Now for an example usage. 现在来看一个示例用法。 We can take a products queryable, use SelectCurrent to get the current translation and then Compose to map that translation to the actual filter that we want to apply: 我们可以选择一个可查询的产品,使用SelectCurrent来获取当前翻译,然后使用Compose将该翻译映射到我们要应用的实际过滤器:

public static void Foo()
{
    IQueryable<Product> products = null;
    var query = products.Where(
        SelectCurrent((Product p) => p.Translations)
        .Compose(translation => translation.Name.ToLower().Contains("abc")));
}

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

相关问题 C# - 如何使用lambda在Entity Framework中进行简单查询? - C# - How can I do a simple query in Entity Framework using lambda? 使用C#中的ExpressionVisitor识别lambda表达式中的括号 - Identify parentheses in lambda expression with ExpressionVisitor in C# 使用LINQ ExpressionVisitor将基本参数替换为lambda表达式中的属性引用 - Using a LINQ ExpressionVisitor to replace primitive parameters with property references in a lambda expression C# LINQ 在搜索过程中忽略空值的实体框架查询 - C# LINQ Entity Framework query that ignores nulls as part of the search 在实体框架查询中使用C#函数 - Using C# function in Entity Framework Query 在C#中使用Entity Framework和lambda表达式的Gernerate List - Gernerate List using Entity Framework and lambda expressions in C# C#实体框架-联接lambda查询后返回单个实体的列表 - C# Entity Framework - returning list of single entity after a join lambda query 如何使用带有Lambda表达式的实体框架在mysql查询下编写代码? - How to write below mysql query using Entity Framework with Lambda expression? 实体框架 - 使用lambda表达式编写查询 - Entity Framework - writing query using lambda expression 如何在C#上编写实体框架查询和Linq查询? - How to write entity framework query and Linq query on c#?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM