I have a fixed-length data file need to persistence into database. I use a XML file to define the length for the fields and use a list of FieldItem class to store the data.
class FieldItem
{
public string ObjectName {get; set;}
public string ObjectProperty {get; set;}
public string ObjectValue {get; set;}
public FieldItem()
{
}
}
So the FieldItem will look like
var fieldItem = new FieldItem
{
ObjectName = "Company",
ObjectProperty = "Name",
ObjectValue = "ABC Corp."
}
After get the list of FieldItem, I will do refection to create Company and other domain objects to save them into database.
But before they are saved into database, I have some business rules needed to be applied to validate the data line. My data line will look like:
var fieldItemList = new List<FieldItem>(){
new FieldItem {
ObjectName="Company",
ObjectProperty = "Name",
ObjectValue = "ABC"
}
new FieldItem{
ObjectName = "Product",
ObjectProperty = "ProductCode",
ObjectValue ="XYZ0123"
new FieldItem{
ObjectName = "Product",
ObjectProperty = "ProductName",
ObjectValue ="Christmas Tree"
}
// other FieldItem objects...
}
For example, my rule is to check if the company == "ABC" and ProductCode == "XYZ0123". Those rules are created by users and stored as a string in the database. What I am doing right now is to use Microsoft's System.Linq.Dynamic to evaluate the rule, such as
string validationRule = " (ObjectName == \"Company\" And ObjectProperty=\"CompanyName\" And ObjectValue = \"ABC Corp.\") And (ObjectName == \"Product\" And ObjectProperty=\"ProductCode\" And ObjectValue = \"XYZ0123\") ";
var query = fieldItemList.AsQuerable().Where(validationRule);
then check if it has one row return to tell if that data row has passed the rule or not.
Obviously, it is too verbose. Do you have any better suggestion? What should I do if I only like my rule expression like: "Company.CompanyName = 'ABC Corp.' and Product.ProductCode = 'XYZ001'"?
RE: "What should I do if I only like my rule expression like: "Company.CompanyName = 'ABC Corp' and Product.ProductCode = 'XYZ001'"?
Map this user-friendly query: "Company.CompanyName = 'ABC Corp' AND Product.ProductCode = 'XYZ001'" to something that is friendly to your data structure(FieldItem):
"ObjectName = 'Company' AND ObjectProperty = 'CompanyName' AND ObjectValue = 'ABC Corp' OR ObjectName = 'Product' AND ObjectProperty = 'ProductCode' AND ObjectValue = 'XYZ001'"
How to know if the user's rules passed the rules or not? Count the number of conditions, if it matches the count of results of fieldItemList.AsQueryable().Where(itemFieldFriendlyQuery), then the data line is valid based on user's rules.
some rudimentary mapper(use regular expression or roll your own parser to make the following code truly valid):
public Form3()
{
InitializeComponent();
string userFriendlyQuery = "Company.CompanyName = 'ABC Corp' AND Product.ProductCode = 'XYZ001'";
string[] queryConditions = userFriendlyQuery.Split(new string[]{" AND "},StringSplitOptions.None);
int conditionsCount = queryConditions.Length;
string itemFieldFriendlyQuery = string.Join(" OR ",
queryConditions.Select(condition =>
{
var conditionA = condition.Split(new string[] { " = " }, StringSplitOptions.None);
var left = conditionA[0];
var leftA = left.Split('.');
string objectName = leftA[0];
string objectProperty = leftA[1];
var right = conditionA[1];
return string.Format("ObjectName = '{0}' AND ObjectProperty = '{1}' AND ObjectValue = {2}",
objectName, objectProperty, right);
}
).ToArray());
MessageBox.Show(itemFieldFriendlyQuery);
// outputs: "ObjectName = 'Company' AND ObjectProperty = 'CompanyName' AND ObjectValue = 'ABC Corp' OR ObjectName = 'Product' AND ObjectProperty = 'ProductCode' AND ObjectValue = 'XYZ001'"
bool isValid = fieldItemList.AsQueryable().Where(itemFieldFriendlyQuery).Count() == conditionsCount;
MessageBox.Show(isValid.ToString());
}
Consider using the FileHelpers library to parse your file directly into regular .NET domain objects. I believe this, combined with your Dynamic Linq approach, will provide you with the syntax that you're looking for.
You could have them store it in the database as C# code expression, and then use the CodeDom classes to parse and compile it into a type which exposes a method which will run the comparison.
That will create a lot of temp assemblies, which would be a PITA to manage, IMO.
Rather, I'd still use the C# notation for expression syntax, but instead of compiling it into code, create a lambda expression dynamically (through the Expression class) and then compile into a delegate that takes all the parameters (you will need a mapping mechanism here) and then just call it using your created types.
As an aside: why class instead of struct?
Value types for data, reference types for behaviour.
For your FieldItem class, couldn't you create a property where you can inject a Predicate delegate method, such that:
class FieldItem
{
public string ObjectName {get; set;}
public string ObjectProperty {get; set;}
public string ObjectValue {get; set;}
public Predicate<FieldItem> EvalMethod { private get; set; }
public FieldItem()
{
}
public bool Evaluate()
{
return EvalMethod(this);
}
}
For constructing your FieldItem object then, you could use the following:
new FieldItem { ObjectName = "SomeObject",
ObjectProperty = "SomeProperty",
ObjectValue = "SomeValue",
EvalMethod = GetEvalMethodFor("SomeObject")
}
Of course you will have to have a parser of some sort (GetEvalMethodFor method above) to translate your validationRule string into a boolean evaluation method.
In your collection, you can then call the Evaluate() within, say, a foreach loop.
UPDATE
If you need to evaluate each and every object in the list, you can do this:
bool areAllItemsValid = true;
foreach (var f in fieldItemList)
{
areAllItemsValid = areAllItemsValid && f.Evaluate();
if (areAllItemsValid)
{
continue;
}
else
{
return false;
}
}
return true;
You can opt to inherit List<FieldItem> and make an EvaluateAll() method to contain the above, or declare it as a method in a separate object altogether.
Related
In C#, I want to replace the string Placeholder with Object Properties using Reflection
string formula = "{\"Name\": \"{{Name}}\", \"Email\": \"{{Email}}\" }";
Student student = new Student();
student.Name = "Parker";
student.Email = "Parker#xyz.com";
student.Address = "Mark Avenue";
var result1 = GenerateJson(formula, student);
//Output : "{\"Name\": \"Parker\", \"Email\": \"Parker#xyz.com\" }"
student.Name = "Royal";
student.Email = "Royal#xyz.com";
student.Address = "Cross Lane";
var result2 = GenerateJson(formula, student);
//Output : "{\"Name\": \"Royal\", \"Email\": \"Royal#xyz.com\" }"
public string GenerateJson(string formula, Student student)
{
string result = "";
//logic for replacing the Placeholder woth object properties
return result;
}
class Student
{
public string Name { get; set; }
public string Email { get; set; }
public string Address { get; set; }
}
If you really don't want or cannot use Json.NET than you can try solution below
public string GenerateJson(string formula, Student student)
{
return Regex.Replace(formula, #"\{\{(\w+)\}\}", match => typeof(Student).GetProperty(
match.Groups[1].ToString())?.GetValue(student)?.ToString());
}
You can deserialize it to ExpandoObject (IDictionary<string,object>). Then compare property names with the known type. If there is match between Dictionary's key and student's propertyName. Replace ExpandoObject's Value with Student's property's value. After all, serialize it to json.
Here it is,
public string GenerateJson(string formula, Student student)
{
IDictionary<string, object> templateValues = JsonConvert.DeserializeObject<IDictionary<string, object>>(formula);
PropertyInfo[] sourceProperty = typeof(Student).GetProperties();
foreach (var item in sourceProperty)
{
KeyValuePair<string,object> value = templateValues.FirstOrDefault(x=> x.Key == item.Name);
if (value.Key != null)
{
templateValues[item.Name] = item.GetValue(student);
}
}
return JsonConvert.SerializeObject(templateValues);
}
It looks like the actual problem is retrieving the value of specific properties to generate an API signature. It's unclear if the signature to sign really needs to be a JSON string or not.
The easiest way is to create an anonymous type with the necessary properties and serialize it, eg :
var payload=JsonConvert.Serialize(new {student.Name,student.Email});
This is far faster than any reflection code and allocates a single extra object only. If you want to use an API with a lot of different request types, it pays to use a code generator or in C# 9, a source generator to generate such calls.
It's possible (but slow) to use reflection to retrieve specific properties, eg with :
var dict=typeof(Student).GetProperties()
.Where(prop=>myProps.Contains(prop.Name))
.ToDictionary(prop=>prop.Name,prop=>prop.GetValue(student));
var json=JsonConvert.Serialize(dict);
A JSON object is actually a dictionary, so serializing a dictionary behaves similarly to serializing an object with the same properties.
Reflection is relatively expensive though, so it's a good idea to cache the PropertyInfo objects you want and reuse them:
Dictionary<Type,PropertyInfo[]> _properties=new Dictionary<Type,PropertyInfo[]>();
...
string GenerateJson<T>(T item)
{
PropertyInfo[] props;
if (!_properties.TryGetValue(typeof(T),out props))
{
props=typeof(Student).GetProperties()
.Where(prop=>myProps.Contains(prop.Name))
.ToArray();
}
var dict=props.ToDictionary(prop=>prop.Name,prop=>prop.GetValue(item));
return JsonConvert.Serialize(dict);
}
I'm trying to determine the MemberExpression of all the parts of a structure with the use of reflection. These are some of the objects to illustrate the issue:
public class Entity
{
public Part FirstPart { get; set; }
}
public class Part
{
public int Id { get; set; }
}
public class SubPart : Part
{
public int ExtraProperty { get; set; }
}
The function I used to determine the MemberExpression of every component, works fine for the following object structure:
Entity entity = new Entity() { FirstPart = new Part() { Id = 1 } };
The function is:
var param = Expression.Parameter(entity.GetType());
String[] childProperties = ("FirstPart.Id").Split('.');
var propExpression = Expression.PropertyOrField(param, childProperties[0]);
for (int i = 1; i < childProperties.Length; i++)
{
propExpression = Expression.PropertyOrField(propExpression, childProperties[i]);
}
But this doesn't work for the following, due to inheritance:
Entity entity = new Entity() { FirstPart = new SubPart() { ExtraProperty = 1 } };
In order to retrace the properties we need to change the path to "FirstPart.ExtraProperty":
var param = Expression.Parameter(entity.GetType());
String[] childProperties = ("FirstPart.ExtraProperty").Split('.');
var propExpression = Expression.PropertyOrField(param, childProperties[0]);
for (int i = 1; i < childProperties.Length; i++)
{
propExpression = Expression.PropertyOrField(propExpression, childProperties[i]);
}
The error message states that: 'ExtraProperty' is not a member of Part. Does anyone have an idea how to overcome this issue?
You can't. Think of expressions as code, which is compiled at runtime instead of compile time. There is no magic and similar rules apply (expressions are low level and more restrictive, so many syntactic sugar which are available at C# code level are not available in expressions). Saying that, since entity.FirstPart.ExtraProperty is not valid in C# code, it won't be valid in expressions as well.
You could insert explicit cast - but then you assume that instance will actually be of type SubPart, so why don't you define member FirstPart of type SubPart instead of Part. Or you could create type test logic using TypeIs expression and then cast the same way as you would in C# code.
EDIT:
After rereading your problem, I see that what you actually are trying to implement is property walker over arbitrary objects. So TypeIs expression will not help you here since it requires that type, which you are testing against, is known at compile time. But in your case there can be arbitrary class derived from Part in FirstPart member with arbitrary additional properties. In this case, there is no other option, but to evaluate each property access one by one and retrieve actual type from intermediate values. For example:
Entity entity = new Entity() { FirstPart = new SubPart() { ExtraProperty = 1 } };
object currentObjectInChain = entity;
String[] childProperties = ("FirstPart.ExtraProperty").Split('.');
foreach (var property in childProperties)
{
if (currentObjectInChain == null)
{
throw new ArgumentException("Current value is null");
}
var type = currentObjectInChain.GetType();
var param = Expression.Parameter(type);
var lambda = Expression.Lambda(
Expression.PropertyOrField(param, property),
param).Compile(); // cache based on type and property name
currentObjectInChain = lambda.DynamicInvoke(currentObjectInChain);
}
At the end of loop currentObjectInChain will hold your value.
My API response will return a list of JSON objects and I need to verify the order of the list, so I write a function as follows. But I got a problem for the LINQ order by sentence, it only works when I specify the actual field, but I need pass this field name as a parameter. so something like
var expectedList = jObjList.OrderBy(x => x.parameterFieldName.ToString());
please give me some suggestions, many thanks.
public void VerifyOrderBy(string jsonString, string parameterFieldName)
{
List<dynamic> jObjList = JsonConvert.DeserializeObject<List<dynamic>>(jsonString);
var expectedList = jObjList.OrderBy(x => x.LastName.ToString());
Assert.IsTrue(expectedList.SequenceEqual(jObjList));
}
the JSON string looks like follows
[
{
"FirstName": "w3pCj",
"LastName": "mSJOV",
"IsDeleted": false
},
{
"FirstName": "rMnH7",
"LastName": "rMnH7",
"IsDeleted": false
},
{
"FirstName": "Jia",
"LastName": "Yu",
"IsDeleted": false
}
]
You can use the nameof() operator keyword like this:
jObjList.OrderBy(x => nameof(x.LastName));
UPDATE #1
Let's say we have a Person class:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsDeleted { get; set; }
}
Let's say we have a list of people:
var people =
JsonConvert
.DeserializeObject<List<Person>>(
File.ReadAllText("data.json", Encoding.UTF8)
);
We can have a parameter that will contain the property name we want to order by:
string parameterName = nameof(Person.LastName); // or simply "LastName"
We get a reference to that property:
PropertyInfo orderByProperty =
typeof(Person)
.GetProperties()
.SingleOrDefault(property => property.Name == parameterName);
Now we can order by the selected property:
var result = people.OrderBy(person => orderByProperty.GetValue(person)).ToList();
Please note:
Of course you should check if the orderByProperty is not null. :)
This will work only on in-memory objects (LINQ-to-Objects) but not on a DB set (LINQ-to-SQL, EF)
Do not forget to add the required using statement to be able to get the PropertyInfo:
using System.Reflection;
UPDATE #2
If you have such a simple json structure and you want to use dynamic objects for ordering then you can achieve it like this:
var people =
JsonConvert
.DeserializeObject<List<dynamic>>(
File.ReadAllText("data.json", Encoding.UTF8)
);
string parameterName = "LastName";
var result =
people
.OrderBy(person =>
{
var personObject = person as JObject;
var propertyValueObject = personObject.GetValue(parameterName) as JValue;
return propertyValueObject.Value;
})
.ToList();
Although it works I would prefer UPDATE #1 solution. :)
Here is an implementation with custom comparer. This allows you to pass any property name:
public class JObjComp<T> : IComparer<T>
{
private string _field;
public JObjComp(string field)
{
_field = field;
}
int IComparer<T>.Compare(T a, T b)
{
//this is bit flimsy but as we know that passed object is
//a dynamic, should work
dynamic aa=(dynamic)a;
dynamic bb=(dynamic)b;
return string.Compare(aa[_field].ToString(), bb[_field].ToString());
}
}
Now use our custom comparer:
List<dynamic> jObjList = JsonConvert.DeserializeObject<List<dynamic>>(jstr);
jObjList.Sort(new JObjComp<dynamic>(field));
The list is sorted insitu, so you can assert using jObjList itself.
I am creating a custom reporting tool that allows the user to enter a list of criteria.
public partial class CustomReportCriteria
{
public int Id { get; set; }
public int ReportId { get; set; }
public string Operator { get; set; }
public string CriteriaValue { get; set; }
public string CriteriaType { get; set; }
public string CriteriaField { get; set; }
public string PropertyType { get; set; }
public virtual CustomReport CustomReport { get; set; }
}
I need to utilize these fields to create dynamic queries on my database. For example:
Select BidYear From FWOBid Where BidYear = 2015
// ^ this would look like this instead
Select CriteriaField From PropertyType Where CriteriaField [Operator] CriteriaValue
However, I may not always be querying the same table, and in most cases the query will require joins on other tables.
I already have generic methods for the where clauses:
public static IQueryable<T> Filter<T>(IQueryable<T> query, string propertyName, string propertyValue, string op)
{
PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName);
return Filter<T>(query, propertyInfo, propertyValue, op);
}
public static IQueryable<T> Filter<T>(IQueryable<T> query, PropertyInfo propertyInfo, string propertyValue, string op)
{
ParameterExpression e = Expression.Parameter(typeof(T), "e");
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
UnaryExpression c = GetExpressionByType(propertyValue, propertyInfo.PropertyType);
BinaryExpression b = null;
if (op == "=")
{
b = Expression.Equal(m, c);
}
else if (op == "!=")
{
b = Expression.NotEqual(m, c);
}
else if (op == ">")
{
b = Expression.GreaterThan(m, c);
}
else if (op == "<")
{
b = Expression.LessThan(m, c);
}
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(b, e);
return query.Where(lambda);
}
The approach I was envisioning was something like this:
foreach (var crit in report.CustomReportCriterias)
{
var objectName = crit.PropertyType;
var value = crit.CriteriaValue;
var type = crit.CriteriaType;
var field = crit.CriteriaField;
var op = crit.Operator;
// how to dynamically get a generic collection ?
var queryable = _context.Set<objectName>();
var results = Filter<objectName>(queryable, field, value, op);
// would then do joins if needed
}
But, I'm struggling with the initial steps of retrieving a queryable from the db based on a string, and then I'm lost as how to join these results after.
Using Entity Framework you can execute a query directly and have the results map to an entity like this.
var results = db.ExecuteStoreQuery<YourEntity>(
"SELECT * FROM YourEntities WHERE SomeColumn = #param1",
param1);
Have a look at https://dynamiclinq.codeplex.com/releases/view/109593. This library extends the Linq library with a System.Linq.Dynamic namespace that provides string-based overloads to common query methods, so you can dynamically build string-based criteria for filtering and grouping. In addition, the one in my link is one better than the version available through NuGet, because it allows you to define the entire query, including the record type to retrieve, in Linq syntax as one string (see the Documentation tab for one example; it uses a static typeof expression to provide a Type to parse the results into, but you can reflectively create generic types with Type.MakeGenericType()).
The problem's going to be getting back to a statically-defined type; once you go dynamic, you tend to have to stay dynamic, using GetProperties() to enumerate data fields and GetValue/SetValue to manipulate them. There are a few tricks, typically requiring the use of a set of concretely-typed overloads that force the runtime to examine the true type of the object, then once the object's passed into one of those overloads you're back in static world again.
I have such class:
public class SomeClass
{
public string Text1 { get; set; }
public string Text2 { get; set; }
public int Number { get; set; }
}
And I have list of this classes objects:
List<SomeClass> myList = new List<SomeClass>();
I want to query this list using LINQ (lambda syntax):
var result = myList.Where(obj => obj.Text1 == "SomeString");
Is there any way to pass property(eg. by string name), by which I want this LINQ query to be performed? In this example, I search by Text1 property, but let's say I want to invoke this search dynamically on Text1 or Text2 (determined in runtime). I want to be able to pass property name, on which this search is performed, and check whether this property is string, so that I'm sure this search CAN be performed first.
Is that possible? I know Reflections and Expressions have something to do about it, but I don't know them very well.
Thanks
The approach using Reflection:
var result = myList.Where(obj => obj.GetType()
.GetProperty("Text1")
.GetValue(obj)
.Equals("SomeString"));
With this way you can change from "Text1" to "Text2" property.
Another approach you can use dynamic linq:
var result = myList.AsQueryable().Where("Text1=#0", "SomeString");
Dynamic LINQ is also available via nuget.
You could use expression-trees?
string memberName = "Text1", value = "SomeString";
var p = Expression.Parameter(typeof(SomeClass), "obj");
var predicate = Expression.Lambda<Func<SomeClass, bool>>(
Expression.Equal(
Expression.PropertyOrField(p, memberName),
Expression.Constant(value,typeof(string))
), p);
var result = myList.AsQueryable().Where(predicate);
or alternative for the last line:
var result = myList.Where(predicate.Compile());