Lambda Expression for Unpivoting DataTable - c#

I am reading data from an Excel sheet in the following format:
I need to store the data in the following way:
I am trying to do it with the help of Linq lambda expression but I think I'm not getting anywhere with this.
DataTable dataTable= ReadExcel();
var dt = dataTable.AsEnumerable();
var resultSet = dt.Where(x => !String.IsNullOrEmpty(x.Field<String>("Project_Code")))
.GroupBy(x =>
new
{
Month = x.Field<String>("Month"),
ProjectCode = x.Field<String>("Project_Code"),
//change designation columns into row data and then group on it
//Designation =
}
);
//.Select(p =>
// new
// {
// Month= p.d
// }
// );`

I would use ToDictionary with a pre-defined set of designation names:
private static readonly string[] designationNames = {"PA","A","SA","M","SM","CON"};
void Function()
{
/* ... */
var resultSet = dt.AsEnumerable().Where(x => !String.IsNullOrEmpty(x.Field<String>("Project_Code")))
.Select(x =>
new
{
Month = x.Field<String>("Month"),
ProjectCode = x.Field<String>("Project_Code"),
Designations = designationNames.ToDictionary(d => d, d => x.Field<int>(d))
}
);
}
This is the normalized version. If you want it flat instead, use:
private static readonly string[] designationNames = {"PA","A","SA","M","SM","CON"};
void Function()
{
/* ... */
var resultSet = dt.AsEnumerable().Where(x => !String.IsNullOrEmpty(x.Field<String>("Project_Code")))
.Select(x =>
designationNames.Select(
d =>
new
{
Month = x.Field<String>("Month"),
ProjectCode = x.Field<String>("Project_Code"),
Designation = d,
Count = x.Field<int>(d)
}
)
).SelectMany(x => x).ToList();
}
If the type is not always int then you might want to use x.Field<String>(d) instead and check for validity.

Related

Unable to translate set operation after client projection has been applied

I have a method within my repository which queries two separate join tables. I am trying to combine the two IQueryables before converting them into Lists but I am receiving the above error message in the console.
Here is the method within the repository:
public async Task<PagedList<EventDto>> GetUserOrganisedEvents(EventParams eventParams)
{
var user = await _userRepository.GetUserByUsernameAsync(eventParams.username);
var organisedEventsquery = _context.UserEvents.AsQueryable();
var organisedEvents = organisedEventsquery.Where(events => events.OrganiserId == user.Id);
var AttendedEventsquery = _context.EventUsers.AsQueryable();
var AttendedEvents = AttendedEventsquery.Where(events => events.AttendeeId == user.Id);
var organisedEventsList = organisedEvents.Select(organisedEvent => new EventDto
{
Name = organisedEvent.Event.Name,
Id = organisedEvent.EventId,
Date = organisedEvent.Event.Date,
Location = organisedEvent.Event.Location,
MainPhotoUrl = organisedEvent.Event.MainPhotoUrl,
Creator = _mapper.Map<MemberDto>(organisedEvent.Event.Creator),
Organisers = _mapper.Map<ICollection<MemberDto>>(organisedEvent.Event.Organisers),
Attendees = _mapper.Map<ICollection<MemberDto>>(organisedEvent.Event.Attendees)
});
var AttendedEventsList = AttendedEvents.Select(AttendedEvent => new EventDto
{
Name = AttendedEvent.AttendingEvent.Name,
Id = AttendedEvent.AttendingEventId,
Date = AttendedEvent.AttendingEvent.Date,
Location = AttendedEvent.AttendingEvent.Location,
MainPhotoUrl = AttendedEvent.AttendingEvent.MainPhotoUrl,
Creator = _mapper.Map<MemberDto>(AttendedEvent.AttendingEvent.Creator),
Organisers = _mapper.Map<ICollection<MemberDto>>(AttendedEvent.AttendingEvent.Organisers),
Attendees = _mapper.Map<ICollection<MemberDto>>(AttendedEvent.AttendingEvent.Attendees)
});
var events = organisedEventsList.Concat(AttendedEventsList);
return await PagedList<EventDto>.CreateAsync(events, eventParams.PageNumber, eventParams.PageSize);
}
Here is the CreateAsync method:
public static async Task<PagedList<T>> CreateAsync(IQueryable<T> source, int pageNumber, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
return new PagedList<T>(items, count, pageNumber, pageSize);
}
I came across this error too.
Much simpler query.
var query1 = _context.Set<TableA>()
.Select(x => NewMethod(x));
var query2 = _context.Set<TableB>()
.Select(x => NewMethod1(x));
private static ViewModel NewMethod1(TableA x)
{
return new ViewModel()
{
Number = x.Number,
Date = x.Date
};
}
private static ViewModel NewMethod(TableB x)
{
return new SalesLedgerRecord()
{
Number = x.Number,
Date = x.Date
};
}
The shown code above will fail with the error message in the subject if I union the results:
var query = query1.Union(query2)
.OrderBy(OrderBy).AsNoTracking();
However, if I remove the two private static functions to have:
var query = _context.Set<TableA>()
.Select(x =>
new ViewModel()
{
Number = x.Number,
Date = x.Date
};
All seems to be working ok.
Also, all works if I leave 'NewMethodX' but remove the union.
Not sure what change extracting to private static changes but it does changes something.

C# LINQ separate row into two rows based on List<String> property

I have a list of IPInfo objects already filtered in some way. My problem is to separate records based on last List<String> property:
class IPInfo
{
public String TRADE_DATE;
public String CUSTOMER_NAME;
public List<String> ORIGINAL_IP;
public List<String> LOGON_IP = new List<String>();
}
List<IPInfo> fields when exported to .xls file looks like this:
I need each Logon IP record to be in a separate row, but Original IP to remain combined. On a picture, for CITI I need:
----------------------------------------------
10.55.13.104 | 128.110.34.102
128.110.34.102 |
----------------------------------------------
10.55.13.104 | 10.55.13.104
128.110.34.102 |
Any help?
EDIT:
this query was applied before to combine duplicated Logon IPs. But, as I told, I need Logon IPs separated
private static List<IPInfo> selectFields(ref List<IPInfo> fields)
{
var distinct =
fields
.GroupBy(x => new { x.TRADE_DATE, x.CUSTOMER_NAME })
.Select(y => new IPInfo()
{
TRADE_DATE = y.Key.TRADE_DATE,
CUSTOMER_NAME = y.Key.CUSTOMER_NAME,
ORIGINAL_IP = y.SelectMany(x => x.ORIGINAL_IP).Distinct().ToList(),
LOGON_IP = y.SelectMany(x => x.LOGON_IP).Distinct().ToList()
})
.ToList();
return distinct;
}
var distinct = fields
.GroupBy(x => new { x.TRADE_DATE, x.CUSTOMER_NAME })
.Select(y => {
var logonIps = y.SelectMany(x => x.LOGON_IP).Distinct().ToList();
foreach (var logonIp in logonIps)
{
return new IPInfo(){
TRADE_DATE = y.Key.TRADE_DATE,
CUSTOMER_NAME = y.Key.CUSTOMER_NAME,
ORIGINAL_IP = y.SelectMany(x => x.ORIGINAL_IP).Distinct().ToList(),
LOGON_IP = logonIp
};
}
})
.ToList();
return distinct;
Loop through the logonIP list and treat it as new row for each logonIP.

AggregationContainer vs. AggregationDescriptor

I am trying to send an array of ranges to an Aggregation Descriptor, but the Lambda expression expects a comma delimited expresions
.Aggregations(agg =>
{
AggregationDescriptor ag = agg.Terms("objectTypes", ot => ot.Field("doc.objectType"));
if (!parameters.ContainsKey("userID"))
ag = ag.Terms("users", ot => ot.Field("doc.entryUserID"));//.Field("doc.sourceUserID")))
ag.Terms("commentTypes", ot => ot.Field("doc.commentType"));
if (!parameters.ContainsKey("dateRange"))
{
Dictionary<string, SearchDateRange> dateMap = GetDateRangeMap();
ag.DateRange("dates", dr => dr.Field("doc.date").Format("yyyy-MM-dd")
.Ranges(r1 => r1.Key("Today").From(dateMap["Today"].startDate.Value.ToString("yyyy-MM-dd")).To("now"),
r2 => r2.Key("SinceWednesday").From(dateMap["Today"].startDate.Value.ToString("yyyy-MM-dd")).To("now"),
r3 => r3.Key("ThisYear").From(dateMap["ThisYear"].startDate.Value.ToString("yyyy-MM-dd")).To("now"),
r3 => r3.Key("Last2Years").From(dateMap["Last2Years"].startDate.Value.ToString("yyyy-MM-dd")).To("now"),
r4 => r4.Key("Last3Years").From(dateMap["Last3Years"].startDate.Value.ToString("yyyy-MM-dd")).To("now")
));
}
The above code works.
Below I would like to use the Range[] array and pass it to the aggregate discriptor but I can't, but I can create an AggregationContainer with the range array. How do I marry these two pieces together?
if (!parameters.ContainsKey("revenueRange") && docTypes.Contains(CouchbaseDocumentType.Company))
{
Dictionary<string, SearchNumberRange> numMap = GetMoneyRangeMap();
Range<double>[] ranges = numMap.Select(m =>
{
var r = new Range<double>().Key(m.Key);
if (m.Value.low.HasValue) r.From(m.Value.low.Value);
if (m.Value.high.HasValue) r.To(m.Value.high.Value);
return r;
}).ToArray();
AggregationContainer agr = new AggregationContainer
{
Range = new RangeAggregator { Field = "doc.lastFinancial.revenueUSD", Ranges = ranges }
};
}
return ag;
}
)
I created simple example to show you how you can achieve this.
var funcs = new List<Func<Range<double>, Range<double>>>();
funcs.Add(range => new Range<double>().From(1).To(2));
funcs.Add(range => new Range<double>().From(3).To(4));
var searchResponse = client.Search<Document>(
s => s.Aggregations(agg => agg.Range("range", descriptor => descriptor.Field(f => f.Number).Ranges(funcs.ToArray()))));
Document class:
public class Document
{
public int Id { get; set; }
public double Number { get; set; }
}
Hope you won't have problem with putting it into your context.

Add query to linq var

I never used this before. I need to add another member to the query. How can I add "link" to "source"?
var titles = regexTitles.Matches(pageContent).Cast<Match>();
var dates = regexDate.Matches(pageContent).Cast<Match>();
var link = regexLink.Matches(pageContent).Cast<Match>();
var source = titles.Zip(dates, (t, d) => new { Title = t, Date = d });
foreach (var item in source)
{
var articleTitle = item.Title.Groups[1].Value;
var articleDate = item.Date.Groups[1].Value;
//var articleLink = item.Link.Groups[1].Value;
Console.WriteLine(articleTitle);
Console.WriteLine(articleDate);
//Console.WriteLine(articleLink);
Console.WriteLine("");
}
Console.ReadLine();
It sounds like you just need another call to Zip. The first call will pair up the titles and dates, and the second call will pair up the title/date pairs with the links:
var source = titles.Zip(dates, (t, d) => new { t, d })
.Zip(link, (td, l) => new { Title = td.t,
Date = td.d,
Link = l });
or (equivalently, just using projection initializers):
var source = titles.Zip(dates, (Title, Date) => new { Title, Date })
.Zip(link, (td, Link) => new { td.Title, td.Date, Link });
(I sometimes think it would be nice to have another couple of overloads for Zip to take three or four sequences... it wouldn't be too hard. Maybe I'll add those to MoreLINQ :)
You can easily use Zip once more as in the below code:
var source = titles.Zip(dates, (t, d) => new { Title = t, Date = d });
.Zip(link, (d, l) => new { Title = d.Title,
Date = d.Date,
Link= l });

Where Clause in LINQ with IN operator

I have a LINQ function that returns a summary table.
private DataTable CreateSummaryFocusOfEffortData()
{
var result = ReportData.AsEnumerable()
.GroupBy(row => new
{
Value = row.Field<string>("Value"),
Description = row.Field<string>("Description")
})
.Select(g =>
{
var row = g.First();
row.SetField("Hours", g.Sum(r => r.Field<double>("Hours")));
return row;
});
return result.CopyToDataTable();
}
Now I want to add a WHERE clause to this function so that it only sums up rows for fields that are there in a list. Something like the IN operator in sql. For example : Lets say I have a list with values (1,2,3) and I want to base by where clause with values that are there in list.
Just an example of how you could try it:
private DataTable CreateSummaryFocusOfEffortData(List<int> yourList)
{
var result = ReportData.AsEnumerable()
.GroupBy(row => new
{
Value = row.Field<string>("Value"),
Description = row.Field<string>("Description")
})
.Select(g =>
{
var row = g.First();
row.SetField("Hours",
g.Where(r=>yourList.Contains(r.Field<int>("Id")))
.Sum(r => r.Field<double>("Hours")));
return row;
});
return result.CopyToDataTable();
}

Categories