Use LINQ expression to Update Property of object - c#

So I have a situation where I need to dynamically update properties of my object based on values contained. In the case below, I need to update the value with replacing the first two characters of the current value with a different string if condition is true.
PersonDetail.EvaluateConditionalRule("ID",
"((ID.Length > Convert.ToInt32(#0) ) AND ID.Substring(Convert.ToInt32(#1), Convert.ToInt32(#2)) == #3 )",
new[] { "1", "0", "2", "SS" }, " ID = (#0 + ID.Substring(Convert.ToInt32(#1))) " , new[] { "98", "2" });
public static void EvaluateConditionalRule(this PersonDetail Detail, String PropertyToEvaluate,
String ConditionalExpression, String[] parameters, String IfTrueExpression,String[] IfTrueExpreassionparameters )
{
var property = Detail.GetType().GetProperties().Where(x => x.Name == PropertyToEvaluate).FirstOrDefault();
if (property == null)
throw new InvalidDataException(String.Format("Please specify a valid {0} property name for the evaluation.", Detail.GetType()));
//put together the condition like so
if (new[] { Detail }.AsQueryable().Where(ConditionalExpression, parameters).Count() > 0 && IfTrueExpression != null)
{
var result = new[] { Detail }.AsQueryable().Select(IfTrueExpression, IfTrueExpreassionparameters);
//Stuck Here as result does not contain expected value
property.SetValue( Detail,result , null);
}
}
Essentially what I want is, to be able to execute expressions fed this, and I don't think I have the format right for the substring replace expression to correctly evaluate. What I want from the above is something like
ID = "98"+ ID.Substring(2);
Any help will be appreciated. Thanks

Please give us some information on why you need the updates to be this dynamic. Do you want the user to enter the condition strings by hand?
About your code:
Your selector string is wrong. In the LINQ Dynamic Query Library there is a special syntax for it. Please look that up in the Documentation (see paragraph Expression Language and Data Object Initializers) provided with the sample: http://msdn.microsoft.com/en-US/vstudio/bb894665.aspx
I wrote a small sample:
var id = new string[] { "SS41231" }.AsQueryable();
// *it* represents the current element
var res = id.Where("it.Length > #0 AND it.Substring(#1, #2) = #3", 1, 0, 2, "SS"); // Save the result, don't throw it away.
if (res.Any())
{
// Line below in normal LINQ: string newID = res.Select(x => "98" + x.Substring(2)).First();
string newId = res.Select("#0 + it.Substring(#1)", "98", 2).Cast<string>().First();
Console.WriteLine(newId);
}
Please write some feedback.
Greetings.

Not sure what exactly you mean saying "dynamically" but i suppose the approach with lambdas does not fit you:
static void Main(string[] args) {
User user = new User { ID = 5, Name = "Test" };
SetNewValue(user, u => u.Name, s => s.StartsWith("T"), s => s + "123");
}
static void SetNewValue<TObject, TProperty>(TObject obj, Func<TObject, TProperty> propertyGetter, Func<TProperty, bool> condition, Func<TProperty, TProperty> modifier) {
TProperty property = propertyGetter(obj);
if (condition(property)) {
TProperty newValue = modifier(property);
//set via reflection
}
}
So I would recommend you using Expression trees which allow you to build any runtime construction you like, for part of your example
var exp = Expression.Call(Expression.Constant("test"), typeof(string).GetMethod("Substring", new[] { typeof(int) }), Expression.Constant(2));
Console.WriteLine(Expression.Lambda(exp).Compile().DynamicInvoke()); //prints "st"
However if you want to use strings with raw c# code as expressions check the CSharpCodeProvider class

Related

How to replace placeholders within email template dynamically

Is there a good way to replace placeholders with dynamic data ?
I have tried loading a template and then replaced all {{PLACEHOLDER}}-tags, with data from the meta object, which is working.
But if I need to add more placeholders I have to do it in code, and make a new deployment, so if it is possible I want to do it through the database, like this:
Table Placeholders
ID, Key (nvarchar(50), Value (nvarchar(59))
1 {{RECEIVER_NAME}} meta.receiver
2 {{RESOURCE_NAME}} meta.resource
3 ..
4 .. and so on
the meta is the name of the parameter sent in to the BuildTemplate method.
So when I looping through all the placeholders (from the db) I want to cast the value from the db to the meta object.
Instead of getting "meta.receiver", I need the value inside the parameter.
GetAllAsync ex.1
public async Task<Dictionary<string, object>> GetAllAsync()
{
return await _context.EmailTemplatePlaceholders.ToDictionaryAsync(x => x.PlaceholderKey, x => x.PlaceholderValue as object);
}
GetAllAsync ex.2
public async Task<IEnumerable<EmailTemplatePlaceholder>> GetAllAsync()
{
var result = await _context.EmailTemplatePlaceholders.ToListAsync();
return result;
}
sample not using db (working))
private async Task<string> BuildTemplate(string template, dynamic meta)
{
var sb = new StringBuilder(template);
sb.Replace("{{RECEIVER_NAME}}", meta.receiver?.ToString());
sb.Replace("{{RESOURCE_NAME}}", meta.resource?.ToString());
return sb.ToString();
}
how I want it to work
private async Task<string> BuildTemplate(string template, dynamic meta)
{
var sb = new StringBuilder(template);
var placeholders = await _placeholders.GetAllAsync();
foreach (var placeholder in placeholders)
{
// when using reflection I still get a string like "meta.receiver" instead of meta.receiver, like the object.
// in other words, the sb.Replace methods gives the same result.
//sb.Replace(placeholder.Key, placeholder.Value.GetType().GetField(placeholder.Value).GetValue(placeholder.Value));
sb.Replace(placeholder.Key, placeholder.Value);
}
return sb.ToString();
}
I think it might be a better solution for this problem. Please let me know!
We have solved similar issue in our development.
We have created extension to format any object.
Please review our source code:
public static string FormatWith(this string format, object source, bool escape = false)
{
return FormatWith(format, null, source, escape);
}
public static string FormatWith(this string format, IFormatProvider provider, object source, bool escape = false)
{
if (format == null)
throw new ArgumentNullException("format");
List<object> values = new List<object>();
var rewrittenFormat = Regex.Replace(format,
#"(?<start>\{)+(?<property>[\w\.\[\]]+)(?<format>:[^}]+)?(?<end>\})+",
delegate(Match m)
{
var startGroup = m.Groups["start"];
var propertyGroup = m.Groups["property"];
var formatGroup = m.Groups["format"];
var endGroup = m.Groups["end"];
var value = propertyGroup.Value == "0"
? source
: Eval(source, propertyGroup.Value);
if (escape && value != null)
{
value = XmlEscape(JsonEscape(value.ToString()));
}
values.Add(value);
var openings = startGroup.Captures.Count;
var closings = endGroup.Captures.Count;
return openings > closings || openings%2 == 0
? m.Value
: new string('{', openings) + (values.Count - 1) + formatGroup.Value
+ new string('}', closings);
},
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
return string.Format(provider, rewrittenFormat, values.ToArray());
}
private static object Eval(object source, string expression)
{
try
{
return DataBinder.Eval(source, expression);
}
catch (HttpException e)
{
throw new FormatException(null, e);
}
}
The usage is very simple:
var body = "[{Name}] {Description} (<a href='{Link}'>See More</a>)";
var model = new { Name="name", Link="localhost", Description="" };
var result = body.FormatWith(model);
You want to do it like this:
sb.Replace(placeholder.Key, meta.GetType().GetField(placeholder.Value).GetValue(meta).ToString())
and instead of meta.reciever, your database would just store receiver
This way, the placeholder as specified in your database is replaced with the corresponding value from the meta object. The downside is you can only pull values from the meta object with this method. However, from what I can see, it doesn't seem like that would be an issue for you, so it might not matter.
More clarification: The issue with what you tried
//sb.Replace(placeholder.Key, placeholder.Value.GetType().GetField(placeholder.Value).GetValue(placeholder.Value));
is that, first of all, you try to get the type of the whole string meta.reciever instead of just the meta portion, but then additionally that there doesn't seem to be a conversion from a string to a class type (e.g. Type.GetType("meta")). Additionally, when you GetValue, there's no conversion from a string to the object you need (not positive what that would look like).
As you want to replace all the placeholders in your template dynamically without replacing them one by one manually. So I think Regex is better for these things.
This function will get a template which you want to interpolate and one object which you want to bind with your template. This function will automatically replace your placeholders like
{{RECEIVER_NAME}} with values in your object.
You will need a class which contain all the properties that you want to bind. In this example by class is MainInvoiceBind.
public static string Format(string obj,MainInvoiceBind invoice)
{
try
{
return Regex.Replace(obj, #"{{(?<exp>[^}]+)}}", match =>
{
try
{
var p = Expression.Parameter(typeof(MainInvoiceBind), "");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, match.Groups["exp"].Value);
return (e.Compile().DynamicInvoke(invoice) ?? "").ToString();
}
catch
{
return "Nill";
}
});
}
catch
{
return string.Empty;
}
}
I implement this technique in a project where I hade to generates email dynamically from there specified templates. Its working good for me. Hopefully, Its solve your problem.
I updated habibs solution to the more current System.Linq.Dynamic.Core NuGet package, with small improvements.
This function will automatically replace your placeholders like {{RECEIVER_NAME}} with data from your object. You can even use some operators, since it's using Linq.
public static string Placeholder(string input, object obj)
{
try {
var p = new[] { Expression.Parameter(obj.GetType(), "") };
return Regex.Replace(input, #"{{(?<exp>[^}]+)}}", match => {
try {
return DynamicExpressionParser.ParseLambda(p, null, match.Groups["exp"].Value)
.Compile().DynamicInvoke(obj)?.ToString();
}
catch {
return "(undefined)";
}
});
}
catch {
return "(error)";
}
}
You could also make multiple objects accessible and name them.

C# Reflection: replace all occurrence of property with value in text

I have a class like
public class MyClass {
public string FirstName {get; set;}
public string LastName {get; set;}
}
The case is, I have a string text like,
string str = "My Name is #MyClass.FirstName #MyClass.LastName";
what I want is to replace #MyClass.FirstName & #MyClass.LastName with values using reflection which are assigned to objects FirstName and LastName in class.
Any help please?
If you want to generate the string you can use Linq to enumerate the properties:
MyClass test = new MyClass {
FirstName = "John",
LastName = "Smith",
};
String result = "My Name is " + String.Join(" ", test
.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(property => property.CanRead) // Not necessary
.Select(property => property.GetValue(test)));
// My Name is John Smith
Console.Write(result);
In case you want to substitute within the string (kind of formatting), regular expressions can well be your choice in order to parse the string:
String original = "My Name is #MyClass.FirstName #MyClass.LastName";
String pattern = "#[A-Za-z0-9\\.]+";
String result = Regex.Replace(original, pattern, (MatchEvaluator) ((match) =>
test
.GetType()
.GetProperty(match.Value.Substring(match.Value.LastIndexOf('.') + 1))
.GetValue(test)
.ToString() // providing that null can't be returned
));
// My Name is John Smith
Console.Write(result);
Note, that in order to get instance (i. e. not static) property value you have to provide the instance (test in the code above):
.GetValue(test)
so #MyClass part in the string is useless, since we can get type directly from instance:
test.GetType()
Edit: in case that some properties can return null as value
String result = Regex.Replace(original, pattern, (MatchEvaluator) ((match) => {
Object v = test
.GetType()
.GetProperty(match.Value.Substring(match.Value.LastIndexOf('.') + 1))
.GetValue(test);
return v == null ? "NULL" : v.ToString();
}));
First of all, I'd advice against using reflection when other options such as string.Format is possible. Reflection can make your code less readable and harder to maintain. Either way, you could do it like this:
public void Main()
{
string str = "My Name is #MyClass.FirstName #MyClass.LastName";
var me = new MyClass { FirstName = "foo", LastName = "bar" };
ReflectionReplace(str, me);
}
public string ReflectionReplace<T>(string template, T obj)
{
foreach (var property in typeof(T).GetProperties())
{
var stringToReplace = "#" + typeof(T).Name + "." + property.Name;
var value = property.GetValue(obj);
if (value == null) value = "";
template = template.Replace(stringToReplace, value.ToString());
}
return template;
}
This should require no additional changes if you want to add a new property to your class and update your template string to include the new values. It should also handle any properties on any class.
Using Reflection you can achieve it as shown below
MyClass obj = new MyClass() { FirstName = "Praveen", LaseName = "Paulose" };
string str = "My Name is #MyClass.FirstName #MyClass.LastName";
string firstName = (string)obj.GetType().GetProperty("FirstName").GetValue(obj, null);
string lastName = (string)obj.GetType().GetProperty("LaseName").GetValue(obj, null);
str = str.Replace("#MyClass.FirstName", firstName);
str = str.Replace("#MyClass.LastName", lastName);
Console.WriteLine(str);
You are first finding the relevant Property using GetProperty and then its value using GetValue
UPDATE
Based on further clarification requested in the comment
You could use a regex to identify all placeholders in your string. i.e. #MyClass.Property. Once you have found them you can use Type.GetType to get the Type information and then use the code shown above to get the properties. However you will need the namespace to instantiate the types.
Although this is five years old, I found it useful.
Here is my adaptation of Dmitry Bychenko's that also dives into any JSON fields as well.
Only works on one level, but I'll make it recursive at some point.
Regex _replacementVarsRegex = new Regex(#"\{\{([\w\.]+)\}\}", RegexOptions.Compiled);
var result = "string with {{ObjectTypeName.PropertyName}} and json {{ObjectTypeName.JsonAsStringProperty.JsonProperty}}";
string ReplaceFor(object o) => _replacementVarsRegex.Replace(result, match =>
{
if (o == null) return match.Value;
var typeName = match.Value.Substring(0, match.Value.IndexOf('.')).TrimStart('{');
var type = o.GetType();
if (typeName != type.Name) return match.Value;
var name = match.Value.Substring(match.Value.LastIndexOf('.') + 1).TrimEnd('}');
var propertyInfo = type.GetProperty(name);
var value = propertyInfo?.GetValue(o);
var s = value?.ToString();
if (match.Value.Contains(".Metadata."))
{
var jsonProperty = type.GetProperty("Metadata");
var json = jsonProperty?.GetValue(o)?.ToString() ?? string.Empty;
if (!string.IsNullOrWhiteSpace(json))
{
// var dObj = JsonConvert.DeserializeObject(json);
var jObj = JObject.Parse(json);
var val = jObj[name].Value<string>();
s = val;
}
}
return s ?? match.Value;
});
result = ReplaceFor(object1);
result = ReplaceFor(object2);
//...
result = ReplaceFor(objectn);
//remove any not answered, if desired
result = _replacementVarsRegex.Replace(result, string.Empty);
improvement suggestions welcome!
Working on a project recently I ended up needing something similar. The requirement was to use a placeholder format that wouldn't naturally occur in our text #obj.property may have so #obj.property# was used. Also, handling of cascading tokens (tokens depending on tokens) and handling of data coming from IEnumerable, such as: ["Cat", "Dog", "Chicken"] needing to displayed in the text as "Cat, Dog, Chicken".
I've created a library and have a NuGet package to get this functionality into your code a little quicker.

Use a numeric value in a linq dynamic query string

I am trying to make a dynamic linq query that will check for values based on a string.
First of all, here's the query:
objQry = from o in m_Db.OBJECTS.Where(whereConditions)
select o;
if(!objQry.Any())
{
return null;
}
The whereConditions variable is a string I build and pass as parameter to find out the values I need. Here's examples of valid string:
OBJ_NAME == \"Sword\" and OBJ_OWNER == \"Stan\"
This will return any item whose name is "Sword" and owner is "Stan;
OBJ_COLOR == \"Blue\" OR OBJ_COLOR == \"Red\"
This will return any item which color is either blue or red.
Up to there, I'm fine, but now I have a problem: I need to check a decimal field. So I've tried this string:
OBJ_NUMBER == 1
But the query returns null even if there are objects which OBJ_NUMBER value is 1. It's a decimal. How can I indicate the query that they need to check for a decimal value?
**** EDIT ****
I have tried to "modify" the value passed so that it looks like this:
"CARD_NUMBER == Convert.ToDecimal(1)"
And now I have a different kind of error telling me this:
LINQ to Entities does not recognize the method 'System.Decimal ToDecimal(Int32)' method, and this method cannot be translated into a store expression.
Any clues anyone? I'm still looking for a way to do this. Thanks!
EDIT 2
You can get an example of how my code is shaped by looking at this question.
Let's come back at this problem. I want to check decimal values. Let's say that OBJ_NUMBER is a decimal field.
Using Dynamic Linq, I tried to read the decimal field. Say that I want to get each object which number is 1.27. The whereConditions field would then be shaped like this:
OBJ_NUMBER == 1.27
But then I would get an Invalid real literal '1.27' error. I don't know why.
So I have tried Gert Arnold's solution and done this instead:
decimal bDecimal = decimal.Parce(valueToParse);
param = new ObjectParameter("cardNumber", typeof(decimal)) { Value = bDecimal };
valuesToUse.Add("CARD_NUMBER == #cardNumber");
listParams.Add(param);
But I ended up having 2 problems:
The first problem is that my whereConditions string is shaped this way:
CARD_NUMBER == #cardNumber
But I get the following error:
No property or field 'cardNumber' exists in type 'CARD'
Leading me to believe that it cannot make the link between the object parameter and the string used to do the query.
As you can see, I have a list of Params. This is because I cannot know for sure how many parameters the user will chose. So each time the user enters a new search field, I have to create a new ObjectParameter and store it in a list. Here's how I try to do the thing after:
ObjectParameter[] arrayParameters = listParams.ToArray();
// Convert the list to an array
And then, when I try to make the query:
cardQry = from c in mDb.CARD.Where(whereConditions, arrayParameters)
select c;
But to no avail.
RESULTS
Based on the answered question below, I have developped something "awful", yet functional.
First of all, I ignore every decimal fields because I could never reach them with dynamic linq. Instead, I do this:
var valuesToParse = keyValuePair.Value.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
// Here I parse the value and, if that's the case, the symbol.
decimal baseValue = decimal.Parse(valuesToParse[0]);
if (valuesToParse.Count() > 1)
{
string baseMethod = valuesToParse[1];
if (baseMethod == ">" || baseMethod == ">=")
{
if (baseMethod == ">=")
{
baseValue--;
}
// The list is actually like this: Dictionary<string, object> list = new Dictionary<string, object>();
list.Add("low", baseValue);
// I kind of activate a tag telling me that the user is looking for a higher value.
cardHigher = true;
}
else
{
if (baseMethod == "<=")
{
baseValue++;
}
list.Add("low", baseValue);
cardLower = true;
}
}
else
{
//lowParam = new ObjectParameter("dec", typeof(decimal)) { Value = baseValue };
list.Add("low", baseValue);
}
cardNumberActivated = true;
At the end, when I get the list of objects, I do this:
if (list.Count > 0)
{
(example)
if (cardNumberActivated)
{
if (cardHigher)
{
q = mDb.CARD.Where("CARD_NUMBER >= #0", list["low"]).ToList();
}
else if (cardLower)
{
q = mDb.CARD.Where("CARD_NUMBER <= #0", list["low"]).ToList();
}
else
{
q = mDb.CARD.Where("CARD_NUMBER == #0", list["low"]).ToList();
}
}
}
// Here we get the orinalData with the basic filters.
listToReturn.AddRange(cardQry);
if (q != null)
{
//listToReturn.AddRange(q);
for (int i = 0; i < listToReturn.Count; i++)
{
var priceList1 = listToReturn[i];
if (!q.Any(_item => _item.CARD_NUMBER == priceList1.CARD_NUMBER))
{
listToReturn.RemoveAt(i);
i--;
}
}
}
And it works. This is not an elegant way to make it work, but I can validate the fields the way I wanted, and for this, I am thankful at last.
You should not build a query string with inline predicate values. Use parameters in stead. Then will also be able to specify the type:
var whereConditions= "it.CARD_NUMBER = #cardNumber";
var param = new ObjectParameter("cardNumber", typeof(decimal)) { Value = 1 };
objQry = from o in m_Db.OBJECTS.Where(whereConditions, param);
Edit
I don't know what doesn't work in your code. Here's just a random piece of working code derived from one of my own projects:
var param1 = new ObjectParameter("dec", typeof(decimal)) { Value = 90000m };
var param2 = new ObjectParameter("int", typeof(int)) { Value = 90000 };
var q = ValueHolders.Where("it.DecimalValue >= #dec OR it.IntegerValue > #int",
param1, param2).ToList();
Note that param1, param2 could also be an array of ObjectParameter.

Linq query to get data from array of objects c#

public struct Parameter
{
public Parameter(string name, string type, string parenttype)
{
this.Name = name;
this.Type = type;
this.ParentType = parenttype;
}
public string Name;
public string Type;
public string ParentType;
}
Following values are stored in the array of Parameter:
Name Type ParentType
-------------------------------------------------------
composite CompositeType
isThisTest boolean
BoolValue boolean CompositeType
StringValue string CompositeType
AnotherType AnotherCompositeType CompositeType
account string AnotherCompositeType
startdate date AnotherCompositeType
I want to read this to build an xml. something like:
<composite>
<BoolValue>boolean</BoolValue>
<StringValue>string</StringValue>
<AnotherType>
<account>string</account>
<startdate>date</startdate>
</AnotherType>
<composite>
<isThisTest>boolean</isThisTest>
I am using the following logic to read the values:
foreach (Parameter parameter in parameters)
{
sb.Append(" <" + parameter.Name + ">");
//HERE: need to check parenttype and get all it's child elements
//
sb.Append("</" + parameter.Name + ">" + CrLf);
}
Is there a simpler way to read the array to get the parents and thier child? May be using LINQ? I still on .Net 3.5. Appreciate any suggestions with example code :)
You could write a little recursive method to deal with this :
IEnumerable<XElement> GetChildren ( string rootType, List<Parameter> parameters )
{
return from p in parameters
where p.ParentType == rootType
let children = GetChildren ( p.Type, parameters )
select children.Count() == 0 ?
new XElement ( p.Name, p.Type ) :
new XElement ( p.Name, children );
}
Each call builds up an Enumerable of XElements which contains the parameters whose parent has the passed in type. The select recurses into the method again finding the children for each Element.
Note that this does assume that the data is correctly formed. If two parameters has eachother as a parent you will get a Stack Overflow.
The magic is in the XElements class (Linq to Xml) that accepts enumerables of XElements to build up the tree like Xml structure.
The first call, pass null (or use default parameters if using C# 4) as the rootType. Use like :
void Main()
{
var parameters = new List<Parameter> {
new Parameter {Name = "composite", Type = "CompositeType" },
new Parameter {Name = "isThisTest", Type = "boolean" },
new Parameter {Name = "BoolValue", Type = "boolean", ParentType = "CompositeType" },
new Parameter {Name = "StringValue", Type = "string", ParentType = "CompositeType" },
new Parameter {Name = "AnotherType", Type = "AnotherCompositeType", ParentType = "CompositeType" },
new Parameter {Name = "account", Type = "string", ParentType = "AnotherCompositeType" },
new Parameter {Name = "startdate", Type = "date", ParentType = "AnotherCompositeType" }
};
foreach ( var r in GetChildren ( null, parameters ) )
{
Console.WriteLine ( r );
}
}
Output :
<composite>
<BoolValue>boolean</BoolValue>
<StringValue>string</StringValue>
<AnotherType>
<account>string</account>
<startdate>date</startdate>
</AnotherType>
</composite>
<isThisTest>boolean</isThisTest>
Edit
In response to your comment, XElement gives you two options for outputting as a string.
ToString() will output formatted Xml.
ToString(SaveOptions) allows you to specify formatted or unformatted output as well as ommitting duplicate namespaces.
I'm sure you could probably adapt the solution to use StringBuilder if you really had to, although it probably wouldn't be as elegant..
It looks like you want to use a recursive method, something like:
string GetChildren(Parameter param, string indent)
{
StringBuilder sb = new StringBuilder();
if (HasChildren(param))
{
sb.AppendFormat("{0}<{1}>{2}", indent, param.Name, Environment.NewLine);
foreach (Parameter child in parameters.Where(p => p.ParentType == param.Type))
{
sb.Append(GetChildren(child, indent + " "));
}
sb.AppendFormat("{0}</{1}>{2}", indent, param.Name, Environment.NewLine);
}
else
{
sb.AppendFormat("{0}<{1}>{2}</{1}>{3}", indent, param.Name, param.Type, Environment.NewLine);
}
return sb.ToString();
}
The method that looks to see whether a Parameter has child nodes would look like:
bool HasChildren(Parameter param)
{
return parameters.Any(p => p.ParentType == param.Type);
}
The collection parameters could be defined as an IEnumerable<Parameter> and could be implemented using a List<Parameter>.

Refactor linq statement

I have a linq expression that I've been playing with in LINQPad and I would like to refactor the expression to replace all the tests for idx == -1 with a single test. The input data for this is the result of a free text search on a database used for caching Active Directory info. The search returns a list of display names and associated summary data from the matching database rows. I want to extract from that list the display name and the matching Active Directory entry. Sometimes the match will only occur on the display name so there may be no further context. In the example below, the string "Sausage" is intended to be the search term that returned the two items in the matches array. Clearly this wouldn't be the case for a real search because there is no match for Sausage in the second array item.
var matches = new []
{
new { displayName = "Sausage Roll", summary = "|Title: Network Coordinator|Location: Best Avoided|Department: Coordination|Email: Sausage.Roll#somewhere.com|" },
new { displayName = "Hamburger Pattie", summary = "|Title: Network Development Engineer|Location: |Department: Planning|Email: Hamburger.Pattie#somewhere.com|" },
};
var context = (from match in matches
let summary = match.summary
let idx = summary.IndexOf("Sausage")
let start = idx == -1 ? 0 : summary.LastIndexOf('|', idx) + 1
let stop = idx == -1 ? 0 : summary.IndexOf('|', idx)
let ctx = idx == -1 ? "" : string.Format("...{0}...", summary.Substring(start, stop - start))
select new { displayName = match.displayName, summary = ctx, })
.Dump();
I'm trying to create a list of names and some context for the search results if any exists. The output below is indicative of what Dump() displays and is the correct result:
displayName summary
---------------- ------------------------------------------
Sausage Roll ...Email: Sausage.Roll#somewhere.com...
Hamburger Pattie
Edit: Regex version is below, definitely tidier:
Regex reg = new Regex(#"\|((?:[^|]*)Sausage[^|]*)\|");
var context = (from match in matches
let m = reg.Match(match.summary)
let ctx = m.Success ? string.Format("...{0}...", m.Groups[1].Value) : ""
select new { displayName = match.displayName, context = ctx, })
.Dump();
(I know this doesn't answer your specific question), but here's my contribution anyway:
You haven't really described how your data comes in. As #Joe suggested, you could use a regex or split the fields as I've done below.
Either way I would suggested refactoring your code to allow unit testing.
Otherwise if your data is invalid / corrupt whatever, you will get a runtime error in your linq query.
[TestMethod]
public void TestMethod1()
{
var matches = new[]
{
new { displayName = "Sausage Roll", summary = "|Title: Network Coordinator|Location: Best Avoided|Department: Coordination|Email: Sausage.Roll#somewhere.com|" },
new { displayName = "Hamburger Pattie", summary = "|Title: Network Development Engineer|Location: |Department: Planning|Email: Hamburger.Pattie#somewhere.com|" },
};
IList<Person> persons = new List<Person>();
foreach (var m in matches)
{
string[] fields = m.summary.Split('|');
persons.Add(new Person { displayName = m.displayName, Title = fields[1], Location = fields[2], Department = fields[3] });
}
Assert.AreEqual(2, persons.Count());
}
public class Person
{
public string displayName { get; set; }
public string Title { get; set; }
public string Location { get; set; }
public string Department { get; set; }
/* etc. */
}
Or something like this:
Regex reg = new Regex(#"^|Email.*|$");
foreach (var match in matches)
{
System.Console.WriteLine(match.displayName + " ..." + reg.Match(match.summary) + "... ");
}
I haven't tested this, probably not even correct syntax but just to give you an idea of how you could do it with regex.
Update
Ok, i've seen your answer and it's good that you posted it because I think i didn't explain it clearly.
I expected your answer to look something like this at the end (tested using LINQPad now, and now i understand what you mean by using LINQPad because it actually does run a C# program not just linq commands, awesome!) Anyway this is what it should look like:
foreach (var match in matches)
Console.WriteLine(string.Format("{0,-20}...{1}...", match.displayName, Regex.Match(match.summary, #"Email:(.*)[|]").Groups[1]));
}
That's it, the whole thing, take linq out of it, completely!
I hope this clears it up, you do not need linq at all.
like this?
var context = (from match in matches
let summary = match.summary
let idx = summary.IndexOf("Sausage")
let test=idx == -1
let start =test ? 0 : summary.LastIndexOf('|', idx) + 1
let stop = test ? 0 : summary.IndexOf('|', idx)
let ctx = test ? "" : string.Format("...{0}...", summary.Substring(start, stop - start))
select new { displayName = match.displayName, summary = ctx, })
.Dump();

Categories

Resources