Я пытаюсь вернуть объект с обновленными свойствами на основе условия в запросе LINQ. Ниже приведен запрос и работает нормально.
public class SampleDataModel
{
public string Name { get; set; }
public string Group { get; set; }
public int? Hyper { get; set; }
public string Sin { get; set; }
public int? Auto { get; set; }
public string Comment
{
get { return (Auto.HasValue ? "Hyper & Name should be masked" : ""); }
}
public override string ToString()
{
return $"Group={Group};Name={Name};Hyper={Hyper};Sin={Sin};Auto={Auto};Comment={Comment}";
}
}
public class SampleData
{
private static readonly IList<SampleDataModel> Data = new List<SampleDataModel>();
static SampleData()
{
Data.Add(new SampleDataModel()
{Name = "Name 1", Group = "Group 11", Hyper = 10, Sin = "S12345986554", Auto = 1});
Data.Add(new SampleDataModel()
{Name = "Name 2", Group = "Group 12", Hyper = 101, Sin = "MS7123452331", Auto = 19});
Data.Add(new SampleDataModel()
{Name = "Name 3", Group = "Group 13", Hyper = 103, Sin = "SJK1234Z5666", Auto = 18});
Data.Add(new SampleDataModel()
{Name = "Name 4", Group = "Group 14", Hyper = 210, Sin = "DFS125349811", Auto = 41});
Data.Add(new SampleDataModel()
{Name = "Name 5", Group = "Group 15", Hyper = 150, Sin = "YUTS12345000", Auto = null});
Data.Add(new SampleDataModel()
{Name = "Name 6", Group = "Group 16", Hyper = 106, Sin = "KTRS12Y345211", Auto = null});
Data.Add(new SampleDataModel()
{Name = "Name 7", Group = "Group 17", Hyper = 510, Sin = "CVS12X342895", Auto = 23});
Data.Add(new SampleDataModel()
{Name = "Name 8", Group = "Group 18", Hyper = 170, Sin = "ZASS12356545", Auto = 76});
Data.Add(new SampleDataModel()
{Name = "Name 9", Group = "Group 19", Hyper = 1210, Sin = "QWES18782345", Auto = 11});
Data.Add(new SampleDataModel()
{Name = "Name 10", Group = "Group 21", Hyper = 1450, Sin = "RTYS989812345", Auto = 91});
Data.Add(new SampleDataModel()
{Name = "Name 11", Group = "Group 31", Hyper = 1067, Sin = "SDF134562345", Auto = null});
Data.Add(new SampleDataModel()
{Name = "Name 12", Group = "Group 41", Hyper = 1087, Sin = "SHGF12376745", Auto = null});
Data.Add(new SampleDataModel()
{Name = "Name 13", Group = "Group 51", Hyper = 10912, Sin = "SKJU00012345", Auto = null});
}
public static IEnumerable<SampleDataModel> Get => Data;
}
public static class MaskService
{
public static IEnumerable<SampleDataModel> Mask(IEnumerable<SampleDataModel> data)
{
List<SampleDataModel> result = new List<SampleDataModel>();
var result1 = (from d in data
select new SampleDataModel()
{
Auto = d.Auto, Group = d.Group,
Sin = d.Sin,
Hyper = (!d.Auto.HasValue) ? d.Hyper : null,
Name = (!d.Auto.HasValue) ? d.Name : "XXXXXX"
});
result = result1.ToList<SampleDataModel>();
return result;
}
}
class Program
{
static void Main(string[] args)
{
var data = SampleData.Get;
var maskedData = MaskService.Mask(data);
foreach (var model in maskedData)
{
Console.WriteLine($"{model}");
}
}
}
Каждый раз, когда происходит изменение в столбцах, для которых требуется переопределение, происходит изменение кода либо для переопределения условия, либо для свойства. Чтобы преодолеть это и повторно использовать код, я придумал универсальное решение c, основанное на некоторых предложениях от stackoverflow, сообщениях из поиска Google и примере от Microsoft.
Generi c Подход:
public static class MaskService
{
public static bool Contains<T>(this IEnumerable<T> source, string property) where T:IMaskCriteria
{
if (source == null || string.IsNullOrWhiteSpace(property))
return false;
foreach (var maskCriteria in source)
{
return maskCriteria.Contains(property);
}
return false;
}
public static IEnumerable<MaskCondition> Get<T>(this IEnumerable<T> source, string property) where T : IMaskCriteria
{
IEnumerable<MaskCondition> result = null;
if (source == null || string.IsNullOrWhiteSpace(property))
return result;
foreach (var maskCriteria in source)
{
if (maskCriteria.Contains(property))
{
result = maskCriteria.Conditions;
break;
}
}
return result;
}
public static PropertyInfo Get(this PropertyInfo[] source, string property)
{
if (source == null || string.IsNullOrWhiteSpace(property))
return null;
property = property.ToLower();
foreach (var prop in source)
{
if (prop.Name.ToLower().Equals(property))
{
return prop;
}
}
return null;
}
public static IEnumerable<T> Mask<T>(this IEnumerable<T> source, IEnumerable<IMaskCriteria> maskProperties) where T : new()
{
var result = source.Select(MaskFun<T>(maskProperties));
return result;
}
public static Func<T, T> MaskFun<T>(IEnumerable<IMaskCriteria> maskProperties) where T : new()
{
if (maskProperties == null)
return null;
Type typeofT = typeof(T);
var model = Expression.New(typeofT);
var inputLinqVar = Expression.Parameter(typeofT, "d");//input variable in linq query
object GetDefault(Type type) => Nullable.GetUnderlyingType(type) != null ? null : type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : "XXXXXX";
var maskCriterias = maskProperties as List<IMaskCriteria> ?? maskProperties.ToList();
PropertyInfo[] props = typeofT.GetProperties();
List<MemberAssignment> memberAssignments = new List<MemberAssignment>();
foreach (var propertyInfo in props)
{
if (!propertyInfo.CanWrite)
continue;
bool hasToBeMasked = maskCriterias.Contains(propertyInfo.Name);
var valueFromLinqVariable = Expression.Property(inputLinqVar, propertyInfo); //d.{propertyName} eg. d.Id
MemberAssignment memberAssignment = Expression.Bind(propertyInfo, valueFromLinqVariable);//{propertyName} = d.{propertyName} eg. Id = d.Id
if (hasToBeMasked)
{
List<MaskCondition> maskConditions = maskCriterias.Get(propertyInfo.Name) as List<MaskCondition>;
List<Expression> binConditions = new List<Expression>();
if (maskConditions != null)
foreach (var maskCondition in maskConditions)
{
var condPropInfo = props.Get(maskCondition.PropertyName);
Expression left = Expression.Property(inputLinqVar, condPropInfo);
Expression right = Expression.Constant(maskCondition.PropertyValue);
Expression e1 = null;
switch (maskCondition.Comparer)
{
case MaskConditionComparer.Equals:
{
e1 = Expression.Equal(left, right);
break;
}
case MaskConditionComparer.NotEquals:
{
e1 = Expression.NotEqual(left, right);
break;
}
case MaskConditionComparer.GreaterThan:
{
e1 = Expression.GreaterThan(left, right);
break;
}
case MaskConditionComparer.GreaterThanOrEquals:
{
e1 = Expression.GreaterThanOrEqual(left, right);
break;
}
case MaskConditionComparer.LessThan:
{
e1 = Expression.LessThan(left, right);
break;
}
case MaskConditionComparer.LessThanOrEquals:
{
e1 = Expression.LessThanOrEqual(left, right);
break;
}
}
binConditions.Add(e1);
}
ConstantExpression maskedValue = Expression.Constant(GetDefault(propertyInfo.PropertyType),
propertyInfo.PropertyType);
ConditionalExpression assignMaskValueIfMaskConditionTrue = null;
if (binConditions.Count == 1)
{
assignMaskValueIfMaskConditionTrue =
Expression.Condition(binConditions[0], maskedValue, valueFromLinqVariable);
}
else if (binConditions.Count > 1)
{
Expression andExpression = binConditions[0];
//Expression getExp(Expression e1, Expression e2) => Expression.AndAlso(e1, e2);
for (var i = 1; i < binConditions.Count; i++)
{
andExpression = Expression.AndAlso(andExpression, binConditions[i]);
}
assignMaskValueIfMaskConditionTrue =
Expression.Condition(andExpression, maskedValue, valueFromLinqVariable);
}
assignMaskValueIfMaskConditionTrue =
(ConditionalExpression)new ParameterReplacer(inputLinqVar).Visit(
assignMaskValueIfMaskConditionTrue);
var condExp = Expression.Lambda<Func<T, Object>>(assignMaskValueIfMaskConditionTrue, inputLinqVar);
memberAssignment = Expression.Bind(propertyInfo, condExp);
}
memberAssignments.Add(memberAssignment);
var message = hasToBeMasked ? "will be masked" : "no masking";
Console.WriteLine($"{propertyInfo.Name}->{propertyInfo.PropertyType.Name}==>{message};{Nullable.GetUnderlyingType(propertyInfo.PropertyType)?.FullName}");
}
var modelInit = Expression.MemberInit(model, memberAssignments);// new T() { propertyName = d.propertyName} eg. new Emp() { Name = d.Name }
var lambda = Expression.Lambda<Func<T, T>>(modelInit, inputLinqVar);
return lambda.Compile();
}
}
public class ParameterReplacer : ExpressionVisitor
{
private readonly ParameterExpression _parameter;
protected override Expression VisitParameter(ParameterExpression node)
{
return base.VisitParameter(_parameter);
}
public ParameterReplacer(ParameterExpression parameter)
{
_parameter = parameter;
}
}
public class PropertyComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
if (string.IsNullOrWhiteSpace(x) || string.IsNullOrWhiteSpace(y))
return false;
return (x.ToLower() == (y.ToLower()));
}
public int GetHashCode(string obj)
{
return obj.GetHashCode();
}
}
public interface IMaskCriteria
{
List<string> ToBeMasked { get; set; }
List<MaskCondition> Conditions { get; set; }
bool Contains(string property);
}
public class MaskCriteria : IMaskCriteria
{
private static readonly PropertyComparer comparer = new PropertyComparer();
public MaskCriteria()
{
ToBeMasked = new List<string>();
Conditions = new List<MaskCondition>();
}
public List<string> ToBeMasked { get; set; }
public List<MaskCondition> Conditions { get; set; }
public bool Contains(string property)
{
if (ToBeMasked == null)
return false;
if (string.IsNullOrWhiteSpace(property))
return false;
return ToBeMasked.Contains(property, comparer);
}
}
public class MaskCondition
{
public string PropertyName { get; set; }
public object PropertyValue { get; set; }
public MaskConditionComparer Comparer { get; set; }
}
public enum MaskConditionComparer
{
Equals,
NotEquals,
GreaterThan,
GreaterThanOrEquals,
LessThan,
LessThanOrEquals
}
class Program
{
static void Main(string[] args)
{
var data = SampleData.Get;
Console.WriteLine("******************Start Masking Using Expression Trees In LINQ***********************");
var mc = new MaskCriteria();
mc.ToBeMasked.AddRange(new List<string>() { "name", "hyper" });
mc.Conditions.Add(new MaskCondition(){ PropertyValue = null,PropertyName = "Auto", Comparer = MaskConditionComparer.Equals});
maskedData = data.Mask<SampleDataModel>(new List<MaskCriteria>() {mc});
foreach (var model in maskedData)
{
Console.WriteLine($"{model}");
}
Console.WriteLine("******************End Masking Using Expression Trees In LINQ***********************");
Console.ReadLine();
}
}
Когда я запускаю код, я получаю следующее исключение в memberAssignment = Expression.Bind(propertyInfo, condExp);
Система .ArgumentException: 'Типы аргументов не совпадают'
Если я удалю регистр переключателя, то свойства Name и Hyper получат XXXXX или ноль соответственно.
Я пробовал несколько способов без удачи и разных исключений. Я никогда не использовал деревья выражений раньше. Так может кто-нибудь подсказать, как поступить с условным заданием?