I've the following code:
public class C
{
public string Field {get; set;}
public string Data {get; set;}
}
var x = new C { Field = "F", Data = "Data 1" };
var y = new C { Field = "G", Data = "Data 2" };
And I want to cast this to an anonymous object like:
var x_a = new { F = "Data 1" };
var y_a = new { G = "Data 2" };
Note that the property name (F or G) is the content, so this can change dynamically. I'm currently using the System.Linq.Dynamic 'Select' method for this:
public static object CastToAnonymous(this C source)
{
var objects = new List<C>(new [] {source}).AsQueryable().Select("new (Data as " + source.Field + ")") as IEnumerable<object>;
return objects.First();
}
I'm wondering if there is an easier way to achieve this?
Related
Basically I want to rewrite this code:
var values = "{\"source\":{\"index\": \"" + oldIndex.Index + "\",\"type\": \"" + MyType+ "\"},"
+ "\",\"size\": \"" + size + "\"},"
+ "\"dest\": {\"index\": \"" + tempIndex + "\",\"type\": \"_doc\"}}";
var result = this.httpClient.PostAsync(ReIndexUrl, new StringContent(values, System.Text.Encoding.UTF8, "application/json")).Result;
var response = result.Content.ReadAsStringAsync().Result;
to NEST client:
var response = elasticClient.ReindexOnServer(
r => r
.Source(
s => s
.Index(sourceIndex)
)
.Destination(
d => d
.Index(targetIndex)
).Refresh(false).Size(maximumDocuments)
.WaitForCompletion()
);
While reindexing docs from V6 to V7 using NEST version, I get errors because of duplicate types on the target:
java.lang.IllegalArgumentException: Rejecting mapping update to [audit-trail-2020-04-temp] as the final mapping would have more than 1 type: [_doc, audittrailentry]
I cannot find a way to workaround this issue using NEST since there is no way to exclude type?
Unfortunately, I dit not find a solution for this using NEST Client.
At the end I wrote my own extension:
internal static long ReIndex(this IElasticClient elasticClient, ReindexModel reindexModel)
{
elasticClient.DisableRefreshing(reindexModel.SourceIndex);
var result = HttpClient.PostAsync(ReIndexUrl, new StringContent(JsonConvert.SerializeObject(reindexModel.Value), Encoding.UTF8, "application/json")).Result;
var response = result.Content.ReadAsStringAsync().Result;
var responseObj = JsonConvert.DeserializeObject<ReindexResponse>(response);
return responseObj.Total;
}
internal class ReindexModel
{
public ReindexModel(string sourceIndex, string targetIndex, string docType, int maxBulkSize)
{
this.SourceIndex = sourceIndex;
this.TargetIndex = targetIndex;
this.DocType = docType;
this.Value = new { source = new { index = this.SourceIndex, type = this.DocType, size = maxBulkSize }, dest = new { index = this.TargetIndex, type = "_doc" } };
}
public object Value { get; }
public string SourceIndex { get; }
public string TargetIndex { get; }
private string DocType { get; }
}
internal class ReindexResponse
{
[JsonProperty]
public long Total { get; set; }
}
I can use the below code to group by a ID property in an array which works.
var docArray = MyArray;
var docGroup = docArray.GroupBy(x => x.ID)
.Select(grp => new
{
Id = grp.Key,
Results = grp.ToList(),
}).ToList();
If MyArray has another array inside it which has a property say Data can some please tell me how to do the grouping based on the Data property.
class MyArray
{
SecondArray[] arr = new SecondArray[2];
public int ID{get;set;}
}
class SecondArray
{
public string Data{ get; set; }
}
var query = from a in docArray
from b in a.arr
group new { a, b } by b.Data into g
select new
{
g.Key,
Results = g.ToList()
};
lstReport=lstReport.Where(o=>DateTime.Parse(o.Field)==DateTime.Parse(o.FieldValue));
//I am creating above statement dynamically like this
var variable = Expression.Variable(typeof(Report));
foreach (SQWFilterConstraint oFC in oFilter.LstFilterConstraint) //using this collection I am making dynamic query
{
Expression ExprLeft =Expression.Property(variable, oFC.Field);
MethodInfo methodDateTimeParse = typeof(DateTime).GetMethod("Parse", newType[] { typeof(string) });
var methodParam = Expression.Parameter(typeof(string), oFC.FieldValue);
Expression exprRight = Expression.Call(methodDateTimeParse, methodParam ); //This is working fine for right side
}
var props = new[] { variable };
var lambda = Expression.Lambda<Func<Report, bool>>(ExprPrev, props).Compile();
ReportList = ReportList.Where(lambda).ToList();
So I need to apply the DateTime.Parse method on field also which comes at the left side (which is underlined and bold above left side of the operator)
Not sure what you trying to achieve.
1) What's the foreach for? Each property that has to be compared?
2) ExprPrev was never declared.
Anyhow, the way to create that expression is as follows.
[TestMethod]
public void TestDateTimeParse()
{
var variable = Expression.Variable(typeof (Report));
var parseMethodInfo = typeof (DateTime).GetMethod("Parse", new[] {typeof (string)});
var left = Expression.Call(parseMethodInfo, Expression.Property(variable, "Field"));
var right = Expression.Call(parseMethodInfo, Expression.Property(variable, "FieldValue"));
var equals = Expression.Equal(left, right);
var expression = Expression.Lambda<Func<Report, bool>>(equals, variable).Compile();
var target = new Report {Field = DateTime.Now.ToString()};
target.FieldValue = target.Field;
expression(target).Should().Be.True();
}
public class Report
{
public string Field { get; set; }
public string FieldValue { get; set; }
}
I know I can map two object types with LINQ using a projection as so:
var destModel = from m in sourceModel
select new DestModelType {A = m.A, C = m.C, E = m.E}
where
class SourceModelType
{
string A {get; set;}
string B {get; set;}
string C {get; set;}
string D {get; set;}
string E {get; set;}
}
class DestModelType
{
string A {get; set;}
string C {get; set;}
string E {get; set;}
}
But what if I want to make something like a generic to do this, where I don't know specifically the two types I am dealing with. So it would walk the "Dest" type and match with the matching "Source" types.. is this possible? Also, to achieve deferred execution, I would want it just to return an IQueryable.
For example:
public IQueryable<TDest> ProjectionMap<TSource, TDest>(IQueryable<TSource> sourceModel)
{
// dynamically build the LINQ projection based on the properties in TDest
// return the IQueryable containing the constructed projection
}
I know this is challenging, but I hope not impossible, because it will save me a bunch of explicit mapping work between models and viewmodels.
You have to generate an expression tree, but a simple one, so it's not so hard...
void Main()
{
var source = new[]
{
new SourceModelType { A = "hello", B = "world", C = "foo", D = "bar", E = "Baz" },
new SourceModelType { A = "The", B = "answer", C = "is", D = "42", E = "!" }
};
var dest = ProjectionMap<SourceModelType, DestModelType>(source.AsQueryable());
dest.Dump();
}
public static IQueryable<TDest> ProjectionMap<TSource, TDest>(IQueryable<TSource> sourceModel)
where TDest : new()
{
var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
var destProperties = typeof(TDest).GetProperties().Where(p => p.CanWrite);
var propertyMap = from d in destProperties
join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
select new { Source = s, Dest = d };
var itemParam = Expression.Parameter(typeof(TSource), "item");
var memberBindings = propertyMap.Select(p => (MemberBinding)Expression.Bind(p.Dest, Expression.Property(itemParam, p.Source)));
var newExpression = Expression.New(typeof(TDest));
var memberInitExpression = Expression.MemberInit(newExpression, memberBindings);
var projection = Expression.Lambda<Func<TSource, TDest>>(memberInitExpression, itemParam);
projection.Dump();
return sourceModel.Select(projection);
}
(tested in LinqPad, hence the Dumps)
The generated projection expression looks like that :
item => new DestModelType() {A = item.A, C = item.C, E = item.E}
If you have a simple Linq query like:
var result = from record in db.Customer
select new { Text = record.Name,
Value = record.ID.ToString() };
which is returning an object that can be mapped to a Drop Down List, is it possible to dynamically specify which fields map to Text and Value?
Of course, you could do a big case (switch) statement, then code each Linq query separately but this isn't very elegant. What would be nice would be something like:
(pseudo code)
var myTextField = db.Customer["Name"]; // Could be an enumeration??
var myValueField = db.Customer["ID"]; // Idea: choose the field outside the query
var result = from record in db.Customer
select new { Text = myTextField,
Value = myValueField };
Right way to do this is with closures.
Func<Customer, string> myTextField = (Customer c) => c["Name"];
Func<Customer, int> myValueField = (Customer c) => c["ID"];
var result = from record in db.Customer
select new { Text = myTextField(record),
Value = myValueField(record) };
The one limitation is that your definition of myTextField always needs to return a string.
You could try something like
class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
var dict = new Dictionary<string, Func<Customer, string>>
{ { "ID", (Customer c) => c.ID.ToString() },
{ "Name", (Customer c) => c.Name},
{ "Surname", (Customer c) => c.Surname } };
List<Customer> rows = new List<Customer>();
rows.Add(new Customer { ID = 1, Name = "Foo", Surname = "Bar"});
var list = from r in rows
select new { Text = dict["ID"](r), Value = dict["Name"](r) };
To try to access the properties dynamically, you could try something like
var dict = new Dictionary<string, Func<Customer, string>>
{ { "ID", (Customer c) => c.GetType().GetProperty("ID").GetValue(c,null).ToString() },
{ "Name", (Customer c) => c.GetType().GetProperty("Name").GetValue(c,null).ToString()},
{ "Surname", (Customer c) => c.GetType().GetProperty("Surname").GetValue(c,null).ToString() } };