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.
Related
Yes, I'm well aware Not to do this, but I have no choice. I'd agree that it's an XYZ issue, but since I can't update the service I have to use, it's out of my hands. I need some help to save some time, maybe learn something handy in the process.
I'm looking to map a list of models (items in this example) to what is essentially numbered variables of a service I'm posting to, in the example, that's the fields a part of new 'newUser'.
Additionally, there may not be always be X amount items in the list (On the right in the example), and yet I have a finite amount (say 10) of numbered variables from 'newUser' to map to (On the left in the example). So I'll have to perform a bunch of checks to avoid indexing a null value as well.
Current example:
if (items.Count >= 1 && !string.IsNullOrWhiteSpace(items[0].id))
{
newUser.itemId1 = items[0].id;
newUser.itemName1 = items[0].name;
newUser.itemDate1 = items[0].date;
newUser.itemBlah1 = items[0].blah;
}
else
{
// This isn't necessary, but this effectively what will happen
newUser.itemId1 = string.Empty;
newUser.itemName1 = string.Empty;
newUser.itemDate1 = string.Empty;
newUser.itemBlah1 = string.Empty;
}
if (items.Count >= 2 && !string.IsNullOrWhiteSpace(items[1].id))
{
newUser.itemId2 = items[1].id;
newUser.itemName2 = items[1].name;
newUser.itemDate2 = items[1].date;
newUser.itemBlah2 = items[1].blah;
}
// Removed the else to clean it up, but you get the idea.
// And so on, repeated many more times..
I looked into an example using Dictionary, but I'm unsure of how to map that to the model without just manually mapping all the variables.
PS: To all who come across this question, if you're implementing numbered variables in your API, please don't- it's wildly unnecessary and time consuming.
As an alternative to fiddling with the JSON, you could get down and dirty and use Reflection.
Given the following test data:
const int maxItemsToSend = 3;
class ItemToSend {
public string
itemId1, itemName1,
itemId2, itemName2,
itemId3, itemName3;
}
ItemToSend newUser = new();
record Item(string id, string name);
Item[] items = { new("1", "A"), new("2", "B") };
Using the rules you set forth in the question, we can loop through the projected fields as so:
// If `itemid1`,`itemId2`, etc are fields:
var fields = typeof(ItemToSend).GetFields();
// If they're properties, replace GetFields() with
// .GetProperties(BindingFlags.Instance | BindingFlags.Public);
for(var i = 1; i <= maxItemsToSend; i++){
// bounds check
var item = (items.Count() >= i && !string.IsNullOrWhiteSpace(items[i-1].id))
? items[i-1] : null;
// Use Reflection to find and set the fields
fields.FirstOrDefault(f => f.Name.Equals($"itemId{i}"))
?.SetValue(newUser, item?.id ?? string.Empty);
fields.FirstOrDefault(f => f.Name.Equals($"itemName{i}"))
?.SetValue(newUser, item?.name ?? string.Empty);
}
It's not pretty, but it works. Here's a fiddle.
So I made a windows form which has a search textbox that will return the parts of string that you have entered in the datagrid. However, in my attempt to code this following event. The datagrid shows boolean instead.
Which parts of the code is making all these result turns boolean and how can i fix this?
private void txtSearch_TextChanged(object sender, EventArgs e)
{
this.dataGridView1.DataSource = null;
this.dataGridView1.Rows.Clear();
using (var context = new edeappEntities1())
{
var data = context.bookingorders
.Join(
context.addressbooks,
booking => booking.addrID,
address => address.addrID,
(booking, address) => new
{
accID = booking.accID.Contains(txtSearch.Text),
bookId = booking.bookingID.Contains(txtSearch.Text),
companyName = address.companyName.Contains(txtSearch.Text),
address = address.addressLn1.Contains(txtSearch.Text) || address.addressLn2.Contains(txtSearch.Text) ||
address.addressLn3.Contains(txtSearch.Text),
region = address.region.Contains(txtSearch.Text),
postcode = address.postcode.Contains(txtSearch.Text),
contact = address.contectName.Contains(txtSearch.Text),
phone = address.phoneNo.Contains(txtSearch.Text),
fax = address.faxNo.Contains(txtSearch.Text),
telex = address.telexNo.Contains(txtSearch.Text),
pickupTime = booking.pickupDate.Contains(txtSearch.Text)
|| booking.pickupTime.Contains(txtSearch.Text)
}
).ToList();
foreach (var db in data)
{
dataGridView1.Rows.Add(db.accID, db.bookId, db.companyName, db.address, db.region,
db.postcode, db.contact, db.phone, db.fax, db.telex, db.pickupTime);
}
}
}
My modelling structure: model1.edmx
Search result is boolean: link
You are getting a Boolean result in all the columns because you are creating a new anonymous type and assigning the result of string.Contains() method to each property in that new anonymous type and string.Contains() returns a Boolean(bool).
For example, if I do this:
string str = "Hello!"
bool result = str.Contains("o");
Here, the Contains() method will return a Boolean value indicating whether the string contains the specified substring("o") in it. The return value here will be true which will be assigned to result.
In your code, you do something similar for each field:
accID = booking.accID.Contains(txtSearch.Text)
This will check if booking.accID contains the string searched by the user which is captured in txtSearch.Text. If your booking.accID contains txtSearch.Text, the method will return true and false if it does not contain the search text. This will create a new variable of type bool called accId and the return value will be stored in accId on the left-hand side of =.
Anonymous Types
In C#, an anonymous type is a quick way to create a wrapper object containing a set of properties without actually creating a class.
For instance, I want an object containing details about a person without creating a Person class, I can do this:
var myPerson = new { Name = "John", Age = 25, Salary = 10_000L };
Now, I have an object containing the properties Name, Age and Salary without even creating a Person class. The compiler creates a hidden class in the background. More on anonymous types here.
You are creating a lambda function that returns an anonymous type as the fourth parameter of the Join() method. This lambda function will be called on each result of the join operation.
Solution
The filtering condition should be specified in a Where() method instead of assigning it to properties in the anonymous type. The anonymous type should be used to capture and combine the two results:
var searchData = context
.bookingorders
.Join(
context.addressbooks,
booking => booking.addrID,
address => address.addrID,
(booking, address) => new
{
Booking = booking,
Address = address
})
.Where(data =>
data.Booking.bookingID.Contains(txtSearch.Text) ||
data.Address.companyName.Contains(txtSearch.Text) ||
data.Address.addressLn1.Contains(txtSearch.Text) ||
data.Address.addressLn2.Contains(txtSearch.Text) ||
data.Address.region.Contains(txtSearch.Text) ||
data.Address.postcode.Contains(txtSearch.Text) ||
data.Address.contectName.Contains(txtSearch.Text) ||
data.Address.phoneNo.Contains(txtSearch.Text) ||
data.Address.faxNo.Contains(txtSearch.Text) ||
data.Address.telexNo.Contains(txtSearch.Text) ||
data.Booking.pickupDate.Contains(txtSearch.Text) ||
data.Booking.pickupTime.Contains(txtSearch.Text)
)
.ToList();
foreach(var row in searchData)
{
dataGridView1.Rows.Add(
row.Booking.bookingId,
row.Address.companyName,
$"{row.Address.addressLn1} {row.Address.addressLn2}",
row.Address.region,
row.Address.postcode,
row.Address.contectName,
row.Address.phoneNo,
row.Address.faxNo,
row.Address.telexNo,
row.Booking.pickupDate,
row.Booking.pickupTime
);
}
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.
Sorry that I couldn't explain better in the question header. I'll try to define my question better here.
That's what I am doing - in my game, there is an option to use items on one another. For each pair of items, the game performs an action.
Currently, there are two variables that game uses for this: "ItemUsed" and "ItemUsedOn". First, the game chooses first item - its id goes to "ItemUsed", then he chooses second item, it's id goes to "ItemUsedOn". Then, there is a void that defines a specific action.
A short example of code:
if (ItemUsed == "itm_cable")
{
if (ItemUsedOn == "itm_towel")
SubMain.ItemsMergedText = "To achieve what?";
else if (ItemUsedOn == "itm_wet_towel")
SubMain.ItemsMergedText = "No, water will damage it";
else if (ItemUsedOn == "itm_glass")
SubMain.ItemsMergedText = "I don't need to cut the cable";
}
if (ItemUsed == "itm_book_electronics")
{
if (ItemUsedOn == "itm_towel")
SubMain.ItemsMergedText = "Why?";
if (ItemUsedOn == "itm_wet_towel")
SubMain.ItemsMergedText = "No, water will damage the book";
else if (ItemUsedOn == "itm_soap")
SubMain.ItemsMergedText = "Wrong plan";
else if (ItemUsedOn == "itm_hair")
SubMain.ItemsMergedText = "It won't help";
}
And there are many such pairs.
However, there is a problem with this approach. When two items are combined, their order doesn't matter, for example "itm_toolbox" can be "ItemUsed" and "itm_cable" can be "ItemUsedOn", but also can be the other way around, the result will be the same. How can this be achieved?
I did try using this in every "larger" if:
else
CombineItems(ItemUsedOn, "itm_book_edda");
But this doesn't always work, and I couldn't find why.
So, what I am looking for is function that gets 2 variables:
void CombineItems(string Item1, string Item2)
And then give same result in those cases:
if (Item1="Tomato")&&(Item2="Cucumber")
Item3="Salad"
if (Item1="Cucumber")&&(Item2="Tomato")
Item3="Salad"
My question: is there an easier way for this, without using so many "if's"?
Thank you in advance,
Evgenie
I'd recommend that you create yourself an eg UnorderedPair type, which overrides the behaviour of .Equals and/or ==, such that:
new UnorderedPair("Tomato", "Cumcumber") == new UnorderedPair("Cucumber", "Tomato");
Then you can reduce all of your if statements down to a simple dictionary:
combinedItems = new Dictionary<UnderedPair, string>
{
[new UnorderedPair("Tomato", "Cumcumber")] = "Salad",
[new UnorderedPair("Bread", "Filling")] = "Sandwich",
...
};
and your code for determining the text can then just be:
SubMain.ItemsMergedText = combinedItems[new UnorderedPair(ItemUsed, ItemUsedOn)];
You can use params(to treat them as array) and LINQ. Store the salad-items in an array too:
private string[] SaladItems = new string[] { "Tomato", "Cucumber" };
string CombineItems(params string[] Items)
{
bool isSalad = SaladItems.Length == Items.Length && !SaladItems.Except(Items).Any();
if (isSalad) return "Salad";
// other types ...
return null; // no match, exception?
}
Side-Note: if you want to accept "tomato"(so ignore the case) use:
!SaladItems.Except(Items, StringComparer.InvariantCultureIgnoreCase).Any()
You could make this endless list of comparisons a lot smaller if you structure the logic into separate parts.
First, I would create a list or so to keep the items and their result:
var items = new[] { new { Item1 = "tomato", Item2 = "Cucumber", Result = "Salad" }
, new { Item1 = "tomato2", Item2 = "Cucumber2", Result = "Salad2" }
};
Then find the matching item:
var match = items.FirstOrDefault
(itm => (itm.Item1 == Item1 && itm.Item2 == Item2)
|| (itm.Item1 == Item2 && itm.Item2 == Item1)
);
There are two approaches that can keep down the amount of duplication throughout the code:
Sort the items so they're alphabetical order:
if(Item1 > Item2) {
var tmp = Item2;
Item2 = Item1;
Item1 = tmp;
}
now all of your remaining code can assume that Item1 values will always be earlier alphabetically than Item2, so you'd only write:
if (Item1="Cucumber")&&(Item2="Tomato")
Item3="Salad"
Or, you can, after exhausting all of your options (again, written only once), call your method recursively with the parameters swapped:
void TakeAction(Item1, Item2) {
.
.
.
/* No matches */
else {
TakeAction(Item2,Item1);
}
}
var items= new HashSet<string>();
items.Add("tomato");
items.Add("Cucumber");
if(items.Contains("tomato") && items.Contains("Cucumber")) //Order does **NOT** matter
Item3="Salad";
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