gpt4 book ai didi

c# - 表达式——如何重用业务逻辑?如何组合它们?

转载 作者:行者123 更新时间:2023-11-30 20:55:08 25 4
gpt4 key购买 nike

注意:这是一篇很长的帖子,请滚动到底部查看问题 - 希望这能让我更容易理解我的问题。谢谢!


我有“成员”模型,定义如下:

public class Member
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string ScreenName { get; set; }

[NotMapped]
public string RealName
{
get { return (FirstName + " " + LastName).TrimEnd(); }
}

[NotMapped]
public string DisplayName
{
get
{
return string.IsNullOrEmpty(ScreenName) ? RealName : ScreenName;
}
}
}

这是现有的项目和模型,我不想更改它。现在我们收到一个请求,要求通过 DisplayName 启用配置文件检索:

public Member GetMemberByDisplayName(string displayName)
{
var member = this.memberRepository
.FirstOrDefault(m => m.DisplayName == displayName);
return member;
}

此代码不起作用,因为 DisplayName 未映射到数据库中的字段。好吧,那我来个表情:

public Member GetMemberByDisplayName(string displayName)
{
Expression<Func<Member, bool>> displayNameSearchExpr = m => (
string.IsNullOrEmpty(m.ScreenName)
? (m.Name + " " + m.LastName).TrimEnd()
: m.ScreenName
) == displayName;

var member = this.memberRepository
.FirstOrDefault(displayNameSearchExpr);

return member;
}

这行得通。唯一的问题是生成显示名称的业务逻辑被复制/粘贴到两个不同的地方。我想避免这种情况。但我不明白该怎么做。我带来的最好的是:

  public class Member
{

public static Expression<Func<Member, string>> GetDisplayNameExpression()
{
return m => (
string.IsNullOrEmpty(m.ScreenName)
? (m.Name + " " + m.LastName).TrimEnd()
: m.ScreenName
);
}

public static Expression<Func<Member, bool>> FilterMemberByDisplayNameExpression(string displayName)
{
return m => (
string.IsNullOrEmpty(m.ScreenName)
? (m.Name + " " + m.LastName).TrimEnd()
: m.ScreenName
) == displayName;
}

private static readonly Func<Member, string> GetDisplayNameExpressionCompiled = GetDisplayNameExpression().Compile();

[NotMapped]
public string DisplayName
{
get
{
return GetDisplayNameExpressionCompiled(this);
}
}

[NotMapped]
public string RealName
{
get { return (FirstName + " " + LastName).TrimEnd(); }
}

}

问题:

(1) 如何在 FilterMemberByDisplayNameExpression() 中重用 GetDisplayNameExpression()?我尝试了 Expression.Invoke:

public static Expression<Func<Member, bool>> FilterMemberByDisplayNameExpression(string displayName)
{
Expression<Func<string, bool>> e0 = s => s == displayName;
var e1 = GetDisplayNameExpression();

var combinedExpression = Expression.Lambda<Func<Member, bool>>(
Expression.Invoke(e0, e1.Body), e1.Parameters);

return combinedExpression;
}

但我从提供商处收到以下错误:

The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.

(2)DisplayName 属性中使用 Expression.Compile() 是一种好方法吗?有什么问题吗?

(3) 如何在 GetDisplayNameExpression() 中移动 RealName 逻辑?我想我必须创建另一个表达式和另一个编译表达式,但我不明白如何从 GetDisplayNameExpression() 内部调用 RealNameExpression

谢谢。

最佳答案

我可以修复你的表达式生成器,我可以编写你的 GetDisplayNameExpression (所以 13)

public class Member
{
public string ScreenName { get; set; }
public string Name { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }

public static Expression<Func<Member, string>> GetRealNameExpression()
{
return m => (m.Name + " " + m.LastName).TrimEnd();
}

public static Expression<Func<Member, string>> GetDisplayNameExpression()
{
var isNullOrEmpty = typeof(string).GetMethod("IsNullOrEmpty", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string) }, null);

var e0 = GetRealNameExpression();
var par1 = e0.Parameters[0];

// Done in this way, refactoring will correctly rename m.ScreenName
// We could have used a similar trick for string.IsNullOrEmpty,
// but it would have been useless, because its name and signature won't
// ever change.
Expression<Func<Member, string>> e1 = m => m.ScreenName;

var screenName = (MemberExpression)e1.Body;
var prop = Expression.Property(par1, (PropertyInfo)screenName.Member);
var condition = Expression.Condition(Expression.Call(null, isNullOrEmpty, prop), e0.Body, prop);

var combinedExpression = Expression.Lambda<Func<Member, string>>(condition, par1);
return combinedExpression;
}

private static readonly Func<Member, string> GetDisplayNameExpressionCompiled = GetDisplayNameExpression().Compile();

private static readonly Func<Member, string> GetRealNameExpressionCompiled = GetRealNameExpression().Compile();

public string DisplayName
{
get
{
return GetDisplayNameExpressionCompiled(this);
}
}

public string RealName
{
get
{
return GetRealNameExpressionCompiled(this);
}
}

public static Expression<Func<Member, bool>> FilterMemberByDisplayNameExpression(string displayName)
{
var e0 = GetDisplayNameExpression();
var par1 = e0.Parameters[0];

var combinedExpression = Expression.Lambda<Func<Member, bool>>(
Expression.Equal(e0.Body, Expression.Constant(displayName)), par1);

return combinedExpression;
}

请注意我是如何重用 GetDisplayNameExpression 的相同参数的表达式 e1.Parameters[0] (放入 par1 )这样我就不必重写表达式(否则我需要使用表达式重写器)。

我们可以使用这个技巧,因为我们只有一个表达式要处理,我们必须附加一些新代码。完全不同的(我们需要一个表达式重写器)是尝试组合两个表达式的情况(例如做一个 GetRealNameExpression() + " " + GetDisplayNameExpression() ,两者都需要一个 Member 作为参数,但它们的参数是分开的......可能这 https://stackoverflow.com/a/5431309/613130 会工作...

对于 2,我没有发现任何问题。您正确使用了 static readonly .但是请看GetDisplayNameExpression并思考“某些业务代码重复付费还是那个更好?”

通用解决方案

现在......我非常确定它是可行的......事实上它可行的:一个表达式“扩展器”将“特殊属性”“扩展”到它们的表达式“自动地”。

public static class QueryableEx
{
private static readonly ConcurrentDictionary<Type, Dictionary<PropertyInfo, LambdaExpression>> expressions = new ConcurrentDictionary<Type, Dictionary<PropertyInfo, LambdaExpression>>();

public static IQueryable<T> Expand<T>(this IQueryable<T> query)
{
var visitor = new QueryableVisitor();
Expression expression2 = visitor.Visit(query.Expression);

return query.Expression != expression2 ? query.Provider.CreateQuery<T>(expression2) : query;
}

private static Dictionary<PropertyInfo, LambdaExpression> Get(Type type)
{
Dictionary<PropertyInfo, LambdaExpression> dict;

if (expressions.TryGetValue(type, out dict))
{
return dict;
}

var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

dict = new Dictionary<PropertyInfo, LambdaExpression>();

foreach (var prop in props)
{
var exp = type.GetMember(prop.Name + "Expression", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).Where(p => p.MemberType == MemberTypes.Field || p.MemberType == MemberTypes.Property).SingleOrDefault();

if (exp == null)
{
continue;
}

if (!typeof(LambdaExpression).IsAssignableFrom(exp.MemberType == MemberTypes.Field ? ((FieldInfo)exp).FieldType : ((PropertyInfo)exp).PropertyType))
{
continue;
}

var lambda = (LambdaExpression)(exp.MemberType == MemberTypes.Field ? ((FieldInfo)exp).GetValue(null) : ((PropertyInfo)exp).GetValue(null, null));

if (prop.PropertyType != lambda.ReturnType)
{
throw new Exception(string.Format("Mismatched return type of Expression of {0}.{1}, {0}.{2}", type.Name, prop.Name, exp.Name));
}

dict[prop] = lambda;
}

// We try to save some memory, removing empty dictionaries
if (dict.Count == 0)
{
dict = null;
}

// There is no problem if multiple threads generate their "versions"
// of the dict at the same time. They are all equivalent, so the worst
// case is that some CPU cycles are wasted.
dict = expressions.GetOrAdd(type, dict);

return dict;
}

private class SingleParameterReplacer : ExpressionVisitor
{
public readonly ParameterExpression From;
public readonly Expression To;

public SingleParameterReplacer(ParameterExpression from, Expression to)
{
this.From = from;
this.To = to;
}

protected override Expression VisitParameter(ParameterExpression node)
{
return node != this.From ? base.VisitParameter(node) : this.Visit(this.To);
}
}

private class QueryableVisitor : ExpressionVisitor
{
protected static readonly Assembly MsCorLib = typeof(int).Assembly;
protected static readonly Assembly Core = typeof(IQueryable).Assembly;

// Used to check for recursion
protected readonly List<MemberInfo> MembersBeingVisited = new List<MemberInfo>();

protected override Expression VisitMember(MemberExpression node)
{
var declaringType = node.Member.DeclaringType;
var assembly = declaringType.Assembly;

if (assembly != MsCorLib && assembly != Core && node.Member.MemberType == MemberTypes.Property)
{
var dict = QueryableEx.Get(declaringType);

LambdaExpression lambda;

if (dict != null && dict.TryGetValue((PropertyInfo)node.Member, out lambda))
{
// Anti recursion check
if (this.MembersBeingVisited.Contains(node.Member))
{
throw new Exception(string.Format("Recursively visited member. Chain: {0}", string.Join("->", this.MembersBeingVisited.Concat(new[] { node.Member }).Select(p => p.DeclaringType.Name + "." + p.Name))));
}

this.MembersBeingVisited.Add(node.Member);

// Replace the parameters of the expression with "our" reference
var body = new SingleParameterReplacer(lambda.Parameters[0], node.Expression).Visit(lambda.Body);

Expression exp = this.Visit(body);

this.MembersBeingVisited.RemoveAt(this.MembersBeingVisited.Count - 1);

return exp;
}
}

return base.VisitMember(node);
}
}
}
  • 它是如何工作的?魔法、倒影、仙尘……
  • 它是否支持引用其他属性的属性?
  • 它需要什么?

它需要名称 Foo 的每个“特殊”属性有一个相应的名为 FooExpression 的静态字段/静态属性返回 Expression<Func<Class, something>>

需要通过扩展方法“转换”查询Expand()在具体化/枚举之前的某个时刻。所以:

public class Member
{
// can be private/protected/internal
public static readonly Expression<Func<Member, string>> RealNameExpression =
m => (m.Name + " " + m.LastName).TrimEnd();

// Here we are referencing another "special" property, and it just works!
public static readonly Expression<Func<Member, string>> DisplayNameExpression =
m => string.IsNullOrEmpty(m.ScreenName) ? m.RealName : m.ScreenName;

public string RealName
{
get
{
// return the real name however you want, probably reusing
// the expression through a compiled readonly
// RealNameExpressionCompiled as you had done
}
}

public string DisplayName
{
get
{
}
}
}

// Note the use of .Expand();
var res = (from p in ctx.Member
where p.RealName == "Something" || p.RealName.Contains("Anything") ||
p.DisplayName == "Foo"
select new { p.RealName, p.DisplayName, p.Name }).Expand();

// now you can use res normally.
  • 限制 1:一个问题是像 Single(Expression) 这样的方法, First(Expression) , Any(Expression)和类似的,不返回 IQueryable .首先使用 Where(Expression).Expand().Single() 进行更改

  • 限制 2:“特殊”属性不能在循环中引用自身。因此,如果 A 使用 B,则 B 不能使用 A,并且使用三元表达式之类的技巧将无法正常工作。

关于c# - 表达式——如何重用业务逻辑?如何组合它们?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18488184/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com