How to set Properties in a List with Grouping - c#

I have a List<MyObject>.
public class MyObject
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateTime { get; set; }
public decimal Value { get; set; }
public decimal ValueToCalculate { get; set; }
}
The ValueToCalculate is not set yet.
How can I set the ValueToCalculate by the Value of the minimum DateTime grouped by FirstName and LastName.
I've tried various versions with GroupBy/MaxBy/SelectMany and whatever without success.

First do the grouping, then find the object with the minimum date in each group to get the value from and finally update the objects in this group with this value
var groups = list
.GroupBy(o => (o.FirstName, o.LastName));
foreach (var group in groups) {
decimal value = group
.OrderBy(o => o.DateTime)
.First().Value;
foreach (MyObject item in group) {
item.ValueToCalculate = value;
}
}
I build a tuple with the first and last names as group key to make grouping by the two properties possible.
Each group is an enumerable of MyObject we can enumerate to get the objects belonging to this group. To get the object with the minimum DateTime I order the objects by this DateTime and get the first one from which I extract the Value.
It is often easier to do the things step by step instead of trying to merge everything into a single unreadable LINQ query just to save 2 lines of code.

Related

Unable to get product names based on condition

I have two model classes
public class GetProductNameRequest
{
public DateTime ExpiryDate { get; set; }
public bool IsActive { get; set; }
}
public class GetProductNameResponse
{
public List<string> ProductName { get; set; }
}
linq query:
public async Task<List<ProductSKU>> GetProductNames(GetProductNameRequest request)
{
var res = DbSet.Include(t => t.Product).Where(t => t.EndDate >= request.ExpiryDate && t.IsActive == request.IsActive).GroupBy(x => x.Product);
Contracts.Models.ProductNameResponse product = new Contracts.Models.ProductNameResponse();
foreach (var item in res)
{
product.ProductName = item.Key.ProductName;
}
}
So i'm unble get list of Product Names based on Id's plz let me know the solution.
productsku table:
SkuId ProductId Sku MRP CreatedOn UpdatedOn StartDate EndDate IsActive RowVersion
You have a number of problems in your code. Two most obvious reasons why you don't get anything in your product variable is that it is initialized inside the loop, and nothing gets added to it. The code should be something like
Contracts.Models.ProductNameResponse product = new Contracts.Models.ProductNameResponse();
foreach (var item in res)
{
product.ProductName.Add(item.Key.ProductName);
}
I also think your LINQ statement will still throw an error about one cursor not close while another one is open. Search for IQueryable vs IEnumerable issue. But you don't list that as a problem; so maybe in your data source it is fine.

List of Objects, update if found otherwise add

I have the code below which works:
List<XXTJobTableModel> xjobs = filexxts.GroupBy(x=> x.job)
.Select(fx => new JobTableModel
{
job_no = fx.First().job,
emps = fx.GroupBy(x=>x.emp_id).Select(x => new EmployeeTableModel
{
eid = x.First().emp_id,
heds = x.GroupBy(h => h.HED).Select(h => new HEDModel
{
hed = h.First().HED,
hours = h.Sum(c => c.HOURS),
amt = h.Sum(c => c.AMOUNTRATE)
}).ToList()
}).ToList()
}).ToList();
public class JobTableModel
{
public string job_no { get; set; }
public List<EmployeeTableModel> emps { get; set; }
}
public class EmployeeTableModel
{
public string emp_id { get; set; }
public List<HEDModel> heds { get; set; }
}
public class THEDModel
{
public string hed { get; set; }
public decimal hours { get; set; }
public decimal amt { get; set; }
}
I have another List<XXTJobTableModel> yjobs already loaded from another data source. What I would like to do, if the job_no is not found in yjobs, then add the job (with employee and he data) to yjobs. If the job_no is found in yjobs, and the emp_id is not found in the Employee table for that job, then add the employee (and hed data) to that jobs. If the employee is found in that job, then just add the HED data to the employees list.
I do not have a preference if I somehow merge xjobs and yjobs or if I load the xjobs directly into the yjobs list.
thanks
Are you looking for a very concise set of LINQ statements to do this merge operation or just any solution at all? What I would do is loop through one set and use LINQ to compare each item to the other set, and add items resulting from the comparison to a third set. Problems like this you have to decompose into manageable pieces, and then recompose it into a larger solution once the smaller pieces are working. Make an attempt and if you get stuck someone on here will surely be able to fill in the blanks.

RavenDB many to many and indexes

Need a help with RavenDB.
In my web page I want to have such list:
item1 category1
item2 category2
...
and another one:
category1, number of items
category2, number of items
...
My data structures:
public class Item
{
public string Id { get; set; }
public string Name { get; set; }
public string CategoryId { get; set; }
}
public class Category
{
public string Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
}
Index for the first list:
public class Item_WithCategory : AbstractIndexCreationTask<Item>
{
public class Result
{
public string Name { get; set; }
public string CategoryName { get; set; }
}
public Item_WithCategory()
{
Map = items => from item in items
select new
{
Name = item.Name,
CategoryName = LoadDocument<Category>(item.CategoryId).Name
};
}
}
Is this data structure suitable for my case, or will it be better to have Category instead of CategoryId in item structure?
Should I use my index or is there a better solution to take category name?
If my index is good, how to write a correct query? My current try:
Item_WithCategory.Result[] all;
using (var session = DocumentStoreHolder.Store.OpenSession())
{
all = session.Query<Item_WithCategory.Result, Item_WithCategory>().ToArray();
}
but it throws exception stating that return type is item, not result. How to fix it?
You have a couple of options here. You could store both the CategoryId and the CategoryName on the Item entity. This will of course lead to duplicated data (if you still need to store the Category entity), but "storage is cheap" is a popular term these days.The downside of this is that you need to update each Item document of a given category if the category name changes to keep things consistent. A benefit is that you need to do less work to get your desired result.
If you store Category Name on the item as well you don't need a special index to handle the first list, just query on the Items and return what you need. For the second list you need to create a Map/Reduce index on the Item entity that groups on the category.
However, if you need to use the data structure you've given, there are a couple of ways of solving this. First, it's really not recommended to use a LoadDocument inside of an index definition, especially not in a select statement. This might affect indexing performance in a negative way.
Instead, just index the properties you need to query on (or use an auto index) and then use a Result Transformer to fetch information from related documents:
public class ItemCategoryTransformer : AbstractTransformerCreationTask<Item>
{
public ItemCategoryTransformer()
{
TransformResults = results => from item in results
let category = LoadDocument<Category>(item.CategoryId)
select new ItemCategoryViewModel
{
Name = item.Name,
CategoryName = category.Name
};
}
}
public class ItemCategoryViewModel
{
public string Name { get; set; }
public string CategoryName { get; set; }
}
You can use this Transformer with a Query on the Item entity:
using (var session = documentStore.OpenSession())
{
var items = session.Query<Item>()
.TransformWith<ItemCategoryTransformer, ItemCategoryViewModel>()
.ToList();
}
As for the second list, still using your data structure, you have to use a couple of things. First, a Map/Reduce index over the Items, grouped by CategoryId:
public class Category_Items_Count : AbstractIndexCreationTask<Item, Category_Items_Count.Result>
{
public class Result
{
public string CategoryId { get; set; }
public int Count { get; set; }
}
public Category_Items_Count()
{
Map = items => from item in items
select new Result
{
CategoryId = item.CategoryId,
Count = 1
};
Reduce = results => from result in results
group result by result.CategoryId
into c
select new Result
{
CategoryId = c.Key,
Count = c.Sum(x => x.Count)
};
}
}
But as you only have the CategoryId on the Item entity, you have to use a similar transformer as in the first list:
public class CategoryItemsCountTransformer : AbstractTransformerCreationTask<Category_Items_Count.Result>
{
public CategoryItemsCountTransformer()
{
TransformResults = results => from result in results
let category = LoadDocument<Category>(result.CategoryId)
select new CategoryItemsCountViewModel
{
CategoryName = category.Name,
NumberOfItems = result.Count
};
}
}
public class CategoryItemsCountViewModel
{
public string CategoryName { get; set; }
public int NumberOfItems { get; set; }
}
And lastly, query for it like this:
using (var session = documentStore.OpenSession())
{
var items = session.Query<Category_Items_Count.Result, Category_Items_Count>()
.TransformWith<CategoryItemsCountTransformer, CategoryItemsCountViewModel>()
.ToList();
}
As you can see, there are quite a difference in work needed depending on what data structure you're using. If you stored the Category Name on the Item entity directly you wouldn't need any Result Transformers to achieve the results you're after (you would only need a Map/Reduce index).
However, Result Transformers are executed server side and are only executed on request, instead of using LoadDocument inside of an index which is executed every time indexing occurs. Also, and maybe why LoadDocuments inside of index definitions isn't recommended, every change to a document that's referenced with a LoadDocument in an index will cause that index to have to be rewritten. This might lead to a lot of work for the index engine.
Lastly, to answer your last question about why you get an exception when querying: As the actual return type of your index is the document that that's being indexed (in this case Item). To use something else you need to project your result to something else. This can be done by using ".As()" in the query:
Item_WithCategory.Result[] all;
using (var session = DocumentStoreHolder.Store.OpenSession())
{
all = session.Query<Item_WithCategory.Result, Item_WithCategory>()
.As<Item_WithCategory.Result>()
.ToArray();
}
Long post, but hope it helps!

How to set values of entities

I have module which is not mapped to database( sql server) and is only use to generate report.
public class Report
{
public int USERID { get; set; }
public DateTime DateToCal { get; set; }
public string Name { get; set; }
public string Position { get; set; }
public TimeSpan? Intime { get; set; }
public TimeSpan? OutTime { get; set; }
}
I generate a query and fill some properties(USERID, DateToCal, Name, Position, Intime) of Report and some properties of Report remains null ( as OutTime is null)
var query = .....;
Now what I want iterate on items of query( of type Report) and set value for null properties OutTime as
foreach(var items in query)
{
var outtime= from x in con.CHECKINOUTs
where x.USERID == items.USERID && EntityFunctions.TruncateTime(x.CHECKTIME) == EntityFunctions.TruncateTime(items.DateToCal && x.CHECKTYPE == "O"
select x.CHECKTIME
.Single();
items.OutTime= outtime.TimeOfDay;
}
Now problem is, on mousehover to items.OutTime with in foreach there appear value but if I out from foreach and mousehover to query there is still OutTime is null. There not appear value what I set. Is this is possible to set value of entities such way. Or what is my problem?
Thank you.
Save query results locally before iterating over them:
var query = ....ToList();
Looks like in your case query executed two times - first time when you are updating OutTime property, and second time when you are iterating over query items (either looking in debugger or displaying in UI). So, when query executed second time, you see completely new set of objects as query result (which have original null values of OutTime).
BTW Consider to use single JOIN query instead of making separate outtime query for each item in your main query.
Try code something like this:
public class Report
{
public int USERID { get; set; }
public DateTime DateToCal { get; set; }
public string Name { get; set; }
public string Position { get; set; }
private TimeSpan? _intime;
public TimeSpan Intime {
get { return _intime ?? new TimeSpan(0); }
set { _intime = value; }
}
private TimeSpan? _outTime;
public TimeSpan OutTime
{
get { return _outTime ?? new TimeSpan(0); }
set { _outTime = value; }
}
}

LINQ In Clause With Group By And TOP N Records Within Each Group

I have 2 classes as below. If I had a generic list of class Schedule like List how would I write a LINQ query such that I can get all items within this List where SourceId matches 1,2,3...X and StartTime > some date time and then I want to return top 3 elements for each SourceId (ie: group by SourceId)
Since this List would be containing huge number of records, I want to write the most efficient LINQ query
Also I want the final shape of result to be in the form of List
public class Source
{
public int SourceId { get; set; }
public string Name { get; set; }
}
public class Schedule
{
public int SourceId { get; set; }
public Source Source { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
after hours of googling:
var result = r.ScheduleList.
Where(s => sourceIdIntArray.Contains(s.SourceId) &&
s.StartTime >= new DateTime(2011, 09, 20)).
GroupBy(s => s.SourceId).
SelectMany(g => g.OrderBy(s => s.StartTime).
Take(3)).
ToList();
Assuming that:
no 2 sources have the same source Id
you want the top 3 elements ordered by StartTime
you're ok with having a List<KeyValuePair<int,Schedule>> as your result (top 3 grouped by Source Id and still returned as a list)
IEnumerable<Schedule> schedules;
DateTime someDateTime; // date time used for comparison
IEnumerable<int> sourceIds; // required matching source Ids
schedules.OrderBy(x=>x.StartTime)
.Where(x => x.StartTime > someDateTime)
.GroupBy(x=>x.Source.SourceId)
.Where(x=>sourceIds.Contains(x.Key))
.ToDictionary(x=>x.Key, x=>x.Take(3))
.ToList()

Categories