Update a row adding a relationed row in EF (many-to-many) - c#

I have two entities: Order and Item and a many-to-many relationship between them. I have a method which receive the ItemId as parameter like this:
public void AddItems(int OrderId, int ItemId)
{
Item item = db.ItemSet.SingleOrDefault(i => i.Id == ItemId);
Order order = db.OrderSet.SingleOrDefault(o => o.Id == OrderId);
order.Items.Add(item);
db.SaveChanges();
}
There are a lot of rows in the ItemSet table, so the first query is a heavy one. Is there a way that I can add the item to the order without doing a query first on the "ItemSet" table? I mean, can I add the ItemId directly to the Order.Items or something like that?

Item item = new Item { ID = ItemId };
Order order = new Order { ID = OrderId };
db.ItemSet.Attach(item);
db.OrderSet.Attach(order);
order.Items.Add(item);
db.SaveChanges();
Just be sure that you have this in your Order class:
public Order()
{
Items = new List<Item>();
}
So you don't get null pointer exception in order.Items.Add(item);

So you can model the entities, so:
public DbSet<Item> ItemSet {get;set;}
public DbSet<Order> OrderSet {get;set;}
public DbSet<ItemOrder> ItemOrders {get;set;}
public class Item
{
public int Id {get;set;}
}
public class Order
{
public int Id {get;set;}
}
public class ItemOrder
{
[Key, Column(Order = 0)]
public int ItemId {get;set;}
[Key, Column(Order = 1)]
public int OrderId {get;set;}
}
So in order to update:
public void AddItems(int OrderId, int ItemId)
{
var itemExists = db.ItemOrders.FirstOrDefault(x => x.OrderId == OrderId && x.ItemId == ItemId);
if (itemExists != null) return;
db.ItemOrders.Add(new ItemOrder { OrderId = OrderId, ItemId = ItemId });
db.SaveChanges();
}
Then you can query using the LINQ .Join() and is pretty straightforward.
Just for completeness sake the OP mentioned that the navigation properties have now disappeared. So if you know the OrderId then you can simple do:
var items = db.ItemOrders.GroupJoin(
db.ItemSet,
io => io.ItemId,
i => i.Id,
(itemOrder, items) => items)
.ToList();
So to walk you through, GroupJoin asks for the IQueryable/IEnumerable of data you want to join against.
The second param is the field of the initial data set i.e. ItemOrders the set you want to join to.
The third parameter is the field of the set that is being joined in i.e. ItemSet.
The fouth parameter is basically a Select where you are presented with a Func signature of roughly (ItemOrder itemInFirstSet, IEnumerable<Item> itemsThatAreJoined).
You can apply this same logic to the LINQ Join extension. But rather than itemsThatAreJoined it is itemThatIsJoined. So the distinction is GroupJoin will find multiple entities that match, Join will only find one.
This is no way a detailed explanation, more of an overview

Related

Left outer join using LINQ Query Syntax EF Core C#

I have a question in regards with the below,
Left outer join of two tables who are not connected through Foreign Key.
Order by the results matched in second table.
I would like this to be done in LINQ Query method syntax as I am adding lots of conditions depending on the input provided along with skip and limit.
If we have below Product and Favorite tables
So the output that I would like to have is:
meaning with the favorites as part of first set and which are not favorites should be behind them. Below are the tries that I did.
I am able to join the tables get the output but not sure how I can make sure that in the first page I get all the favs.
This answer was very near to what I thought but it gets the result and then does the ordering which will not be possible in my case as I am doing pagination and using IQueryable to get less data.
Group Join and Orderby while maintaining previous query
Open to any solutions to achieve the same.
[Table("Product")]
public class ProductModel
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ProductId { get; set; }
public string ProductName {get; set;}
public bool IsFavorite { get; set; }
}
[Table("UserFavorite")]
public class UserFavoriteModel
{
[Required]
public Guid UserId { get; set; }
[Required]
public Guid Identifier { get; set; }
[Required]
public FavoriteType Type { get; set; }
}
// Gets products
private async Task<List<ProductModel>> GetProductsAsync(
Guid categoryId,
Guid subCategoryId,
int from,
int limit)
{
var query = _context.Products.AsQueryable();
if (!string.IsNullOrEmpty(categoryId))
query = query.Where(product => product.CategoryId == categoryId);
if (!string.IsNullOrEmpty(subCategoryId))
query = query.Where(product => product.SubCategoryId == subCategoryId);
query = query.Skip(from).Take(limit);
var products = await query.ToListAsync();
query = query.GroupJoin(
_context.Favorites.AsNoTracking()
.Where(favorite => favorite.Type == FavoriteType.FASHION)
// This user Id will come from context just adding for overall picture.
.Where(favorite => favorite.UserId == userId),
//This orderby if I add will not make any difference.
//.OrderByDescending(favorite => favorite.Identifier),
v => v.ProductId,
f => f.Identifier,
(product, fav) => new { product, fav }).
SelectMany(x => x.Fav.DefaultIfEmpty(),
(x, y) => SetFavorite(x.Project, y));
}
private static ProductModel SetFavorite(ProductModel v, UserFavoriteModel si)
{
v.IsFavorite = (si != null);
return v;
}
I would do something like this:
var query =
_context.Products.AsQueryable().Select(p => new ProductModel {
ProductId = p.ProductId,
ProductName = p.ProductName,
IsFavorite =
_context.Favorites.Any(f =>
f.Identifier = p.ProductId &&
f.Type == FavoriteType.FASHION &&
f.UserId == userId
)
}).OrderByDescending(favorite => favorite.Identifier);

Select categories with subcategories [duplicate]

I have the following entity:
public class Item
{
public int Id { get; set; }
public int? ParentId { get; set; }
public Item Parent { get; set; }
public List<Item> Children { get; set; }
public double PropertyA { get; set; }
public double PropertyB { get; set; }
...
}
Now I want to query the database and retrieve data of all the nested children.
I could achieve this by using Eager Loading with Include():
var allItems = dbContext.Items
.Include(x => Children)
.ToList();
But instead of Eager Loading, I want to do the following projection:
public class Projection
{
public int Id { get; set; }
public List<Projection> Children { get; set; }
public double PropertyA { get; set; }
}
Is it possible to retrieve only the desired data with a single select?
We are using Entity Framework 6.1.3.
Edit:
This is what I have tried so far.
I really don't know how to tell EF to map all child Projection the same way than their parents.
An unhandled exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll
Additional information: The type 'Projection' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.
var allItems = dbContext.Items
.Select(x => new Projection
{
Id = x.Id,
PropertyA = x.PropertyA,
Children = x.Children.Select(c => new Projection()
{
Id = c.Id,
PropertyA = c.PropertyA,
Children = ???
})
})
.ToList();
Generally speaking, you can't load a recursive structure of unknown unlimited depth in a single SQL query, unless you bulk-load all potentially relevant data irregardless whether they belong to the requested structure.
So if you just want to limit the loaded columns (exclude PropertyB) but its ok to load all rows, the result could look something like the following:
var parentGroups = dbContext.Items.ToLookup(x => x.ParentId, x => new Projection
{
Id = x.Id,
PropertyA = x.PropertyA
});
// fix up children
foreach (var item in parentGroups.SelectMany(x => x))
{
item.Children = parentGroups[item.Id].ToList();
}
If you want to limit the number of loaded rows, you have to accept multiple db queries in order to load child entries. Loading a single child collection could look like this for example
entry.Children = dbContext.Items
.Where(x => x.ParentId == entry.Id)
.Select(... /* projection*/)
.ToList()
I see only a way with first mapping to anonymous type, like this:
var allItems = dbContext.Items
.Select(x => new {
Id = x.Id,
PropertyA = x.PropertyA,
Children = x.Children.Select(c => new {
Id = c.Id,
PropertyA = c.PropertyA,
})
})
.AsEnumerable()
.Select(x => new Projection() {
Id = x.Id,
PropertyA = x.PropertyA,
Children = x.Children.Select(c => new Projection {
Id = c.Id,
PropertyA = c.PropertyA
}).ToList()
}).ToList();
A bit more code but will get the desired result (in one database query).
Let's say we have the following self-referencing table:
public class Person
{
public Person()
{
Childern= new HashSet<Person>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int? ParentId { get; set; }
[StringLength(50)]
public string Name{ get; set; }
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
And for some point of time you need to get all grandsons for specific persons.
So, first of all I will create stored procedure(using code-first migration) to get all persons in the hierarchy for those specific persons:
public override void Up()
{
Sql(#"CREATE TYPE IdsList AS TABLE
(
Id Int
)
GO
Create Procedure getChildIds(
#IdsList dbo.IdsList ReadOnly
)
As
Begin
WITH RecursiveCTE AS
(
SELECT Id
FROM dbo.Persons
WHERE ParentId in (Select * from #IdsList)
UNION ALL
SELECT t.Id
FROM dbo.Persons t
INNER JOIN RecursiveCTE cte ON t.ParentId = cte.Id
)
SELECT Id From RecursiveCTE
End");
}
public override void Down()
{
Sql(#" Drop Procedure getChildIds
Go
Drop Type IdsList
");
}
After that you can use Entity Framework to load the ids(you could modify stored procedure to return persons instead of only returning ids) of persons under the passed persons(ex grandfather) :
var dataTable = new DataTable();
dataTable.TableName = "idsList";
dataTable.Columns.Add("Id", typeof(int));
//here you add the ids of root persons you would like to get all persons under them
dataTable.Rows.Add(1);
dataTable.Rows.Add(2);
//here we are creating the input parameter(which is array of ids)
SqlParameter idsList = new SqlParameter("idsList", SqlDbType.Structured);
idsList.TypeName = dataTable.TableName;
idsList.Value = dataTable;
//executing stored procedure
var ids= dbContext.Database.SqlQuery<int>("exec getChildIds #idsList", idsList).ToList();
I hope my answer will help others to load hierarchical data for specific entities using entity framework.

ASP.NET MVC LINQ Entity Framework recursive

I'm not sure how to write LINQ query. I have these models:
class Category
{
ICollection<Thread> Threads {get;set;}
ICollection<Category> SubCategories {get;set;}
}
class Thread
{
Category Category {get;set;}
//Some Stuff
}
So, there could be categories linked like -
Category1
Category2
Category3
Category4
Category5
Category6
I want find all threads linked to Category2 and it SubCategories(3, 4, 5).
I thought about just take Category1 form db, and using C# recursive function build List of threads i need, but i feel it's bad idea.
Any ideas or links would be great. Thank you!
There code, but there is Topics(in Threads), i didnt mention it couse it's not rly matter(at least i think so)
public ActionResult ShowCategoryTopics(int id)
{
var category = db.Categories.Where(x => x.Id == id).FirstOrDefault();
var topics = GetTopics(category);
return View();
}
public List<Topic> GetTopics(Category category)
{
List<Topic> topics = new List<Topic>();
if (!category.IsDeleted && !category.IsHidden)
return null;
foreach (Thread thread in category.Threads)
{
topics.AddRange(thread.Topics.Where(x => !x.IsDeleted).ToList());
}
foreach(Category childCategory in category.SubCategories)
{
topics.AddRange(GetTopics(childCategory));
}
return topics;
}
While EF can load joined records lazily and transparently, it can't load recursive joined records cause it's too complicate.
So, first of all, remove the Category.Threads navigation property:
public class Category
{
public int Id { get; set; }
public int? ParentId { get; set; }
// you can remove the attribute
[ForeignKey(nameof(ParentId))]
public virtual Category Parent { get; set; }
public string Title { get; set; }
public virtual ICollection<Category> SubCategories { get; set; } = new HashSet<Category>();
}
public class Thread
{
public int Id { get; set; }
public int CategoryId { get; set; }
// you can remove the attribute
[ForeignKey(nameof(Category))]
public Category Category { get; set; }
public string Title { get; set; }
}
Now you can use Common Table Expressions to recursive query and Database.SqlQuery<TElement> method to load the result of the query.
This is the SQL query to get all Threads corresponded to the specified #CategoryId and all its subcategories:
WITH RecursiveCategories(Id, ParentId, Title)
AS
(
SELECT Id, ParentId
FROM dbo.Categories AS c1
WHERE Id = #CategoryId
UNION ALL
SELECT Id, ParentId
FROM dbo.Categories AS c2
INNER JOIN c1 ON c2.ParentId = c1.Id
)
SELECT th.*
FROM dbo.Threads AS th
WHERE th.CategoryId IN (SELECT Id FROM RecursiveCategories)
The method to load threads of specified category recursively:
public IEnumerable<Thread> GetAllRecursivelyByCategoryId(int categoryId)
{
var query = #"WITH RecursiveCategories(Id, ParentId, Title)
AS
(
SELECT Id, ParentId
FROM dbo.Categories AS c1
WHERE Id = #CategoryId
UNION ALL
SELECT Id, ParentId
FROM dbo.Categories AS c2
INNER JOIN c1 ON c2.ParentId = c1.Id
)
SELECT th.*
FROM dbo.Threads AS th
WHERE th.CategoryId IN (SELECT Id FROM RecursiveCategories)";
var parameter = new SqlParameter("CategoryId", categoryId);
return _dbContext.Database
.SqlQuery<Thread>(query, parameter)
.AsEnumerable();
}
This method runs the recursive query and maps the result to enumerable of threads. Here is only one request to the SQL server, and the response contains only necessary threads.
The way to do this all in database would be to use a recursive Common Table Expression (CTE) to extract all the category hierarchy. However this is a bit difficult to implement using Linq without resorting to direct SQL.
As you state there will only be about 100 or so categories it may me simpler to do the category extraction in the code rather than database.
I'm assuming you have the foreign key columns as wells as the navigation properties.
First a Helper function, converts a list of categories to an enumerable of nested ids;
static IEnumerable<int> GetCategoryIds(IList<Category> categories, int? targetId) {
if (!targetId.HasValue) {
yield break;
}
yield return targetId;
foreach (var id in categories.Where(x => x.ParentId==targetId).SelectMany(x => GetCategoryIds(x.Id))) {
yield return id;
}
}
Now your query
var ids = GetCategoryIds(db.Categories.ToList(), 2).ToList();
var threads = db.Threads.Where(x => ids.Contains(x.CategoryId));

Linq grouping get multiple properties

I'm trying to group by CategoryId and display the CategoryId + the Name property of the group, I need help modifying this code so the Name property can be displayed, check out view below th see what I mean.
Database
ItemCategory
int ItemId
int CategoryId
Category
int CategoryId
int? ParentId
string Name
var itemcategories = db.Fetch<ItemCategory, Category>(#"SELECT * FROM ItemCategory LEFT JOIN Category on Category.CategoryId = ItemCategory.CategoryId WHERE ItemId = #0", item.ItemId);
var grouped = from b in itemcategories
where b.Category.ParentId != null
group b by b.Category.ParentId ?? 0 into g
select new Group<int, ItemCategory> { Key = g.Key, Values = g };
public class Group<K,T>
{
public K Key;
public IEnumerable<T> Values;
}
In view
#foreach (var group in #Model.ItemCategories)
{
#group.Key **Category.Name should be displayed here**
}
foreach (var value in group.Values)
{
#value.Category.Name
}
I think you're looking for the answer provided here:
Group by in LINQ.
However, you should also change your DB query so that it returns the actual result of the join and not just an IEnumerable<ItemCategory>.
The Group class could look like:
public class Group<K,T>
{
public K Key;
public IEnumerable<T> Values;
public IEnumerable<string> CategoryNames;
}
Note that if you want to group by ParentId, your key will always be ParentId, it's the idea of the GroupBy function.
With that new Group class and DB query, you should be able to use g.Name as the parameter for CategoryNames.

Entity Framework - Selective Condition on Included Navigation Property

Assume I have these simplified EF generated entities...
public class PurchaseOrder
{
public int POID {get;set;}
public int OrderID {get;set;}
public int VendorID {get;set;}
public IEnumerable<Order> Orders {get;set;}
}
public class Order
{
public int OrderID {get;set;}
public decimal Price {get;set;}
public IEnumerable<Item> Items {get;set;}
}
public class Item
{
public int OrderID {get; set;}
public string SKU {get;set;}
public int VendorID {get;set;}
public Order Order {get;set;}
}
Business Logic:
An order can have multiple POs, one for each distinct vendor on the order (vendors are determined at the Item level).
How Can I selectively Include Child Entities?
When querying for POs, I want to automatically include child entites for Order and Item.
I accomplish this, using Include()...
Context.PurchaseOrders.Include("Orders.Items");
This does it's job and pulls back related entities, but, I only want to include Item entities whose VendorID matches the VendorID of the PurchaseOrder entity.
With traditional SQL, I'd just include that in the JOIN condition, but EF builds those internally.
What LINQ magic can I use tell EF to apply the condition, without manually creating the JOINs between the entities?
You can't selectively pull back certain child entities that match a certain condition. The best you can do is manually filter out the relevant orders yourself.
public class PurchaseOrder
{
public int POID {get;set;}
public int OrderID {get;set;}
public int VendorID {get;set;}
public IEnumerable<Order> Orders {get;set;}
public IEnumerable<Order> MatchingOrders {
get {
return this.Orders.Where(o => o.VendorId == this.VendorId);
}
}
}
You can't. EF doesn't allow conditions for eager loading. You must either use multiple queries like:
var pos = from p in context.PurchaseOrders.Include("Order")
where ...
select p;
var items = from i in context.Items
join o in context.Orders on new { i.OrderId, i.VendorId}
equals new { o.OrderId, o.PurchaseOrder.VendorId }
where // same condition for PurchaseOrders
select i;
Or you can use projection in single query:
var data = from o in context.Orders
where ...
select new
{
Order = o,
PurchaseOrder = o.PurchaseOrder,
Items = o.Items.Where(i => i.VendorId == o.PurchaseOrder.VendorId)
};
You could use the IQueryable-Extensions here:
https://github.com/thiscode/DynamicSelectExtensions
The Extension builds dynamically an anonymous type. This will be used for projection as described by #Ladislav-Mrnka.
Then you can do this:
var query = query.SelectIncluding( new List<Expression<Func<T,object>>>>(){
//Example how to retrieve only the newest history entry
x => x.HistoryEntries.OrderByDescending(x => x.Timestamp).Take(1),
//Example how to order related entities
x => x.OtherEntities.OrderBy(y => y.Something).ThenBy(y => y.SomeOtherThing),
//Example how to retrieve entities one level deeper
x => x.CollectionWithRelations.Select(x => x.EntityCollectionOnSecondLevel),
//Of course you can order or subquery the deeper level
//Here you should use SelectMany, to flatten the query
x => x.CollectionWithRelations.SelectMany(x => x.EntityCollectionOnSecondLevel.OrderBy(y => y.Something).ThenBy(y => y.SomeOtherThing)),
});

Categories

Resources