Filter Data based on same ID - c#

I have a model class
public class Model
{
public string name { get; set; }
public string files { get; set; }
}
I have a controller class that populates the data from database into this model
List<Model> model = new List<Model>();
while (reader.Read()){
var om = new Model();
if (reader != null)
{
om.name = reader.GetString(0);
om.files = reader.GetString(1);
model.Add(om)
}
How can I filter and combine all files that have the similar first names?
I read about linq and tried this
var jm = om.name
.Where(o => om.name.Contains(o))
.Select() ;

This might work for you:
var grouped = model.GroupBy(m => m.name).ToArray();
This will create an object grouped of type IGrouping<string, Model>[] (array of IGrouping...)
.ToArray() is optional and if you remove it, you get IEnumerable<IGrouping<string, Model>>, but don't let the nested <> scare you.
The items in your original list are grouped by name, so the group will have Key property that will be the same for each element in the group.
To print the results, for example:
foreach(var group in grouped)
{
Console.WriteLine($"Printing group: {group.Key}");
foreach(var model in group)
{
Console.WriteLine($"\tName: {model.name}, File: {model.file}");
}
}
Note that each element in a group is a collection of your model objects.
Use debugger to see what that structure looks like and if you need more help - ask...

Change your Model
public class Model
{
public string name { get; set; }
public IList<string> files { get; set; }
}
Use a Dictionary.
var models = new Dictionary<string, Model>();
while (reader.Read())
{
var om = new Model();
if (reader != null)
{
var name = reader.GetString(0);
var file = reader.GetString(1);
if (models.Contains(name))
models[name].files.Add(file);
else
{
models.Add(name, new Model
{
name = name,
files = new List<string>{files}
});
}
}
}
Then you can pull out a combined list with LINQ:
var peopleFileLists = models.Select(x => x.Value).ToList();
foreach(var person in peopleFileLists)
{
foreach(var file in person.files)
Console.WriteLine(person.name + "-" + file);
}

Related

Get all keys from combination of JArray and JObject

I'm new to JSON and looked at all the possible answers, but still not able to get it. Basically I'm getting the list of all users and storing it as string. Below is the result Json output string.
[{"Links":[],"RequestedObject":{"Id":181,"DisplayName":"User, Migration","FirstName":"Migration","MiddleName":null,"LastName":"User","LastLoginDate":"2008-01-10T11:04:00","UserName":"1564134","AccountStatus":2,"DomainId":null,"UpdateInformation":{"CreateDate":"2008-01-10T17:04:24.72","UpdateDate":"2011-10-07T16:35:51.74","CreateLogin":2,"UpdateLogin":2}},"IsSuccessful":true,"ValidationMessages":[]},{"Links":[],"RequestedObject":{"Id":16167,"DisplayName":"Xyz, Abc","FirstName":"Abc","MiddleName":null,"LastName":"Xyz","LastLoginDate":"2022-03-04T15:54:29.43","UserName":"1514834","AccountStatus":1,"DomainId":null,"UpdateInformation":{"CreateDate":"2022-03-04T15:53:14.817","UpdateDate":"2022-03-04T15:54:29.293","CreateLogin":14760,"UpdateLogin":11743}},"IsSuccessful":true,"ValidationMessages":[]}]
As you can see first part is JArray and then Jobject. My requirement is to get all "RequestedObject" that have "CreateDate" greater than or equal to CurrentDate. Is there a simple way to achieve this using linq instead of foreach loop. Here is code that I was able to put in from all other answers.
try
{
string text = System.IO.File.ReadAllText(#"H:\Test.txt");
DateTime previousRunTime = new DateTime(2022, 01, 31);
JArray jsonArray = JArray.Parse(text);
var jsonObjects = jsonArray.OfType<JObject>().ToList();
//var users1 = from item in jsonObjects.Children()["RequestedObject"].Value<string>()
// select item;
var abc = jsonObjects.Properties().Where(p => p.Name == "RequestedObject").Select(p => p.Value);
foreach(var q in abc)
{
Console.WriteLine(q.Value<string>("Id").ToString());
}
}
catch (Exception p)
{
Console.WriteLine(p.Message);
}
Looking for solution something like below
var users =
from item in jsonObjects["RequestedObject"]
where item["UpdateInformation"]["CreateDate"].Value<DateTime>() >= previousRunTime
select new UserDetails
{
UserName = item["UserName"].Value<string>(),
UserID = item["Id"].Value<string>(),
};
public class UserDetails
{
public string UserName { get; set; }
public string UserID { get; set; }
}
Thanks,
Prem
RequestedObject is a property on the objects in the array, not the array itself.
var users =
from item in jsonObjects
let obj = item["RequestedObject"]
where (DateTime)obj["UpdateInformation"]["CreateDate"] >= previousRunTime
select new UserDetails
{
UserName = (string)obj["UserName"],
UserID = (string)obj["Id"],
};
you need only one line code if you are using LINQ to JSON
List<UserDetails> users = jsonArray.Where(i => (DateTime)i["RequestedObject"]
["UpdateInformation"]["CreateDate"] >= previousRunTime)
.Select(i => i["RequestedObject"].ToObject<UserDetails>()).ToList();
class
public class UserDetails
{
[JsonProperty("UserName")]
public string UserName { get; set; }
[JsonProperty("Id")]
public string UserID { get; set; }
}

How can I push or add a list to viewModel

I have created two list models.
Query the DB models to get the list of objects for the two models created.(each returns object list)
Created viewModel to return an object.
Now I want to map internationalRatesSummaries and specificRatesSummaries
I don't know how I can assign/map to the returned object.
GetRateSheet
public RateSheetViewModel GetRateSheet(int resellerId)
{
foreach (var item in rateSheetDetails)
{
var internationalSammaries = (from r in _Context.InternationalRatesSummary where r.RateSheetId == item.RateSheetId select r).ToList();
var specificSammaries = (from r in _Context.SpecificRatesSummary where r.RateSheetId == item.RateSheetId select r).ToList();
var viewModel = new RateSheetViewModel
{
RateSheetId = item.RateSheetId,
ResellerId = item.ResellerId,
TarrifId = item.TarrifId,
RateSheetName = item.RateSheetName,
internationalRatesSummaries = ???????, how do i pass internationalSammaries
specificRatesSummaries = ???????? , how do i pass specificSammaries
};
}
return viewModel; //As a results, i cannot return viewModel
}
ViewModel
This is the view model which i want to return it to the API
public class RateSheetViewModel
{
public IEnumerable<InternationalRatesSummaries> internationalRatesSummaries { get; set; }
public IEnumerable<SpecificRatesSummaries> specificRatesSummaries { get; set; }
}
Since you want a collection of data in your view model, i created a data container for those properties.
public class RateSheetData
{
public string RateSheetName { get; set; }
// other sheet data properties..
public IEnumerable<InternationalRatesSummaries> InternationalRatesSummaries { get; set; }
public IEnumerable<SpecificRatesSummaries> SpecificRatesSummaries { get; set; }
}
and updated the view model to have a collection of that container class.
public class RateSheetViewModel
{
public IEnumerable<RateSheetData> RateSheetDatas { get; set; }
}
And in your controller where you contruct the view model, you create an instance for each of the rateSheetDetails objects. The example below using Linq to enumerate over the rateSheetDetails creating a new instance of RateSheetData for each item.
var datas = rateSheetDetails
.Select(item => new RateSheetData()
{
RateSheetName = item.RateSheetName,
// Assign other specific properties...
InternationalRatesSummaries = (from r in _Context.InternationalRatesSummary where r.RateSheetId == item.RateSheetId select r),
SpecificRatesSummaries = (from r in _Context.SpecificRatesSummary where r.RateSheetId == item.RateSheetId select r)
})
Then assign that collection of RateSheetData to the view model.
var viewModel = new RateSheetViewModel()
{
// other properties..
RateSheetDatas = datas
};
Then in your view you can enumerate over the sheet datas like normal.
foreach (var sheetData in Model.RateSheetDatas)

Unable to display LINQ query output values from controller to view

Controller
public ActionResult Track(string awb)
{
ViewBag.Title = "Track Your Shipment";
ViewBag.ErrorMessage = string.Empty;
ViewBag.ShipmentNo = awb;
FLCourierDetail trackOutput = new FLCourierDetail();
if (awb != null)
{
trackOutput = db.FL_CourierDetail.SingleOrDefault(fLCourierDetail => fLCourierDetail.AWBNumber == awb);
if (trackOutput != null)
{
var courierId = db.FL_CourierDetail.Where(s => s.AWBNumber == awb).Select(s => s.Courier);
var currentStatus = (from c in db.FL_CourierDetail
join s in db.FL_CourierStatus
on c.Courier equals s.CourierId
where c.AWBNumber == awb
select new { awb = c.AWBNumber, staus = s.StatusId, updated = s.StatusId, remark = s.Remark }).ToList();
ViewBag.CurrentStatus = currentStatus;
}
else
{
ViewBag.ErrorMessage = "Shipment number not found.";
}
}
else
{
ViewBag.ErrorMessage = "Please provide valid Shipment number.";
}
return View(trackOutput);
}
View
<div class="col-md-6">
#{
var status = ViewBag.CurrentStatus;
foreach (var item in status)
{
<p>#item</p>
}
}
</div>
If I iterate using foreach or if loop I am able to see the data in debug, but I am not able to write in html.
Debug
Web Page
Error
I am not able to read each value like awb, status, date etc.
Did I miss anything here?
The query result is an anonymous class, within the loop, each item is an object and thus the exception, object has now awb property.
One way to solve this is by defining a class:
public class Status {
public string awb { get; set; }
public int staus { get; set; }
public int updated { get; set; }
public string remark { get; set; }
}
Then your select would look like:
... select new Status { awb = c.AWBNumber, staus = s.StatusId, updated = s.StatusId, remark = s.Remark }).ToList();
Then, within the View:
var status = (List<Status>) ViewBag.CurrentStatus;
Another possible solution is to use strongly typed view model
Firstly, you could create a Model class for returned data;
var currentStatus = (from c in db.FL_CourierDetail
join s in db.FL_CourierStatus
on c.Courier equals s.CourierId
where c.AWBNumber == awb
select new CurrentStatus { awb = c.AWBNumber, staus = s.StatusId, updated = s.StatusId, remark = s.Remark }).ToList();
public class CurrentStatus
{
public string awb { get; set; }
public int staus { get; set; }
public int updated { get; set; }
public string remark { get; set; }
}
Secondly, you can't get output an entire object, you should specify properties which you want to display;
<div class="col-md-6">
#{
var status = (List<CurrentStatus>) ViewBag.CurrentStatus;
foreach (var item in status)
{
<p>#item.awb</p>
<p>#item.staus</p>
<p>#item.updated</p>
<p>#item.remark</p>
}
}
</div>
This is really strange, but in console application the following code actually works:
dynamic even = new List<int> { 1, 2, 3, 4 }
.Where(x => x % 2 == 0)
.Select((x, index) => new { Position = index, Num = x });
// Output:
// Position: 0, Number: 2
// Position: 1, Number: 4
foreach (dynamic item in even)
{
Console.WriteLine($"Position: {item.Position}, Number: {item.Num}");
}
However, in ASP.NET ths doesn't work, and I don't really understand because ViewBag is dynamic, too.
UPDATE
Same question was asked here. A quote from there:
You're returning an instance of an anonymous type. If you weren't using dynamic, your only choice here would be to return an object - the anonymous type is unknown outside of your own function. If you had a variable of type object, you'd get a compile time error that it doesn't have a LogoName property. All you've done with dynamic is defer exactly the same lookup rules until runtime. At runtime, the best type that can be determined is object.
As this answer states, the following must not work, but it works:
static void DoWork()
{
dynamic evens = GetEvens();
foreach (dynamic item in evens)
Console.WriteLine($"Position: {item.Position}, Number: {item.Num}");
}
static dynamic GetEvens() =>
new List<int> { 1, 2, 3, 4 }
.Where(x => x % 2 == 0)
.Select((x, index) => new { Position = index, Num = x });
In this case I return dynamic. However, the code work correctly.

Create the List Type dynamically (or a different kind of collection?)

I am writing a class that reads different kinds of CSV files. It picks out the important information based on Model classes, where the properties of the model class are the column names that I want to grab. For example, I could have an OutlookModel with columns FromAddress and ToAddress. Or I could have a SalesforceModel with totally different columns.
When the reader class parses through the rows and columns, it loads up the cells into an instance of the model class. In the code below, the argument className = OutlookModel. The most relevant lines of code here are the signature and the return...
protected void MapColumns(string row, string className, List<OutlookModel> list)
{
string[] cols = row.Split(',');
// create a model to save the important columns
var model = Activator.CreateInstance(nameSpace, nameSpace + className);
int j = 0;
if (cols.Length > 0)
{
foreach (var c in cols)
{
// is this column index one of our important columns?
if (Ordinals.ContainsKey(j))
{
// this is a column we care about, so set the model property
model.GetType().GetProperty(Ordinals[j]).SetValue(model, c);
}
j++;
}
}
list.Add(model);
}
The problem I am having is the collection of model objects. If I define the object as List< OutlookModel > in the arguments, then the method is not extensible. If I define it as List< object >, then (i think) I have to cast the inside list to use my properties which are all different between the models.
I am fairly new to C#. Is there a better way to capture these different model types into a list/array/collection/whatever so that I can then apply logic to the lists?
So first of all i suggest to add a custom attribute to mark the properties you want to read from the csv, so you don't run into any problem when you have to add something later and you don't have to rely on too many magic strings. Here is my test setup:
class ReadFromCsvAttribute : Attribute { }
class OutlookModel
{
public int DontSetThisValueFromCsv { get; set; }
[ReadFromCsv]
public string FromAddress { get; set; }
[ReadFromCsv]
public string ToAddress { get; set; }
}
class SalesForceModel
{
[ReadFromCsv]
public string Name { get; set; }
[ReadFromCsv]
public string Age { get; set; }
}
static void Main(string[] args)
{
string outlookSample = "Id,FromAddress,ToAddress,Useless\r\n" +
"1,a#b.com,c#d.com,asdf\r\n" +
"3,y#z.com,foo#bar.com,baz";
string salesForceSample = "Id,Name,Age\r\n" +
"1,John,30\r\n" +
"2,Doe,100";
var outlook = ReadFromCsv<OutlookModel>(outlookSample);
var salesForce = ReadFromCsv<SalesForceModel>(salesForceSample);
}
I put together this generic method to read whatever model you want from the data:
static List<T> ReadFromCsv<T>(string data)
{
var objs = new List<T>();
var rows = data.Split(new[] {"\r\n"}, StringSplitOptions.None);
//create index, header dict
var headers = rows[0].Split(',').Select((value, index) => new {value, index})
.ToDictionary(pair => pair.index, pair => pair.value);
//get properties to find and cache them for the moment
var propertiesToFind = typeof (T).GetProperties().Where(x => x.GetCustomAttributes<ReadFromCsvAttribute>().Any());
//create index, propertyinfo dict
var indexToPropertyDict =
headers.Where(kv => propertiesToFind.Select(x => x.Name).Contains(kv.Value))
.ToDictionary(x => x.Key, x => propertiesToFind.Single(p => p.Name == x.Value));
foreach (var row in rows.Skip(1))
{
var obj = (T)Activator.CreateInstance(typeof(T));
var cells = row.Split(',');
for (int i = 0; i < cells.Length; i++)
{
if (indexToPropertyDict.ContainsKey(i))
{
//set data
indexToPropertyDict[i].SetValue(obj, cells[i]);
}
}
objs.Add(obj);
}
return objs;
}
Here's another sample. Since you're new to c#, I've avoided linq and extension methods as much as possible. Just copy it into a console app and run.
Also, I like theHennyy recommendation of using .net attributes to describe a class but only if you have full control of your ecosystem.
public class Account
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class LastNameAccount
{
public string LastName { get; set; }
public string Address { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
Test1();
}
private static void Test1()
{
/*
* defines the result of your CSV parsing.
*/
List<string> csvColumns = new List<string> { "FirstName", "LastName" };
List<List<string>> csvRows = new List<List<string>>() {
new List<string>(){"John","Doe"},
new List<string>(){"Bill", "Nie"}
};
//Map the CSV files to Account type and output it
var accounts = Map<Account>(csvColumns, csvRows);
if (accounts != null)
{
foreach (var a in accounts)
{
Console.WriteLine("Account: {0} {1}", a.FirstName, a.LastName);
}
}
//Map the CSV files to LastNameAccount type and output it
var accounts2 = Map<LastNameAccount>(csvColumns, csvRows);
if (accounts2 != null)
{
foreach (var a in accounts2)
{
Console.WriteLine("Last Name Account: {0} {1}", a.LastName, a.Address);
}
}
}
private static List<T> Map<T>(List<string> columns, List<List<string>> rows)
where T : class, new()
{
//reflect the type once and get valid columns
Type typeT = typeof(T);
Dictionary<int, PropertyInfo> validColumns = new Dictionary<int, PropertyInfo>();
for (int columnIndex = 0; columnIndex < columns.Count; columnIndex++)
{
var propertyInfo = typeT.GetProperty(columns[columnIndex]);
if (propertyInfo != null)
{
validColumns.Add(columnIndex, propertyInfo);
}
}
//start mapping to T
List<T> output = null;
if (validColumns.Count > 0)
{
output = new List<T>();
foreach (var row in rows)
{
//create new T
var tempT = new T();
//populate T's properties
foreach (var col in validColumns)
{
var propertyInfo = col.Value;
var columnIndex = col.Key;
propertyInfo.SetValue(tempT, row[columnIndex]);
}
//add it
output.Add(tempT);
}
}
return output;
}
}

Create expression tree to assign to property of list

This has been plaguing me for days now....
If I have a list of my own object SearchResults and SearchResults contains multiple lists of objects, all of which have a match (bool) property, How can I recreate an expression tree to achieve the following:
//searchResults is a List<SearchResults>
searchResults[i].Comments = searchResults[i].Comments.Select(p1 =>
{
p1.Match = ListOfStringVariable.All(p2 =>
{
string value = (string)typeof(CommentData).GetProperty(propertyName).GetValue(p1);
return value.Contains(p2);
});
return p1;
}).OrderByDescending(x => x.Match);
....
public class SearchResults
{
public IEnumerable<CommentData> Comments { get; set; }
public IEnumerable<AdvisorData> Advisors { get; set; }
}
public class CommentData
{
public string CommentText { get; set; }
public bool Match { get; set; }
}
public class AdvisorData
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Match { get; set; }
}
The expression tree is needed as I won't know the property at compile-time that needs to be assigned, whether it is Comments, Advisors, etc (As this is a simplification of a larger problem). The above example is just for Comments, so how could the same code be used to assign to Advisors as well without having a conditional block?
Many thanks
Update:
So far using reflection we have the below from StriplingWarrior
var searchResult = searchResults[i];
foreach (var srProperty in searchResultsProperties)
{
var collectionType = srProperty.PropertyType;
if(!collectionType.IsGenericType || collectionType.GetGenericTypeDefinition() != typeof(IEnumerable<>))
{
throw new InvalidOperationException("All SearchResults properties should be IEnumerable<Something>");
}
var itemType = collectionType.GetGenericArguments()[0];
var itemProperties = itemType.GetProperties().Where(p => p.Name != "Match");
var items = ((IEnumerable<IHaveMatchProperty>) srProperty.GetValue(searchResult))
// Materialize the enumerable, in case it's backed by something that
// would re-create objects each time it's iterated over.
.ToList();
foreach (var item in items)
{
var propertyValues = itemProperties.Select(p => (string)p.GetValue(item));
item.Match = propertyValues.Any(v => searchTerms.Any(v.Contains));
}
var orderedItems = items.OrderBy(i => i.Match);
srProperty.SetValue(srProperty, orderedItems);
}
However orderedItems is of type System.Linq.OrderedEnumerable<IHaveMatchProperty,bool> and needs to be cast to IEnumerable<AdvisorData>. The below throws error:
'System.Linq.Enumerable.CastIterator(System.Collections.IEnumerable)' is a 'method' but is used like a 'type'
var castMethod = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(new[] {propertyType});
var result = castMethod.Invoke(null, new[] { orderedItems });
where propertyType is type AdvisorData
First, make your types implement this interface so you don't have to do quite so much reflection:
public interface IHaveMatchProperty
{
bool Match { get; set; }
}
Then write code to do something like this. (I'm making a lot of assumptions because your question wasn't super clear on what your intended behavior is.)
var searchResult = searchResults[i];
foreach (var srProperty in searchResultsProperties)
{
var collectionType = srProperty.PropertyType;
if(!collectionType.IsGenericType || collectionType.GetGenericTypeDefinition() != typeof(IEnumerable<>))
{
throw new InvalidOperationException("All SearchResults properties should be IEnumerable<Something>");
}
var itemType = collectionType.GetGenericArguments()[0];
var itemProperties = itemType.GetProperties().Where(p => p.Name != "Match");
var items = ((IEnumerable<IHaveMatchProperty>) srProperty.GetValue(searchResult))
// Materialize the enumerable, in case it's backed by something that
// would re-create objects each time it's iterated over.
.ToList();
foreach (var item in items)
{
var propertyValues = itemProperties.Select(p => (string)p.GetValue(item));
item.Match = propertyValues.Any(v => searchTerms.Any(v.Contains));
}
var orderedItems = items.OrderBy(i => i.Match);
srProperty.SetValue(srProperty, orderedItems);
}

Categories

Resources