I have a list of objects(FilesInfo) that contain objects(LanguageInfo). LanguageInfo is an object that contains further objects for LanguageName and LanguageId. The LanguageName and LanguageId is also an object, that (finally) contains a string value.
I want to group the list of files by the language.
This doesn't work (I suppose a matter of by value/reference comparing magic):
var languageGroupings = data.FilesList.GroupBy(ufi => ufi.LanguageInfo);
(although this is what I am essentially trying to achieve)
This does:
var languageGroupings = data.FilesList.GroupBy(ufi => ufi.LanguageInfo.LanguageName.Value);
Now, the issue is that I don't know whether the LanguageInfo will contain LanguageName, or LanguageCode (or one of other similar properties, ClientLanguageName, ClientLanguageCode) - which is why I basically want to group the files based on all of the properties values nested in LanguageInfo.
How do I do that?
These are the (minimized) classes:
public class UniversalLanguageInfo
{
public int UniversalLanguageInfoId { get; set; }
public UniversalDataElement LanguageCode { get; set; }
public UniversalDataElement LanguageId { get; set; }
public UniversalDataElement LanguageName { get; set; }
public UniversalDataElement ClientLanguageCode { get; set; }
public UniversalDataElement ClientLanguageName { get; set; }
}
public class UniversalDataElement
{
public string Value { get; set; }
public DataFormats DataSource { get; set; }
public string OriginalName { get; set; }
public bool IsExcluded { get; set; }
}
public class UniversalFileInfo
{
public virtual UniversalDataFormat UniversalDataFormat { get; set; }
public UniversalLanguageInfo LanguageInfo { get; set; }
public UniversalDataElement FileName { get; set; }
public UniversalDataElement Id { get; set; }
public UniversalWordcount Wordcount { get; set; }
}
Implement Equals(object) and Equals<T> for your UniversalLanguageInfo and UniversalLanguageElement classes. When you do the GroupBy() you will get the results you're looking for.
In your implementations of these methods, you can choose the level to which they are "equal". In the case you describe, that's a "deep equals", which means you need to implement equals for the entire graph except for the objects in that graph that you're sure have an Equals that is suitable. At each level call the Equals of all the children.
As meJustAndrew below suggests, you will have to implement GetHashCode() because that is good practice. Gian Paolo suggests going the comparer route, which is especially useful if you aren't able to modify the classes in your object graph or don't want general equality to be universally available.
Related
Having an issue with projection and getting child objects to load. The following is simplified code to represent the logic I'm trying to implement, not the actual code.
public class TicketItem
{
public int TicketItemId { get; set; }
public string TicketReason { get; set; }
public Station Station { get; set; }
public TicketOwner TicketOwner { get; set; }
}
public class Station
{
public int StationId { get; set; }
public string Name { get; set; }
}
public class TicketOwner
{
public int TicketOwnerId { get; set; }
public Employee Employee { get; set; }
public Organization Organization { get; set; }
}
public class Employee
{
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Organization
{
public int OrganizationId { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
public class CommonReasons
{
public int CommonReasonId { get; set; }
public string Reason { get; set; }
}
public TicketItem GetById(int id)
{
var query = from i in _dataContext.TicketItems
.Include("Station")
.Include("TicketOwner.Employee")
.Include("TicketOwner.Organization")
join r in _dataContext.CommonReasons on i.TicketReason equals r.CommonReasonId.ToString() into r1
from r2 in r1.DefaultIfEmpty()
where i.TicketItemId == id
select new TicketItem {
TicketItemId = i.TicketItemId,
TicketReason = r2.Reason == null ? i.Reason : r2.Reason,
Station = i.Station,
TicketOwner = i.TicketOwner
};
return query
.AsNoTracking()
.FirstOrDefault();
}
Most the code is self-explanatory. The part that is indirectly causing the trouble would be the relationship between TicketItem.TicketReason property (a string) and the CommonReasons entity. From the user interface side, the end-user has an input field of "Reason", and they can select from "common" reasons or input an adhoc reason. They original developer chose to have the TicketReason property contain either the key ID from the CommonReasons table (if the user selected from drop-down) or the adhoc reason typed in.
So, to handle this logic in the linq query, the only way I have found is to do a left join between TicketItem.TicketReason and CommonReasons.CommonReasonId, then use projection to modify the TicketReason column returning either the common reason text or adhoc text. If there is a different way to do this that would get me around the trouble I'm having with projection/include, I'm all ears.
For the "reason" logic, this query works, returning the proper text. The trouble is that none of the "grand-child" objects are returning, i.e. TicketItem.TicketOwner.Employee, TicketItem.TicketOwner.Organization. How do I get those objects to return also?
Changing the structure of the tables would be an absolute last resort, just based on the amount of code that would have to change. There are other spots in the code that are using the above logic but don't need the child objects.
Any help would be appreciated. Hope I've explained enough.
I have a Book class that contains an InventoryItem and the InventoryItem contains the Book, so the relationship is One to One. If I want to get the Inventory item it will return the Book that contains that InventoryItem and so on. I want to return that InventoryItem as IActionResult.
Book class:
public string Title { get; set; }
public IEnumerable<Author> Authors { get; set; }
public Category Category { get; set; }
public string Isbn { get; set; }
public string PublishingHouse { get; set; }
public string Edition { get; set; }
public InventoryItem InventoryItem { get; set; }
public bool IsDamaged { get; set; }
public bool IsLost { get; set; }
InventoryItem class:
public Guid BookId { get; set; }
public Book Book { get; set; }
public int Number { get; set; }
public AcquisitionDetail AcquisitionDetail { get; set; }
The method that return InventoryItem:
public async Task<IEnumerable<InventoryItem>> GetInventoryItemsAsync()
{
return await schoolLibraryContext.InventoryItems.Include(inventoryItem => inventoryItem.Book)
.Include(inventoryItem => inventoryItem.AcquisitionDetail)
.ToListAsync();
}
Question: How can I include only one Book object without the InventoryItem when I want to return the InventoryItem.
I assume question is about Entity Framework, since it is in tags.
In terms of using data in app:
In such case, you don't really gave to worry about it. Unless you specifically ask about it using Include, Book will be lazy loaded, that is, it won't be sent in initial query, and in case you actually use it, another query will be sent to retrieve it.
Be wary though, lazy loading can save a lot of traffic, but it can also easily cause "N+1" problem, where you constantly send queries even though you could just load whole entity at once. So if you happen to actually use Book later, it might be better idea to use Include and load initially, executing queries is relatively expensive when compared to data traffic.
In terms of sending this data outside (through API)
Don't ever return your entity outside. It's really bad practice even if you actually want to return all of its properties.
If you return your data outside, you should map it to an object of other class, and then return this class. This way you make sure that you don't ever send too much data, e.g. by expanding your entity with properties you don't want to send outside.
In your case it could look like:
public class BookModel
{
public string Title { get; set; }
public IEnumerable<AuthorModel> Authors { get; set; }
public Category Category { get; set; }
public string Isbn { get; set; }
public string PublishingHouse { get; set; }
public string Edition { get; set; }
public bool IsDamaged { get; set; }
public bool IsLost { get; set; }
}
public class InventoryItemModel
{
public Guid BookId { get; set; }
public BookModel Book { get; set; }
public int Number { get; set; }
public AcquisitionDetailModel AcquisitionDetail { get; set; }
}
EDIT: I originally worded this question very poorly, stating the problem was with JSON serialization. The problem actually happens when I'm converting from my base classes to my returned models using my custom mappings. I apologize for the confusion. :(
I'm using .NET Core 1.1.0, EF Core 1.1.0. I'm querying an interest and want to get its category from my DB. EF is querying the DB properly, no problems there. The issue is that the returned category has a collection with one interest, which has one parent category, which has a collection with one interest, etc. When I attempt to convert this from the base class to my return model, I'm getting a stack overflow because it's attempting to convert the infinite loop of objects. The only way I can get around this is to set that collection to null before I serialize the category.
Interest/category is an example, but this is happening with ALL of the entities I query. Some of them get very messy with the loops to set the relevant properties to null, such as posts/comments.
What is the best way to address this? Right now I'm using custom mappings that I wrote to convert between base classes and the returned models, but I'm open to using any other tools that may be helpful. (I know my custom mappings are the reason for the stack overflow, but surely there must be a more graceful way of handling this than setting everything to null before projecting from base class to model.)
Classes:
public class InterestCategory
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<Interest> Interests { get; set; }
}
public class Interest
{
public long Id { get; set; }
public string Name { get; set; }
public long InterestCategoryId { get; set; }
public InterestCategory InterestCategory { get; set; }
}
Models:
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public long? InterestCategoryId { get; set; }
}
Mapping functions:
public static InterestCategoryModel ToModel(this InterestCategory category)
{
var m = new InterestCategoryModel
{
Name = category.Name,
Description = category.Description
};
if (category.Interests != null)
m.Interests = category.Interests.Select(i => i.ToModel()).ToList();
return m;
}
public static InterestModel ToModel(this Interest interest)
{
var m = new InterestModel
{
Name = interest.Name,
Description = interest.Description
};
if (interest.InterestCategory != null)
m.InterestCategory = interest.InterestCategory.ToModel();
return m;
}
This is returned by the query. (Sorry, needed to censor some things.)
This is not .NET Core related! JSON.NET is doing the serialization.
To disable it globally, just add this during configuration in Startup
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
}));
edit:
Is it an option to remove the circular references form the model and have 2 distinct pair of models, depending on whether you want to show categories or interests?
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
Note that each of the models has a nested class for it's child objects, but they have their back references removed, so there would be no infinite reference during deserialization?
I have two classes:
public class SavedQuote
{
public int ID { get; set; }
public string Text { get; set; }
public string Context { get; set; }
public string URL { get; set; }
public string Comment { get; set; }
public string WhereToSearch { get; set; }
public DateTime DateOfAdding { get; set; }
public string OwnerName { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class NoteOnSite
{
public int ID { get; set; }
public string URL { get; set; }
public DateTime DateOfAdding { get; set; }
public string OwnerName { get; set; }
public string Comment { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
I have also two lists: one that represents some "SavedQuotes" and one that has some "NoteOnSites". I need to sort data from those Lists by DateOfAdding and display them in one table on my webiste.
The problem is: I (probably) can't save objects with two different classes in one List<> (I need to do this to sort those objects). What do you advise me to do? How would you solve this problem?
I (probably) can't save objects with two different classes in one List<>
You can, as long as object have a common base class. In C#, all objects have a common base class System.Object, which is enough to store objects of entirely different types in a single list.
A heavyweight approach would be to put a common interface on the objects that you wish to sort:
public interface IWithDate {
public DateTime DateOfAdding { get; set; }
}
public class SavedQuote : IWithDate {
...
}
public class NoteOnSite : IWithDate {
...
}
...
var mixedList = new List<IWithDate>();
However, this may introduce more structure than you wish: making the classes related to each other through a common interface is too much, if all you need is to sort objects of these classes together.
If you wish to sort the objects on a commonly named property without adding any static structure around your classes, you can make a list of dynamic objects, and use DateOfAdding directly:
var mixedList = new List<dynamic>();
mixedList.AddRange(quotes);
mixedList.AddRange(notes);
mixedList.Sort((a, b)=>a.DateOfAdding.CompareTo(b.DateOfAdding));
Try a little Linq using JOIN
List<SavedQuote> savedQuotes = new List<SavedQuote>();
List<NoteOnSite> noteOnSites = new List<NoteOnSite>();
var results = from savedQuote in savedQuotes.OrderBy(x => x.DateOfAdding)
join noteOnSite in noteOnSites.OrderBy(x => x.DateOfAdding)
on savedQuote.ID equals noteOnSite.ID
select new { saved = savedQuotes, note = noteOnSites };
here is the object model.When i try to commit Product to Solr, returning unknown field loca
public class Product
{
[SolrUniqueKey("id")]
public string Id { get; set; }
[SolrField("manu")]
public string Manufacturer { get; set; }
[SolrField("cat")] // cat is a multiValued field
public ICollection<string> Categories { get; set; }
[SolrField("price")]
public decimal Price { get; set; }
[SolrField("inStock")]
public bool InStock { get; set; }
[SolrField("loca")]
public Location Location { set; get; }
}
public class Location
{
[SolrField("zipcode")]
public int Zip { set; get; }
[SolrField("country")]
public string Country { set; get; }
}
Is nested classes legal with solr?
why is it failing to store? when i remove [SolrField("loca")] it works fine.
how do you store such classes?
You cannot do nested classes in Solr. So you will need to flatten the location information into the Product class. However, you can then represent it a nested class within your application, by mapping the data into/out of Solr as needed.
As an example, update your Solr schema to store a loca_zipcode and loca_country field and then map those perhaps in a new SolrProduct class defined like the following:
public class SolrProduct
{
[SolrUniqueKey("id")]
public string Id { get; set; }
[SolrField("manu")]
public string Manufacturer { get; set; }
[SolrField("cat")] // cat is a multiValued field
public ICollection<string> Categories { get; set; }
[SolrField("price")]
public decimal Price { get; set; }
[SolrField("inStock")]
public bool InStock { get; set; }
[SolrField("loca_zip")]
public int Zip { set; get; }
[SolrField("loca_country")]
public string Country { get; set; }
}
Then you can use something like AutoMapper to map the SolrProduct flattened class to your Product class with the nested Location class.
Another alternative would be to use dynamic fields in Solr and the dynamic mapping support in SolrNet using a Dictionary. Please see the SolrNet - Mapping section of the SolrNet wiki for more details and examples.