根据ViewModel自动生成Entity表达式树


网站用的Mongodb,Entity和ViewModel分离

项目中所有Entity都实现IBaseEntity接口

public interface IBaseEntity
{

}

//IBaseEntity 默认实现
public class BaseEntity : IBaseEntity
{
	[JsonConverter(typeof(ObjectIdConverter))]
	public ObjectId Id { get; set; }
}

项目中所有的ViewModel都实现IMapFrom<T>接口或者IHaveCustomMapping接口,目的是使用AutoMapper创建Mapper,这里只实现IMapFrom<T>的表达式生成(详见)。

public interface IMapFrom<T>
{

}

public interface IHaveCustomMapping
{
	void CreateMappings(MapperConfigurationExpression cfg);
}

首先定义比较的枚举:

public enum ExpressComparison
{
	/// <summary>
	/// 等于
	/// </summary>
	Equal,
	/// <summary>
	/// 包含
	/// </summary>
	Contains,
	/// <summary>
	/// 不等于
	/// </summary>
	NoEqual,
	/// <summary>
	/// 大于
	/// </summary>
	Gt,
	/// <summary>
	/// 小于
	/// </summary>
	Lt,
	/// <summary>
	/// 大于或等于
	/// </summary>
	GtOrEqual,
	/// <summary>
	/// 小于或等于
	/// </summary>
	LtOrEqual,

	/// <summary>
	/// 自定义规则
	/// </summary>
	Custom
}

再定义特性,用来标注(Attribute)ViewModel中需要在Entity中比较的属性(Property

//只能标注与属性上
[AttributeUsage(AttributeTargets.Property)]
public class ViewModelLabelAttribute:Attribute
{
	public ExpressComparison Comparison { get; private set; }

	/// <summary>
	/// 生成表达式树,需要比较的方式
	/// </summary>
	/// <param name="expressComparison">比较方式</param>
	public ViewModelLabelAttribute(ExpressComparison expressComparison)
	{
		Comparison = expressComparison;
	}
}

处理需要查询的字段

class QueryField
{
	//ViewModel上标注的比较方式
	public ViewModelLabelAttribute Label { get; set; }

	//比较的属性。因为是默认方式,所以ViewModel和Entity上的属性类型应该一致
	public PropertyInfo Property { get; set; }

	//比较的值
	public object Value { get; set; }
	
	//处理各种比较方式
	Expression HandleField(ParameterExpression parameter)
	{
		//如果需要比较的值为null,那么返回Empty表达式
		if (Value == null) return Expression.Empty();
		//约定如果需要比较的值是枚举类型,但是值为-1的情况下,过滤掉
		if (Property.PropertyType.IsEnum && Convert.ToInt32(Value) == -1)
			return Expression.Empty();
		//属性成员表达式
		var memberExpr = Expression.Property(parameter, Property);
		switch (Label.Comparison)
		{
			//相等的比较
			case ExpressComparison.Equal:
				return Expression.Equal(memberExpr, Expression.Constant(Value));
			//大于
			case ExpressComparison.Gt:
				return Expression.GreaterThan(memberExpr, Expression.Constant(Value));
			//大于或者等于
			case ExpressComparison.GtOrEqual:
				return Expression.GreaterThanOrEqual(memberExpr, Expression.Constant(Value));
			//小于
			case ExpressComparison.Lt:
				return Expression.LessThan(memberExpr, Expression.Constant(Value));
			//小于或者等于
			case ExpressComparison.LtOrEqual:
				return Expression.LessThanOrEqual(memberExpr, Expression.Constant(Value));
			//不等于
			case ExpressComparison.NoEqual:
				return Expression.NotEqual(memberExpr, Expression.Constant(Value));
			//包含
			case ExpressComparison.Contains:
				//如果比较的类型是string类型,那么反射获取string类型的Contains方法,生成表达式
				if (Property.PropertyType == typeof(string) && Value is string)
				{
					var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
					if (containsMethod != null)
					{
						return Expression.Call(memberExpr, containsMethod, Expression.Constant(Value));
					}
				}
				//如果比较的类型是集合,那么反射获取linq中的Contains方法,生成表达式
				if (typeof(IEnumerable).IsAssignableFrom(Property.PropertyType))
				{
					var containsMethod = typeof(Enumerable)
						.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
						.FirstOrDefault(x =>
							x.Name == "Contains" && x.ReturnType == typeof(bool) &&
							x.GetParameters().Length == 2);

					if (containsMethod != null)
					{
						containsMethod = containsMethod.MakeGenericMethod(Value.GetType());
						return Expression.Call(containsMethod, memberExpr, Expression.Constant(Value));
					}
				}
				//Todo 暂未实现
				throw new NotImplementedException("string、IEnumerable以外类型的Contains表达式未实现!");
			case ExpressComparison.Custom:
				//Todo 暂未实现
				throw new NotImplementedException("暂无自定义实现");
		}
		//默认为Empty表达式
		return Expression.Empty();
	}
}

扩展一个ViewMode的方法,来生成Entity类型的表达式树

/*
* TEntity,泛型,Entity的类型
* IMapForm<TEntity>,ViewModel的类型,ViewModel实现了IMapForm<IBaseEntiy>接口
*/
public static Expression<Func<TEntity, bool>> GenerateExpressTree<TEntity>(this IMapFrom<TEntity> mapFrom)
	where TEntity : IBaseEntity, new()
{
	//表达式树形参
	var parameter = Expression.Parameter(typeof(TEntity), "__q");
	//获取ViewModel的所有属性
	var queryFieldsExpression = (from x in mapFrom.GetType().GetProperties()
		//获取属性所标注的ViewModelLabelAttribute特性
		let attr = x.GetCustomAttribute<ViewModelLabelAttribute>()
		where attr != null //过滤掉没有标注的特性
		let field = new QueryField
		{
			Label = attr,//特性
			Property = typeof(TEntity).GetProperty(x.Name),//属性
			Value = x.GetValue(mapFrom)//属性值
		}
		//处理查询字段
		select field.HandleField(parameter)).Where(x => x.NodeType != ExpressionType.Default).ToList();
	//创建一个默认表达式树
	var expression =
		(Expression)Expression
			.Constant(true);
	//遍历所有需要查询的字段
	foreach (var expr in queryFieldsExpression)
	{
		//第一个表达式树节点赋值给expression
		if (queryFieldsExpression.IndexOf(expr) == 0)
		{
			expression = expr;
			continue;
		}
		//其余表达式树节点以 && 形式生成
		expression = Expression.AndAlso(expression, expr);
	}
	//生成Lambda表达式树
	return Expression.Lambda<Func<TEntity, bool>>(expression, parameter);
}

单元测试

定义Entity和ViewModel

public enum Sex
{
	Man,
	Wuman
}

public class VmUser : IMapFrom<UserTest>
{
	[ViewModelLabel(ExpressComparison.Equal)]
	public string Id { get; set; }

	[ViewModelLabel(ExpressComparison.Contains)]
	public string Name { get; set; }

	public string Avatar { get; set; }

	[ViewModelLabel(ExpressComparison.Equal)]
	public Sex Sex { get; set; }

	[ViewModelLabel(ExpressComparison.Contains)]
	public string Groups { get; set; }
}

public class UserTest : IBaseEntity
{
	public string Id { get; set; }

	public string Name { get; set; }

	public string Avatar { get; set; }

	public Sex Sex { get; set; }

	public string[] Groups { get; set; }
}

直接从内存从加载相关数据

var list = new List<UserTest>
{
	new UserTest
	{
		Avatar = "http://localhost:9527/images/laola.png",
		Id = "pengqian",
		Name = "彭迁",
		Sex = Sex.Man,
		Groups = new[] {"111", "222", "333"}
	},
	new UserTest
	{
		Avatar = "http://localhost:9527/images/laola.png",
		Id = "pengqian",
		Name = "dpz",
		Sex = Sex.Man,
		Groups = new[] {"444", "555", "666"}
	},
	new UserTest
	{
		Avatar = "http://localhost:9527/images/laola.png",
		Id = "xx",
		Name = "xx",
		Sex = Sex.Wuman,
		Groups = new[] {"777", "888", "999"}
	}
};

var viewModel = new VmUser
{
	Avatar = "http://localhost:9527/images/laola.png",
	Id = "pengqian",
	Name = "d",
	Sex = Sex.Man,
	Groups = "666"
};

单元测试方法

[TestMethod]
public void 生成表达式树单元测试()
{
	var expr = (Expression<Func<UserTest, bool>>) viewModel.GenerateExpressTree();
	var result = list.Where(expr.Compile()).ToList();
	Assert.IsTrue(result.Count > 0);
	result.ForEach(x => Console.WriteLine($"Avatar:{x.Avatar},Id:{x.Id},Name:{x.Name},Sex:{x.Sex},Group:{string.Join("-",x.Group)}"));
}
output:
Avatar:http://localhost:9527/images/laola.png,Id:pengqian,Name:dpz,Sex:Man,Group:444-555-666

结语:当然这只是一个尝试,一个思路,也可以有其它实现方式,比如使用模板之类的实现


文章作者: 彭迁
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 彭迁 !
  目录