EF Code First - Linq to Entities Union EqualityComparer - c#

I have two IEnumerable collections that I would like to union.
One selects news objects that are associated with a particular category. When a user is filtering by a category I would also like news articles that have been tagged with another category to be displayed.
So I have another query that returns news objects that are tagged with the particular sub category.
Now I would like to union the two collections, removing duplicates (as a news article associated to the main category, may also be tagged with the second category).
var catNews = model.Category.News.SelectMany(n => n.News); //get news article associated to the category
var tagNews = _nr.GetNews(model.Category.relatedCategoryName); //this selects news by tags - which I want as the related category name
model.News = catNews.Union(tagNews).OrderByDescending(p => p.Date); //union the two collections
However, model.News now contains two identical news articles and I am not sure why as union should use the default equality comparer?
Am I doing something wrong here? I am using EF Code First and my primary key is the news id.
The way I have got round this issue is by passing in a list of the catNews id to the GetNews function and excluding them
if (excludeIds != null)
q = q.Where(n => !excludeIds.Contains(n.ID));
But I am not sure why I have to this when I thought union would remove the identical articles?

I guess that you are not loading these two collections from the same instance of the entity framework context. Default equality comparer will compare references and if you use the same context it will indeed return same News instance in both collections when Id is matching but if you use different contexts each collection will contain its own News instances and Union will do the same as Concat. In such case you must override Equals (and GetHaschCode) in your News entity to compare Id or use custom comparer.

Related

C# Join two tables using Include. Data comes from a Model

I am very new with C# and need some help. I am working on someone elses code and they are pulling data from a Model. I am trying to join two tables and need to use Include but the error is '==' cannot be applied to Guid and IQueryable. Could someone help with this please. Thanks in advance!
Yes, I am.
.Where() represents your filter. .Select() represents what you want back. If you just want the entities back you don't need a .Select().
If you have an association between menu items and MenuItemProgramData, for example, a MenuItem holds a reference to a MenuItemProgramData then you don't even need the first ID select statement:
return context.DbMenuItems
.Where(x => x.MenItemsProgramData.Plu == plu);
Note: If your context defines DbSet<T> for your various top level entities, you can just use context.Ts rather than .GetItems<T>.
If the relationship exists then this is the preferred approach. Let SQL do the work. The consumer of your method can further .Select() the applicable data, sort it, paginate it, and even append .Include() if you do want to interact with the entire entity graph.
If you don't have a relationship between the menu item and that program data, and know that the # of item IDs from the first query will remain relatively small (say, sub-100) then:
var itemIds = context.DbMenuItemProgramDatas
.Where(x => x.Plu == plu)
.Select(x => x.MenuItemId)
.ToList();
Without the .ToList() you are dealing with an IQueryable which EF would potentially still attempt to translate to SQL statements when later consumed. By using .ToList() it will execute the SQL and populate a List<int>. (Assuming the menu item ID is an int)
To get the IQueryable menu item data rows:
return context.DbMenuItems
.Where(x => itemIds.Contains(x.Id));
And that is it.
Edit: Based on the comment "I want to return a field named ParentId to know if it is empty or not. That's all but I need both tables linked to get that answer."
Additionally, looking back at the original code, the naming of the method is a bit misleading. GetItemProgramDataForSubItems implies returning MenuItemsProgramData rather than MenuItems... However, if ParentId is a property of MenuItem, then the caller of this method can use:
var hasParentId = context.GetItemProgramDataForSubItems(plu)
.Any(x => x.ParentId.HasValue);
If the ParentId is on the MenuItemsProgramData:
var hasParentId = context.GetItemProgramDataForSubItems(plu)
.Any(x => x.MenuItemsProgramData.ParentId.HasValue);
Beyond that, you may want to elaborate on what your entities and relationships look like, and what exactly you aim to accomplish from your method or business logic.

Use tuples with Entity Framework Contains statement

I have an entity whose reference is a composite of an identifier and an environment. I want to implement a function to allow the user to pass a list of tuples of (ID, Environment) and return back the required entities. Is it possible to use Contains() in such scenarios? How? With a simple reference, it is as simple as
model.MyEntities.Where(e => myIds.Contains(e.Id))
EDIT: To clarify, I am not looking for how to use Contains() method to retrieve a list of IDs; the line I wrote above does this. What I am looking for is to be able to retrieve a list of entities matching tuples of (ID, Environment) rather than just ID.
Latest version of Entity Framework allows you to do a Contains on an array of primitive types (I think it works on an IEnumerable too now, I haven't tried).
If you match solely on your Id (ie, your match is good if one of the Tuple's ID is the MyEntity.Id, this will work (I'm using Tuple losely here as your case seems to be an actual object; Tuple only has ItemN properties):
var containedIds = yourListOfTuples.Select(t => t.Id).ToArray();
model.MyEntities.Where(e => containedIds.Contains(e.Id));
This will effectively translate into a WHERE ... IN ([the Ids in containedIds]) statement in SQL.

LINQ get collection in anonymous type

i have the following LINQ statement:
var query =(from item in _itemRepository.FindAll()
where item.Id == "20649458"
from singelitem in item.ListOfChildren
where singelitem.Property == "singelitem"
from manyitems in item.ListOfChildren
where manyitems.Property == "many"
select new
{
item.Id,
singelitem,
manyitems
});
var result = query.ToList();
Tasks is a collection of objects and the where clause tasks.Property == "something" matches several items in the collection, but when i use a anonymous type in the select, i only get back one item (the first) of the matching results instead of a collection of Tasks. How can i get back all the matching tasks in a collection?
Edit:
What really happends is that i get flat objects, (just like a db result set from a join statement).
When you don't use anonymous type you're dealing with entity class which lazy loads the tasks when you access them. If you want to load tasks with your results try using Include method to eager load children. See How do you construct a LINQ to Entities query to load child objects directly, instead of calling a Reference property or Load()
This is the proper behavior of Linq. In fact what you are expecting is not possible. You are expecting a single item matching item.Id == "123"; and what if more than one? it just creates an anonymous item for each matched item. Just think of changing the first "from" statement with the second one; what would you expect?
Also, there is no relationship between first "from" statement and the second one which makes this query a bit "strange". Why not just splitting the query into 2; and creating a new object with the desired properties?

EF Query with multiple contexts

I have two EF contexts _inventoryContext and _auctionContext.
_inventoryContext has a property called Items and _auctionContext has one called Auctions. Items is a collection of Item objects which each contain a Guid to identify them uniquely. The Auctions property is a collection of Auction objects which each contain a Guid InventoryReference that refers to one of the elements of Items.
What I want to do is get a list of all inventory items that are not part of an auction. How do I do this?
This may be of help to you.
Alternatively, you can do this in 2 steps: First get a collection of GuidReferences from your Auction, and then fetch the Items whose Guid's are included in the collection. There will be a performance hit because of the extra query, and because the framework will need to allocate the Guid collection. But depending on the Item collection size, that may not be a big deal for you.
Another possibility would be to create a view in one database/context that pulls the data from the other. This would be read-only, however.
There is a better solution in EF Core
You can create view as named Auctions one of your context and map the DbSet model in your code. So you can use other context model and table in another context. But you must ensure your db user can access those two contexts.
For example in _inventoryContext you can define like that.
public virtual DbSet<Auction> Auctions { get; set; }
modelBuilder.Entity<Auction>(entity =>
{
entity.ToView("vwAuctions");
}
It's provides you something like that
var result= from x in _inventoryContext.InventoryReference
join y in _inventoryContext.Auctions on x.Id equals y.InvRef
select x;

How to model POCO object from a multilanguage database with Entity Framework 4?

I am building a new project from scratch.
I created a db where I have consistently applied a db structure that I explain with a short self-explanatory example:
Table Item -> (Id, Name) -> Contains general information
Table ItemInfo -> (Item_Id, Language, Description) -> Contains the language dependent information.
Id and Item_Id are connected with a foreign key relationship.
My idea was to model it in a way that I would end up using only a single POCO object "Item" populated through Entity Framework. This object would contain only the public properties: Id, Name and Description.
The language will be hidden to the code using this object, the object itself should have the responsibility to give the correct description depending on a global variable that contains the language.
I have tried a few ways to do this and always ended up having problems because Entity Framework wouldn't allow this scenario. I always had to retrieve info for ALL languages and not only the current one or use 2 different queries.
So at the end the solution I started to use was to let a T4 template create both Item and ItemInfo and then I manually added a code similar to this:
public partial class Item
{
private ItemInfo _itemInfo = null;
private ItemInfo itemInfo
{
get
{
if (_itemInfo == null) _itemInfo = ItemInfoes.Single(p => p.Language == GlobalContext.Language);
return _itemInfo;
}
}
public Description
{
get { return itemInfo.Description; }
set { itemInfo.Description = value;}
}
}
With this code I added the additional properties from ItemInfo to Item and selected the correct language as per my requirements.
Do you think this is a good solution? How would you solve this problem instead?
However, running sql profiler I can see that 2 different sql queries are used to populate the Item object, one that queries the Item table and another that queries the ItemInfo.
Can the same scenario be achieved with a single query that does a join between the 2 tables? (I am afraid of the long term performance hit and also this is how I would do it without an ORM).
Any suggestion will be welcome, I have many years of programming experience but I am a newbie with Entity Framework and ORMs in general.
Please help.
You're not showing how you fetch the Item objects, but generally I don't see a problem with fetching everything in one query. You've got several options.
You can do a projection (but not onto a mapped entity - in this example I project onto an anonymous object):
context.
Items.
Select(item => new
{
Id = item.Id,
Name = item.Name,
Description = item.
ItemInfo.
Where(info => info.Language == YourGlobalLang).
Select(info => info.Description).
FirstOrDefault()
};
(This has been edited to use FirstOrDefault instead of Single - see comment discussion with #Craig Stuntz)
This will return a list of all Items - you can add a Where clause to filter.
Or you can fetch it the other way around (starting with ItemInfo):
ItemInfo itemInfo = context.
ItemInfoes.
Include(info => info.Item).
SingleOrDefault(info => info.Language == YourGlobalLang &&
info.Item.Id == itemIdToFetch);
After that you can access the item object itself:
Item item = itemInfo.Item;
I would say it's a reasonable approach. Also, I wouldn't worry about performance issues with two simple selects. If it turns out to be a problem in the future, you might change it to a view, for instance.
You may try to add the where clause dynamically.
Or as it was said use linq to sql directly.
How to append a where clause to an Entity Framework ObjectSet
Add the where clause dynamically in Entity Framework

Categories