Create Expression from a string containing a LINQ expression - c#

I'm looking for a way to create an expression from a string containg a Linq expression itself.
Assume the following class:
public class Person
{
public string Name { get; set; }
public string FirstName { get; set; }
public Address[] Address { get; set; }
}
public class Address
{
public string City { get; set; }
}
A simple expression can be generated with the following code:
var expression = #"x.Name";
var p = Expression.Parameter(typeof(Person), "x");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, expression);
The sample below will return Name 1 as expected:
static void Main(string[] args)
{
var personsCollection = new List<Person>
{
new Person { Name = "Name 1", FirstName = "Firstname 1", Address = new Address { City = "City 1" } },
new Person { Name = "Name 2", FirstName = "Firstname 2", Address = new Address { City = "City 2" } }
};
var expression = #"x.Name";
var p = Expression.Parameter(typeof(Person), "x");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, expression);
var result = e.Compile().DynamicInvoke(personsCollection[0]);
}
Now, assume that I change the expression to project a List<T>.
The expression will than be: var expression = #"x.Address.Select(dest => new { MyCity = dest.City })";
Using this is the sample code above throws the following exception: "Unknown identifier 'dest'"
Any idea on how this can be solved?

Related

form an list object with alternative rows or some of properties

I have a flat object, and it looks like as below
public class Test
{
public string Name {get; set;}
public string Number {get; set;}
public string Description {get; set;}
public string Name1 {get; set;}
public string Name2 {get; set;}
}
and I have the data looks like as below
List<Test> tests = new List<Test>
{
new Test{ Name = "nameA", Number=8, Description="description", Name1 = "test 1", Name2="test name1" },
new Test{ Name = "nameB", Number=3, Description="description2", Name1 = "test 2", Name2="test name2" },
new Test{ Name = "nameC", Number=4, Description="description3", Name1 = "test 3", Name2="test name3" },
};
and I am trying to form an object list that looks like below
I am using the below logic to form a list object but failed to add all objects to that,
List<dynamic> testList = new();
int rowIndex = 1;
foreach (var testDt in tests)
{
dynamic testData = new ExpandoObject();
if (rowIndex % 2 != 0)
{
testData.Name = testDt.Name;
testData.Description = testDt.Description;
testData.Number = testDt.Number;
}
else
{
testData.Name1 = testDt.Name1;
testData.Name2 = testDt.Name2;
}
testList.Add(testData);
rowIndex++;
}
But this logic is not adding all items to the list if the list has more items and could anyone please let me know where I am wrong with the above code.
Many thanks in advance!!!
Here you go:
List<Test> tests = new List<Test>
{
new Test{ Name = "nameA", Number = 8, Description = "description", Name1 = "test 1", Name2 = "test name1" },
new Test{ Name = "nameB", Number = 3, Description = "description2", Name1 = "test 2", Name2 = "test name2" },
new Test{ Name = "nameC", Number = 4, Description = "description3", Name1 = "test 3", Name2 = "test name3" },
};
var results =
from t in tests
from r in new []
{
new { t.Name, Number = t.Number.ToString(), t.Description, Name1 = "", Name2 = "", },
new { Name = "", Number = "", Description = "", Name1 = t.Name1, t.Name2, },
}
select r;
That gives me:
To use a regular foreach loop this would work:
public class Output
{
public string Name { get; set; }
public string Number { get; set; }
public string Description { get; set; }
public string Name1 { get; set; }
public string Name2 { get; set; }
}
var results = new List<Output>();
foreach (var t in tests)
{
results.Add(new Output() { Name = t.Name, Number = t.Number.ToString(), Description = t.Description, Name1 = "", Name2 = "", });
results.Add(new Output() { Name = "", Number = "", Description = "", Name1 = t.Name1, Name2 = t.Name2, });
}
I've created a new class rather than use dynamic, as dynamic generally creates more problems than it solves, IMO. I avoid it where possible.
And finally, the equivalent using LINQ's method syntax:
results =
tests
.SelectMany(t => new []
{
new Output() { Name = t.Name, Number = t.Number.ToString(), Description = t.Description, Name1 = "", Name2 = "", },
new Output() { Name = "", Number = "", Description = "", Name1 = t.Name1, Name2 = t.Name2, },
})
.ToList();
The problem is that you are adding only 1 item to the list for each iterate, however, what you need is to add 2 instead to be able to get the expected result.
You can simply do that as follows.
foreach (var testDt in tests)
{
dynamic testData1 = new ExpandoObject();
testData1.Name = testDt.Name;
testData1.Description = testDt.Description;
testData1.Number = testDt.Number;
testList.Add(testData1);
dynamic testData2 = new ExpandoObject();
testData2.Name1 = testDt.Name1;
testData2.Name2 = testDt.Name2;
testList.Add(testData2);
}

Make Linq function more generic parameters

How do I generalize this function? I want Name property to be variable, and have function accept, replace the persons class with any class.
// Filtering logic
Func<SampleFilterModel, IEnumerable<Person>> filterData = (filterModel) =>
{
return persons.Where(p => p.Name.StartsWith(filterModel.Term ?? String.Empty, StringComparison.InvariantCultureIgnoreCase))
.Skip((filterModel.Page-1) * filter.Limit)
.Take(filterModel.Limit);
};
Other items:
IEnumerable<Person> persons = new List<Person>() {
new Person() { Name = "Laura Callahan", DOB = DateTime.Parse("1958-01-09"), Email = "laura.callahan#test.com" },
new Person() { Name = "Anne Dodsworth", DOB = DateTime.Parse("1966-01-27"), Email = "anne.dodsworth#test.com" }
};
public class SampleFilterModel
{
public int Page { get; set; }
public int Limit { get; set; }
public string Term { get; set; }
public SampleFilterModel()
{
this.Page = 1;
this.Limit = 3;
}
public object Clone()
{
var jsonString = JsonConvert.SerializeObject(this);
return JsonConvert.DeserializeObject(jsonString, this.GetType());
}
}
Current attempt, trying to modify/rework as needed:
giving some problems/errors:
Do I have to declare all variables as static?
Getting error message:
'T' does not contain a definition for 'propertyInfo' and no accessible extension method 'propertyInfo' accepting a first argument of type 'T' could be found (are you missing a using directive or an assembly reference?)
Code:
public class Filterclass<T> where T : class
{
public static string ColumnName;
public static SampleFilterModel filter = new SampleFilterModel();
public static IEnumerable<T> input;
public Func<SampleFilterModel, IEnumerable<T>> filterData = (filterModel) =>
{
var propertyInfo = input.GetType().GetProperty(ColumnName);
return input.Where(p => p.propertyInfo.StartsWith(filterModel.Term ?? String.Empty, StringComparison.InvariantCultureIgnoreCase))
.Skip((filterModel.Page - 1) * filter.Limit)
.Take(filterModel.Limit);
};
}
While the rest of the question does not make much sense to me, the part about trying to make a generic function would require building up a predicate for the Where call
Review the comments included to get an example of how to build up the lambda expression used for the predicate
public static class Filterclass {
static readonly MethodInfo startsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string), typeof(System.StringComparison) });
public static IEnumerable<T> FilterData<T>(this IEnumerable<T> input, string columnName, FilterModel filterModel) where T : class {
var type = typeof(T);
var propertyInfo = type.GetProperty(columnName);
//T p =>
var parameter = Expression.Parameter(type, "p");
//T p => p.ColumnName
var name = Expression.Property(parameter, propertyInfo);
// filterModel.Term ?? String.Empty
var term = Expression.Constant(filterModel.Term ?? String.Empty);
//StringComparison.InvariantCultureIgnoreCase
var comparison = Expression.Constant(StringComparison.InvariantCultureIgnoreCase);
//T p => p.ColumnName.StartsWith(filterModel.Term ?? String.Empty, StringComparison.InvariantCultureIgnoreCase)
var methodCall = Expression.Call(name, startsWith, term, comparison);
var lambda = Expression.Lambda<Func<T, bool>>(methodCall, parameter);
return input.Where(lambda.Compile())
.Skip((filterModel.Page - 1) * filterModel.Limit)
.Take(filterModel.Limit);
}
}
Now this assumes that the column is of type string. Any other type would cause this to throw an exception.
You would also need to assume that p is not null else again a null reference exception will be thrown when trying to call instance members on a null reference.
Sample unit test of the extension method in use
[TestClass]
public class MyTestClass2 {
[TestMethod]
public void MyTestMethod() {
//Arrange
IEnumerable<Person> persons = new List<Person>() {
new Person() { Name = "Nancy Davolio", DOB = DateTime.Parse("1948-12-08"), Email = "nancy.davolio#test.com" },
new Person() { Name = "Andrew Fuller", DOB = DateTime.Parse("1952-02-19"), Email = "andrew.fuller#test.com" },
new Person() { Name = "Janet Leverling", DOB = DateTime.Parse("1963-08-30"), Email = "janet.leverling#test.com" },
new Person() { Name = "Margaret Peacock", DOB = DateTime.Parse("1937-09-19"), Email = "margaret.peacock#test.com" },
new Person() { Name = "Steven Buchanan", DOB = DateTime.Parse("1955-03-04"), Email = "steven.buchanan#test.com" },
new Person() { Name = "Michael Suyama", DOB = DateTime.Parse("1963-07-02"), Email = "michael.suyama#test.com" },
new Person() { Name = "Robert King", DOB = DateTime.Parse("1960-05-29"), Email = "robert.king#test.com" },
new Person() { Name = "Laura Callahan", DOB = DateTime.Parse("1958-01-09"), Email = "laura.callahan#test.com" },
new Person() { Name = "Anne Dodsworth", DOB = DateTime.Parse("1966-01-27"), Email = "anne.dodsworth#test.com" }
};
var filter = new FilterModel {
Term = "Nancy"
};
//Act
var data = persons.FilterData("Name", filter);
//Assert
data.Should().NotBeEmpty();
}
}

Set PredicateBuilder also on child collection

I am trying to apply the predicate not only to the entity parent but also to the child collection. The following is part of my code:
var predicate = PredicateBuilder.New<Entity>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or(p => p.Name.Equals(temp));
predicate = predicate.Or(p => p.Addresses.Select(x=> x.Name.Equals(temp));
}
The line predicate = predicate.Or(p => p.Addresses.Select(x=> x.Name.Equals(temp)); is not working ? any ideas as to why?
EDIT
In the following demo I am asking to get entity parent and child with name = tom but I also get child name = tom2.
private static void Main(string[] args)
{
var result = GetAll(GetAll(), new List<string> { "tom" });
}
private static List<User> GetAll()
{
return new List<User>
{
new User
{
Id = 1,
Name = "tom",
Addesses = new List<Addesses>
{
new Addesses { Id = 1, Name = "tom" },
new Addesses { Id = 1, Name = "tom2" },
}
},
new User
{
Id = 1,
Name = "sam",
Addesses = new List<Addesses>
{
new Addesses { Id = 1, Name = "sam" },
new Addesses { Id = 1, Name = "sam2" },
}
},
};
}
private static List<User> GetAll(List<User> users, List<string> keywords)
{
var predicate = PredicateBuilder.New<User>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or(p => p.Name.Equals(temp));
predicate = predicate.Or(p => p.Addesses.Any(x => x.Name.Equals(temp)));
}
var result = users
.Where(predicate)
.ToList();
return result;
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public List<Addesses> Addesses { get; set; }
}
public class Addesses
{
public int Id { get; set; }
public string Name { get; set; }
}
The call to p.Addresses.Select(x=> x.Name.Equals(temp) is not returning a boolean result.
Depending on your actual logic you may want to look into Any:
predicate = predicate.Or(p => p.Addresses.Any(x=> x.Name.Equals(temp));
or All:
predicate = predicate.Or(p => p.Addresses.All(x=> x.Name.Equals(temp));

Using Dictionary Parameters as Optional Arguments when Calling Method

I'm presently trying to use a dictionary values to name optional parameters when invoking a method. I'm not sure this is possible with c# but I do something similar with queries using dynamic SQL.
string[] dobArrayKey = {"dob: "};
string[] dobArrayValue = {txtDob.Text};
string[] ptntNumArrayKey = { "PatientID: " };
string[] ptntNumArrayValue = { txtOfficeMR.Text};
string[] nameArrayKey = { "FirstName: ", "LastName: " };
string[] nameArrayValue = { txtFirstname.Text, txtLastname.Text };
List<List<string>> searchResults = new List<List<string>>();
Dictionary<string[], string[]> searchCriteria = new Dictionary<string[], string[]>
{
{dobArrayKey,dobArrayValue}
,{ptntNumArrayKey,ptntNumArrayValue}
,{nameArrayKey,nameArrayValue}
};
foreach (var item in searchCriteria)
{
if (item.Value[0] != "" && item.Value[0] != null)
{
searchResults.Add(new List<string>());
for (int x = 0; x <= item.Key.Count(); x++)
{
string strJSON = doPatientSearch(Convert.ToInt32(au.UserID)
, Convert.ToInt32(Session["PracticeID"]), au.SessionID, item.Key[x].ToString() : item.Value[x].ToString() );
PatientSearchResponse ptLi = JsonConvert.DeserializeObject<PatientSearchResponse>(json2);
foreach (PatientList3 patient in ptLi.PatientList)
{
searchResults[x].Add(patient.PatientNumber);
}
}
}
}
public static string doPatientSearch(int UserID, int PracticeID, string SessionID, string PatientID = null,
,string first = null, string last = null, string dob = null, string social = null)
{
//search
}
My colleague suggested I change the method itself by removing all of the optional parameters and instead passing through a dictionary that contains all of the parameters and handling them inside the method.
I think that would work, but for curiosities sake I wanted to get some feedback and find out whether or not something like I'm attempting to do in the above code is possible.
If it is impossible but there is another way of achieving the desired outcome I'd love to see your suggestions.
Thank you in advance.
Pass an expression
Since the criteria are used post-hoc (i.e. by filtering a complete resultset), you can use LINQ to filter the results. For maximum flexibility, the caller can pass in an Expression to be used as a callback on each item to determine if it should be included.
To get a filtered resultset:
public IEnumerable<Patient> FindPatients(Func<Patient,bool> criteria)
{
return sourceData
.Where (criteria);
}
To return a single result:
public Patient FindPatient(Func<Patient,bool> criteria)
{
return sourceData
.Single(criteria);
}
The criteria expression is just a function that accepts a patient and returns a Boolean. The caller can write this any way desired, or insert it as a lambda expression.
var results = patients.FindPatients( p => p.LastName == "Doe" );
Or
var results = patients.FindPatients
(
p =>
p.LastName.Contains("Doe") &&
p.PracticeID == 12
);
Or
var singleResult = patients.FindPatient( p => p.UserID == 1);
As you can see, the caller can provide literally any criteria desired, and has the benefit of type safety and early binding. This is far superior to using a Dictionary which has neither.
Full example code:
class Patient
{
public int UserID { get; set; }
public int PracticeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DOB { get; set; }
public string Social { get; set; }
public override string ToString()
{
return string.Format("{0} {1} {2}", UserID, FirstName, LastName);
}
}
class PatientRepository
{
static private readonly List<Patient> sourceData = new List<Patient>
{
new Patient
{
UserID = 1, PracticeID = 10, FirstName = "John", LastName = "Doe", DOB = DateTime.Parse("1/2/1968"), Social="123456789"
},
new Patient
{
UserID = 2, PracticeID = 10, FirstName = "Jane", LastName = "Doe", DOB = DateTime.Parse("1/2/1958"), Social="123456790"
},
new Patient
{
UserID = 3, PracticeID = 10, FirstName = "John", LastName = "Carson", DOB = DateTime.Parse("4/1/1938"), Social="123456791"
}
};
public IEnumerable<Patient> FindPatients(Func<Patient,bool> criteria)
{
return sourceData
.Where (criteria);
}
public Patient FindPatient(Func<Patient,bool> criteria)
{
return sourceData
.Single(criteria);
}
}
public class Program
{
public static void Main()
{
//Get a reference to the data store
var patients = new PatientRepository();
Console.WriteLine("Multiple record search");
var results = patients.FindPatients
(
p => p.LastName == "Doe"
);
foreach (var p in results)
{
Console.WriteLine(p);
}
Console.WriteLine("Single record search");
var singleResult = patients.FindPatient
(
p => p.UserID == 1
);
Console.WriteLine(singleResult);
}
}
Output:
Multiple record search
1 John Doe
2 Jane Doe
Single record search
1 John Doe
See the working code on DotNetFiddle

Remove duplicates from the list with lists as objects

I have one list with the class as a structure as below
class cl
{
public string name1{ get; set; }
public string name2{ get; set; }
public string name3{ get; set; }
public List<c2> c2List{ get; set; }
}
class c2
{
public string st1{ get; set; }
public string str2{ get; set; }
}
Now I have list of C1 class and i need to remove duplicates from that list
can any one help me how can i do it
When referencing c2List, call it similarly to the below:
var distinctList = c1.c2List.Distinct();
(Assuming that c1 is instantiated further up the code)
I think this is what your after..
var c2Items = new c2[]
{
new c2 { st1 = "value 1", str2 = "value 2" },
new c2 { st1 = "value 2", str2 = "value 1" },
new c2 { st1 = "value 1", str2 = "value 2" }
};
var parent = new cl() { c2List = new List<c2>(c2Items) };
IEnumerable<c2> distinctitems =
parent
.c2List
.GroupBy(o => new { o.st1, o.str2 })
.Select(o => o.First());

Categories

Resources