I have a several types of objects, which are I get from database, with different models that have common date property. I want to sort all this models but pull out data only in the end by pages.
Problem is when I add all IQueryable models in dynamic list they pull out all data from database and if there hundreds of records it will take a long time.
What I have already done:
Created base interface and in models inherited from him.
Added all models in dynamic list.
Sort this list by common property.
Added pages with X.PagedList
Example code:
Models:
private interface BaseInfo
{
public DateTime Date { get; set; }
}
public class CompanyEvent : BaseInfo
{
public string Description { get; set; }
public DateTime Date { get; set; }
}
public class SummerEvent : BaseInfo
{
public bool IsActive { get; set; }
public DateTime Date { get; set; }
public string MainPartner { get; set; }
}
public class Birthday : BaseInfo
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public DateTime Date { get; set; }
}
Code
public IPagedList<dynamic> GetDynamicInfo(int pageNumber)
{
int pageSize = 10;
List<dynamic> dynamicList = new List<dynamic>();
IQueryable<CompanyEvent> companyEvents = Employees.Select(a => new CompanyEvent()
{
Date = a.EventDate,
Description = a.Description
});
IQueryable<SummerEvent> summerEvents = Employees.Select(a => new SummerEvent()
{
Date = a.StartEventDate,
IsActive = a.IsActive,
MainPartner = a.Partner.Name
});
IQueryable<Birthday> birthdays = Employees.Select(a => new Birthday()
{
Date = a.DateBorn,
FirstName = a.FirstName,
a.LastName = a.LastName
});
dynamicList.AddRange(companyEvents);
dynamicList.AddRange(summerEvents);
dynamicList.AddRange(birthdays);
var sortedDynamicList = dynamicList.OrderBy(a => ((BaseInfo)a).Date).ToPagedList(pageNumber, pageSize);
return sortedDynamicList;
}
As you are using ToPageList that is bringing all the data in memory. You can use Skip and Take which will get translated to respective sql and do pagination on DB side like:
var sortedDynamicList = dynamicList
.OrderBy(a => ((BaseInfo)a).Date)
.Skip(pageNumber)
.Take(pageSize)
.ToPagedList(pageNumber, pageSize);
Secondly instead of using IPagedList<dynamic> you can use IPagedList<BaseInfo> that will also work same way.
UPDATE:
As in this scenario data is getting loaded from different tables and even different columns in them, one possible way is to load them separately each page of them and then do sort and paging on those like:
IQueryable<CompanyEvent> companyEvents = Employees.Skip(pageNumber).Take(pageSize)
.Select(a => new CompanyEvent()
{
Date = a.EventDate,
Description = a.Description
});
IQueryable<SummerEvent> summerEvents = Employees.Skip(pageNumber).Take(pageSize)
.Select(a => new SummerEvent()
{
Date = a.StartEventDate,
IsActive = a.IsActive,
MainPartner = a.Partner.Name
});
IQueryable<Birthday> birthdays = Employees.Skip(pageNumber).Take(pageSize)
.Select(a => new Birthday()
{
Date = a.DateBorn,
FirstName = a.FirstName,
a.LastName = a.LastName
});
and then do the following:
dynamicList.AddRange(companyEvents);
dynamicList.AddRange(summerEvents);
dynamicList.AddRange(birthdays);
var sortedDynamicList = dynamicList.OrderBy(a => ((BaseInfo)a).Date)
.ToPagedList(pageNumber, pageSize);
Related
How to write EF Core query to fill the properties in certain order?
public record PersonDto : BaseDto //Id is Guid
{
public string Firstname { get; init; }
public string Lastname { get; init; }
public DateOnly Birthday { get; init; }
public IReadOnlyCollection<Guid> AddressesIds { get; init; }
public Guid? MainAddressId { get; init; }
}
internal class Person : SoftDeletableEntity //Id is Guid
{
public Person()
{
Addresses = new HashSet<Address>();
Emails = new HashSet<Email>();
PhoneNumbers = new HashSet<PhoneNumber>();
}
public string Firstname { get; set; }
public string Lastname { get; set; }
public DateOnly Birthday { get; set; }
public ICollection<Address> Addresses { get; set; }
public Guid? MainAddressId => MainAddress?.Id;
public Address? MainAddress => Addresses.Where(adr => adr.IsPrimary).FirstOrDefault();
}
internal sealed partial class Context : DbContext
{
public DbSet<Person> People => Set<Person>();
}
var context = new Context();
var peopleQuery = context.People
.Skip(10)
.Take(10)
.Select(p=> new PersonDto(){
AddressesIds = new HashSet<Guid>(p.Addresses.Select(a => a.Id).Where(a => a.IsPrimary),
MainAddressId = p.MainAddressId,
//bla bla
};
var peopleResult = people.ToList();
At the end of this fragment, peopleResult has all the addresses ids have been loaded, but the MainAddressId of the dto is null.
When I debug the code, MainAddressId is called before populate the list of Addresses, how I change this, or how is this supposed to be done if I'm doing it wrong.
Thanks in advance.
Do you really need MainAddressId and MainAddress properties of the Person. As I understand you have these in the Address model? You can just do the select like this:
var peopleQuery = context.People
.Skip(10)
.Take(10)
.Select(p=> new PersonDto(){
AddressesIds = new HashSet<Guid>(p.Addresses.Select(a=> a.Id),
MainAddressId = p.Addresses.FirstOrDefault(a=> a.IsPrimary),
};
var peopleResult = people.ToList();
You need only the Addresses, then you can easily filter which is the main one, and assign it to the DTO's property.
I Think the problem is that calculated properties in your entity. I had a similar problem a few months ago. I just removed that properties from my Entity an put it in my DTOs.
I am trying to group a column and form the the rest of the columns as child, hierarchical data:
I am trying to group by Code and form the parent and child relationship from a flat list, below is the hierarchical data I am trying to form:
source list:
public class ItemAssignmentFlatList
{
public int Code { get; set; }
public string Type { get; set; }
public string Description { get; set; }
public int ItemCode{ get; set; }
public DateTime EffectiveDate{ get; set; }
public string Area{ get; set; }
public string TaxCode{ get; set; }
public string LocationId { get; set; }
}
Need to convert above flat list into below List of hierarchical data:
public class ItemInfo
{
public int Code { get; set; }
public string Type { get; set; }
public string Description { get; set; }
public List<TaxInfo> TaxPlan { get; set; }
}
public class TaxPlan
{
public int ItemCode{ get; set; }
public DateTime EffectiveDate{ get; set; }
public string Area{ get; set; }
public string TaxCode{ get; set; }
public string LocationId { get; set; }
}
Need hierarchical list with above flat data list with C# extension methods.
I have below code, but looking for clean code to reduce number of lines:
var items= results.GroupBy(x => new { x.Code, x.Type });
List<ItemInfo> result = new List<ItemInfo>();
foreach (var group in items)
{
var taxPlans = group.
Select(y => new TaxPlan
{
TaxArea = y.TaxArea,
ItemCode = y.ItemCode
});
var itemInfo= new ItemInfo
{
Code = group.FirstOrDefault().Code,
Type = group.FirstOrDefault().Type,
Description = group.FirstOrDefault().Description,
TaxPlan = taxPlans.ToList()
};
result.Add(itemInfo);
}
Something like this?:
var input = new List<ItemAssignmentFlatList>(){
new ItemAssignmentFlatList{
Code = 1,
Area = "a"
},
new ItemAssignmentFlatList{
Code = 1,
Area = "b"
},
new ItemAssignmentFlatList{
Code = 2,
Area = "c"
}
};
input
.GroupBy(
x => x.Code,
(int code, IEnumerable<ItemAssignmentFlatList> items) =>
{
var first = items.FirstOrDefault();
var key = new ItemInfo
{
Code = first.Code
//, ...
};
var plan = items.
Select(y => new TaxPlan
{
Area = y.Area
//, ...
});
return new
{
key = key,
items = plan
};
}
).Dump();
Whenever you have a sequence of similar object, and you want to make "Items with their SubItems", based on common properties in your source sequence, consider to use one of the overloads of Enumerable.GroupBy
Because you don't just want "Groups of source items" but you want to specify your output, consider to use the overload that has a parameter resultSelector.
parameter keySelector: what should all elements in a group have in common
parameter resultSelector: use the common thing, and all elements that have this common thing to make one output element.
.
IEnumerable<ItemAssignmentFlatList> flatItemAssignments = ...
IEnumerable<ItemInfo> items = flatItemAssignments
// make groups with same {Code, Type, Description}
.GroupBy(flatItemAssignment => new {Code, Type, Description},
// parameter resultSelector: take the common CodeTypeDescription,
// and all flatItemAssignments that have this common value
// to make one new ItemInfo
(codeTypeDescription, flatItemAssignmentsWithThisCodeTypeDescription) => new ItemInfo
{
Code = codeTypeDescription.Code,
Type = codeTypeDescription.Type,
Description = codeTypeDescription.Description,
TaxPlans = flatItemAssignmentsWithThisCodeTypeDescription
.Select(flatItemAssignment => new TaxPlan
{
ItemCode = flatItemAssignment.ItemCode,
EffectiveDate = flatItemAssignment.EffectiveDate,
Area = flatItemAssignment.Area,
...
})
.ToList(),
});
Using RavenDB v4.2 or higher, I want to setup an index that queries another collection. Basically, reproduce a WHERE IN clause in the mapping part of the index.
The models below represent two collections. Here each User has a collection of Device ID's:
class Device {
public string Id { get; set; }
public string Name { get; set; }
}
class User {
public string Id { get; set; }
public string BlogPostId { get; set; }
public List<string> DeviceIds { get; set; }
}
Now consider the following index as an example on what I'm trying to achieve:
public class DeviceIndex : AbstractIndexCreationTask<Device, DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
Map = devices => from d in devices
select new Result
{
Id = d.Id,
DeviceName = d.Name,
HasUser = ... ?, // How to get this from Users collection?
UserCount = ... ? // same...
};
}
How do I fill the HasUser true/false and UserCount properties in this index? E.g. how can I query the 'User' collection here?
Please note that this example is seriously simplified for brevity. I'm not so much interested in workarounds, or changing the logic behind it.
As #Danielle mentioned you need to use a mutli-map-index and reduce the result.
Here is a working example
public class DeviceIndex : AbstractMultiMapIndexCreationTask<DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
AddMap<User>(users => from u in users
from deviceId in u.DeviceIds
let d = LoadDocument<Device>(deviceId)
select new Result
{
Id = d.Id,
HasUser = true,
UserCount = 1,
DeviceName = d.Name,
});
AddMap<Device>(devices => from d in devices
select new Result
{
Id = d.Id,
HasUser = false,
UserCount = 0,
DeviceName = d.Name,
});
Reduce = results => from result in results
group result by new { result.Id } into g
select new Result
{
Id = g.First().Id,
DeviceName = g.First().DeviceName,
HasUser = g.Any(e => e.HasUser),
UserCount = g.Sum(e => e.UserCount),
};
}
}
and you can call it like this
var result = await _session.Query<DeviceIndex.Result, DeviceIndex>().ToListAsync();
If you would have a Users List in the Device class List<string> Users
a list that contains the document ids from the Users collection then you could Index these Related documents.
See:
https://demo.ravendb.net/demos/csharp/related-documents/index-related-documents
Or do the opposite,
Create an index on the Users collection, and index the related Device info
Without changing current models,
You can create a Multi-Map Index to index data from different collections.
https://ravendb.net/docs/article-page/4.2/csharp/indexes/multi-map-indexes
https://ravendb.net/docs/article-page/4.2/csharp/studio/database/indexes/create-multi-map-index
https://ravendb.net/learn/inside-ravendb-book/reader/4.0/10-static-indexes-and-other-advanced-options#querying-many-sources-at-once-with-multimap-indexes
In my project i have : countries and CountryEditModel.
public class countries
{
public int id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
public class CountryEditModel
{
public int id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public bool isvalid{ get;set; }
}
countries is my domain model which is binded with entity framework and countryEditModel is the model which i use in my views.
How can i fill values from countries to countryEditModel. I actually want to bind list of all countries to dropdownlist in my view and I don't want to use my countries domain model directly in my view.
To solve i have done this
var countryDomain = context.Country.Select(c => c).ToList();
var countrylist = new List<CountryEditModel>();
var countrymodel = new CountryEditModel();
foreach (var country in countryDomain)
countrymodel = new CountryEditModel()
{
Code = country.Code,
Name = country.Name,
id = country.id
};
countrylist.Add(countrymodel);
Is there any better way?
Answer:
Actually this is what i exactly wanted to do
var countryViewModel = context.Country.Select(c => new CountryEditModel
{
Code = c.Code,
Name = c.Name,
id = c.id
}).ToList();
As indicated by the #rohitsingh this is what he exactly wanted to do
var countryViewModel = context.Country.Select(c => new CountryEditModel
{
Code = c.Code,
Name = c.Name,
id = c.id
}).ToList();
I can't seem to find the correct where clause to get only the items I need.
I have Divisions, these contain Categories en these contain Items.
These are the classes:
public class Division {
public string Description { get; set; }
public List<Category> Categories { get; set; }
}
public class Category : IdEntity
{
public string Description { get; set; }
public Guid DivisionId { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public Guid CategoryId { get; set; }
public DateTime Date { get; set; }
public string Description { get; set; }
}
What I need is a division with the Id in a parameter, all the categories from this division and the items that have a certain date for each category.
So right now I do this:
public Division GetFullDivisionByIdAndDate(Guid id, DateTime date)
{
using (new ChangeTrackingScope(ChangeTracking.Disabled))
{
var divisionGraph = new Graph<Division>().Include(d => d.Categories.Select(c => c.Items));
var division = _divisionDL.GetFullDivisionByIdAndDate(id, divisionGraph, date);
return division;
}
}
And than in the DL I do
public Division GetFullDivisionByIdAndDate(Guid id, Graph<Division> graph, DateTime date)
{
using (var db = new ContextScope<DatabaseContext>())
{
var q = graph.ApplySetReferences(db.Context.Divisions).AsNoTracking();
return q.SingleOrDefault(p => p.Id == id);
}
}
Here I get the division with all its categories (so far so good) but I also get all items and I need only the items with the date given as parameter. Anyone has an idea how to do this?
Your code is not very accessible because of a few missing methods (Graph, ApplySetReferences) so I can't hook into it. But I can show a common way to query an object graph, which is by navigation properties. In you model, a basic query body could look like this:
from d in Divisions
from c in d.Categories
from i in c.Items
select new { Div = d.Description, Cat = c.Description, Item = i.Description }
Starting from here you can add other filters and properties, like
from d in Divisions.Where(div => div.Id == id)
from c in d.Categories
from i in c.Items.Where(item => item.Date == date)
select new { ... }
Or if you want a result with items in a filtered collection:
from d in Divisions.Where(div => div.Id == id)
from c in d.Categories
select new new { Div = d.Description,
Cat = c.Description,
Items = c.Items.Where(item => item.Date == date)
}