Reflection and recursion in array - StackOverflowException - c#

I have two class :
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool isActif { get; set; }
public Product[] Product { get; set; }
}
public class Product
{
public string Id { get; set; }
}
And two instances :
Customer Customer1 = new Customer
{
FirstName = "FirstName1",
LastName = "LastName1",
isActif = true,
Product = new Product[]
{
new Product()
{
Id = "1"
}
}
};
Customer Customer2 = new Customer
{
FirstName = "FirstName2",
LastName = "LastName2",
isActif = false,
Product = new Product[]
{
new Product()
{
Id = "2"
}
}
};
I have one method who compare all properties of the two instances :
But when I get to the property Product, I have an StackOverflowException generated. Why ? And how to loop the property if it's a array ?
EDIT : When I use the List there is not StackOverflowException but System.Reflection.TargetParameterCountException. How to loop the property if it's a array

in:
foreach (PropertyInfo propertyInfo in
objectType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.CanRead))
Change your where condition for:
.Where(x => x.CanRead && x.Name != "SyncRoot")
SyncRoot documentation
If you do:
Console.WriteLine(Customer1.Product.Equals(Customer1.Product.SyncRoot));
You will see that Customer1.Product is equal to its SyncRoot property.
Therefore, when you use your AreEquals method on the Product Property, you will reach the SyncRoot property, which is the same than Product Property.
Then you will never be able to leave the loop. (Because SyncRoot has a SyncRoot property pointing on itself)

Related

EF Core query and projection to dto doesn't fill the property

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.

How to create a dictionary of field values/counts from an observable collection?

I have an ObservableCollection<CustomerModel> Customers, that holds a Country field. What I want to do is, create an observable collection of type PiePointModel. In order to store the country name and number of occurrences of that country name.
So I set up an ObservableCollection<PiePointModel> CountryRatioCollection, where PiePoint holds a name and amount.
Then I tried to assign that collection to my Customers, by converting it to a dictionary holding the required values:
CountryRatioCollection = new ObservableCollection<PiePointModel>();
CountryRatioCollection = Customers.GroupBy(i => i.Country).ToDictionary(g => g.Key, g => g.Count());
But I get an error stating that this can't be implicitly converted:
Error 2 Cannot implicitly convert type 'System.Collections.Generic.Dictionary<string,int>' to 'System.Collections.ObjectModel.ObservableCollection<MongoDBApp.Models.PiePointModel>'
I understand that this is because the Dictionary type is not the same as my PiePoint model class.
Can anyone offer advice on making query and conversion?
This is the PiePoint class for reference, that holds the name and amount:
public class PiePointModel
{
public string Name { get; set; }
public int Amount { get; set; }
}
And this is the CustomerModel that holds the country field:
public class CustomerModel
{
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("firstName")]
public string FirstName { get; set; }
[BsonElement("lastName")]
public string LastName { get; set; }
[BsonElement("email")]
public string Email { get; set; }
[BsonElement("address")]
public string Address { get; set; }
[BsonElement("country")]
public string Country { get; set; }
public override string ToString()
{
return Country;
}
}
You should use Select (not ToDictionary) and create PiePointModel for each group.
IEnumerable<PiePointModel> piePoints = Customers.GroupBy(i => i.Country).Select(s => new PiePointModel()
{
Name = s.Key,
Amount = s.Count()
});
CountryRatioCollection = new ObservableCollection<PiePointModel>(piePoints);
Also notice that I used: CountryRatioCollection = new ObservableCollection<PiePointModel>(..) because CountryRatioCollection is of type ObservableCollection and you cannot assign here dictionary like in your example.
Constructor of ObservableCollection<T> can take IEnumerable<T> - I used it here.
Other way is use loop and add new PiePointModel to collection
CountryRatioCollection = new ObservableCollection<PiePointModel>();
var groups = Customers.GroupBy(i => i.Country);
foreach(var gr in groups)
{
PiePointModel piePointModel = new PiePointModel()
{
Name = gr.Key,
Amount = gr.Count()
};
CountryRatioCollection.Add(piePointModel);
}

How to get foreign key objects with entity framework into a custom object?

I am trying to make a select from the database using the entity framework 5.0.
I have a table called Persons that is referenced by PersonsImages, so basically one record from Persons can have many PersonsImages.
I've made a select statement that gives the Persons, but I would also like to get the PersonsImages as a List<PersonsImages>, and put them in a custom object.
This is the code that I have so far:
var person = new Persons();
using (var context = new PersonEntities())
{
person = context.Persons.Where(x => x.personId == 555)
.Select(xxx => new Persons
{
personName = xxx.personName,
personLastName = xxx.personLastName,
PersonImages = xxx.PersonsImages // there's an error here
})
.FirstOrDefault();
}
The Persons and the PersonsImages classes look like that (they are copies of the ones generated by the entity framework):
public partial class Persons
{
public Persons()
{
this.PersonsImages = new HashSet<PersonsImages>();
}
public string personName { get; set; }
public string personLastName { get; set; }
public virtual ICollection<PersonsImages> PersonsImages { get; set; }
}
public partial class PersonsImages
{
public string imageName { get; set; }
public byte[] image { get; set; }
public virtual Persons Persons { get; set; }
}
I know I can make a second select and "manually" find them, but isn't it possible to do it in one, just as what the entity framework normally does?
Assuming your error is "can't construct an object in a LINQ to Entities query" - project into an anonymous type, call the ToArray() method to enumerate the results, then project into new instances of Persons:
person = context.Persons.Where(x => x.personId == 555)
.Select(xxx => new
{
personName = xxx.personName,
personLastName = xxx.personLastName,
PersonImages = xxx.PersonsImages
})
.ToArray() // data now local
.Select(xxx => new Persons
{
personName = xxx.personName,
personLastName = xxx.personLastName,
PersonImages = xxx.PersonsImages
})
.FirstOrDefault();

Linq anonymous type with list object property

I have an object lets say its classrooms that is returned from the repository but I use an anonymous type for my View so I convert it like so
return from P in db.ClassRooms
where P.LocationId == LocationId && P.IsApproved==true
select new ClassRoomsViewModel
{
Id = P.Id,
Created = P.CreatedOn,
IsApproved = P.IsApproved,
IsDeleted = P.IsDeleted,
Desks = ??
}
the problem is I am not sure how to handle the desk object.
In my ClassRoomsViewModel class Desks is a list object
public class ClassRoomsViewModel{
public long Id { get; set; }
public DateTime Created { get; set; }
public List<DeskViewModel> Desks { get; set; }
}
public class DeskViewModel{
public long Id { get; set; }
public string Name{ get; set; }
}
The classrooms dataobject is link as a reference to the Desk Object.
so from the above linq query P.Desks.Name will return the name of all objects in the classroom for the linq query
You need to take the collection of desks from the data model, convert each one to a DeskViewModel, and convert the resulting sequence to a List<T>.
That would look something like
p.Desks.Select(d => new DeskViewModel { ... }).ToList()
If P.Desks.Name and P.Desks.Id are arrays you could do it like this with zip.
return from P in db.ClassRooms
where P.LocationId == LocationId && P.IsApproved==true
select new ClassRoomsViewModel
{
Id = P.Id,
Created = P.CreatedOn,
IsApproved = P.IsApproved,
IsDeleted = P.IsDeleted,
Desks = P.Desks.Name.Zip(P.Desks.Id,
(n, i) => new DeskViewModel { Id = i, Name = n });
}

Map reduce in RavenDb over 2 collections with child collection

I have 2 different object types stored in RavenDb, which are a parent/child type relationship, like this in JSON:
Account/1
{
"Name": "Acc1",
}
Items/1
{
"Account": "Account/1",
"Value" : "100",
"Tags": [
"tag1",
"tag2"]
}
Items/2
{
"Account": "Account/1",
"Value" : "50",
"Tags": [
"tag2"]
}
Note that I don't want to store these in the same document, as an account may have thousands of items.
I am trying to write a map/reduce index that will return me something like:
{
"Account": "Acc1",
"TagInfo": [
{ "TagName" : "tag1",
"Count" : "1", //Count of all the "tag1" occurrences for acc1
"Value" : "100" //Sum of all the Values for acc1 which are tagged 'tag1'
},
{ "TagName" : "tag2",
"Count" : "2", //Two items are tagged "tag2"
"Value" : "150"
}]
}
i.e. a list of all the distinct tag names along with the number of each and their value.
I think I need to use a multi-map to map the Account and Items collections together, but I can't figure out the reduce part to create the "TagInfo" part of the result.
Is this possible, or am I modelling this all wrong in Raven?
EDIT:
The class I want to retrieve from this query would look something like this:
public class QueryResult
{
public string AccountId {get;set;}
public TagInfo Tags {get;set;}
}
public class TagInfo
{
public string TagName {get;set;}
public int Count {get;set;}
public int TotalSum {get;set;}
}
You can't use a Multi Map/Reduce index for that because you want one map on the tags and the other on the account. They don't have a common property, so you can't have a multi maps/reduce here.
However, you can use TransformResult instead. Here's how to do it:
public class Account
{
public string Id { get; set; }
public string Name { get; set; }
}
public class Item
{
public string Id { get; set; }
public string AccountId { get; set; }
public int Value { get; set; }
public List<string> Tags { get; set; }
}
public class TagsWithCountAndValues : AbstractIndexCreationTask<Item, TagsWithCountAndValues.ReduceResult>
{
public class ReduceResult
{
public string AccountId { get; set; }
public string AccountName { get; set; }
public string Tag { get; set; }
public int Count { get; set; }
public int TotalSum { get; set; }
}
public TagsWithCountAndValues()
{
Map = items => from item in items
from tag in item.Tags
select new
{
AccountId = item.AccountId,
Tag = tag,
Count = 1,
TotalSum = item.Value
};
Reduce = results => from result in results
group result by result.Tag
into g
select new
{
AccountId = g.Select(x => x.AccountId).FirstOrDefault(),
Tag = g.Key,
Count = g.Sum(x => x.Count),
TotalSum = g.Sum(x => x.TotalSum)
};
TransformResults = (database, results) => from result in results
let account = database.Load<Account>(result.AccountId)
select new
{
AccountId = result.AccountId,
AccountName = account.Name,
Tag = result.Tag,
Count = result.Count,
TotalSum = result.TotalSum
};
}
}
Later then, you can query like this:
var results = session.Query<TagsWithCountAndValues.ReduceResult, TagsWithCountAndValues>()
.Where(x => x.AccountId == "accounts/1")
.ToList();
OK, so I figured out a way to do this in an acceptable manner that builds on Daniel's answer, so I'll record it here for any future travellers (probably myself!).
I changed from trying to return one result per account, to one result per account/tag combination, so the index had to change as follows (note the group by in the reduce is on 2 properties):
public class TagsWithCountAndValues : AbstractIndexCreationTask<Item, TagsWithCountAndValues.ReduceResult>
{
public class ReduceResult
{
public string AccountId { get; set; }
public string AccountName { get; set; }
public string TagName { get; set; }
public int TagCount { get; set; }
public int TagValue { get; set; }
}
public TagsWithCountAndValues()
{
Map = items => from item in items
from tag in item.Tags
select new ReduceResult
{
AccountId = item.AccountId,
TagName = tag,
TagCount = 1,
TagValue = item.Value
};
Reduce = results => from result in results
where result.TagName != null
group result by new {result.AccountId, result.TagName}
into g
select new ReduceResult
{
AccountId = g.Key.AccountId,
TagName = g.Key.TagName,
TagCount = g.Sum(x => x.TagCount),
TagValue = g.Sum(x => x.TagValue),
};
TransformResults = (database, results) => from result in results
let account = database.Load<Account>(result.AccountId)
select new ReduceResult
{
AccountId = result.AccountId,
AccountName = account.Name,
TagName = result.TagName,
TagCount = result.TagCount,
TagValue = result.TagValue,
};
}
}
As before, querying this is just:
var results = session
.Query<TagsWithCountAndValues.ReduceResult, TagsWithCountAndValues>()
.ToList();
The result of this can then be transformed into the object I originally wanted by an in-memory LINQ query. At this point the number of results that could be returned would be relatively small, so performing this at the client end is easily acceptable. The LINQ statement is:
var hierachicalResult = from result in results
group new {result.TagName, result.TagValue} by result.AccountName
into g
select new
{
Account = g.Key,
TagInfo = g.Select(x => new { x.TagName, x.TagValue, x.TagCount })
};
Which gives us one object per account, with a child list of TagInfo objects - one for each unique tag.

Categories