Deleting selected relations in many-to-many relationship in EF? - c#

In SQL, I have:
[Bands] 1 ---- * [BandsGenres] * ---- 1 [Genres]
In my EF layer, it translates to:
[Bands] * ---- * [Genres]
(BandsGenres is BandId and GenreId)
My problem is, I can't seem to figure out how to delete just the selected relationships I need. Basically, I'm trying to update a list of type Genre for a band. A list of Guids is passed in and I'm able to get those using joins. However, the .Remove() method isn't removing the old genres and adding the new ones. I tried using .Attach(g) and .DeleteObject(g) but that removed the actual Genre (from Genres) instead of just the relationship.
public void UpdateBandGenres(Guid bandId, IEnumerable<Guid> genres)
{
using (var ctx = new OpenGroovesEntities())
{
var newGenres = from g in ctx.Genres
join t in genres on g.GenreId equals t
select g;
//var bandGenres = ctx.Genres.Include("Bands").Where(g => g.Bands.Any(b => b.BandId == bandId));
var bandGenres = ctx.Bands.SingleOrDefault(b => b.BandId == bandId).Genres;
bandGenres.ToList().ForEach(g =>
{
bandGenres.Remove(g);
});
newGenres.ToList().ForEach(g => bandGenres.Add(g));
ctx.SaveChanges();
}
}
How can I delete/add or update my list of genre relationships for a band, given a list of genre IDs? Thanks.

If I understand your problem correcly, the genres collection contains all the Genre objects that should be in the Band genre list as a result of running the UpdateBandGenres method (and not just the list to add or list to delete). In that case the simplest would be removing all the genres from the collection and adding all the genres with the Guids from your genres collection.
First of all, you don't need joins to grab your newGenres:
var newGenres = ctx.Genres.Where(g => genres.Contains(g.GenreId));
Secondly, you need to fetch the Band object, since modifying its Genres collection will tell EF to modify the BandGenres table in SQL:
Band band = ctx.Bands.SingleOrDefault(b => b.BandId == bandId);
After that you can clear the band.Genres collection and add the newGenres on top. As a result you code will look like:
public void UpdateBandGenres(Guid bandId, IEnumerable<Guid> newGenreIds)
{
using (var ctx = new OpenGroovesEntities())
{
List<Genre> newGenres = ctx.
Genres.
Where(g => newGenreIds.Contains(g.GenreId)).
ToList();
Band band = ctx.Bands.Single(b => b.BandId == bandId);
band.Genres.Clear();
newGenres.ForEach(band.Genres.Add);
ctx.SaveChanges();
}
}
Btw, I'd also recommend being consistent with naming your variables - e.g. IEnumerable<Guid> genres might be a bit misleading, since it's actually a collection of Guids, and not a collection of Genre objects. Therefore I named it newGenreIds to be consistent with your Guid bandId variable name.

Related

LINQ how do I specify select certain columns in a one to many joins

I have a situation where I have a LINQ query. It has two joins (one to many) but it is bringing back all of the columns in the joined tables. I'm not sure how to create the LINQ query to only being back a few fields from the joined tables.
var data = from mc in ctx.MembershipChapters
where mc.PartitionKey == controllerStateManager.PartitionKey && mc.MembershipId == membershipId
join prd in ctx.Products on mc.ProductId
equals prd.Id into prods
from prd in prods.DefaultIfEmpty()
join oli in ctx.OrderLineItems on mc.OrderLineItemId equals oli.OrderLineItemId into olis
from oli in olis.DefaultIfEmpty()
select new
{
MembershipName = mc.Membership.Name,
Products = prods.Select(p => new {
ProductName = p.Name, ProductId = p.Id }),
OrderLineItems = olis.Select(o => new { OrderLineItemName = o.Description, OrderLineItemId = o.OrderLineItemId })
};
controllerStateManager.Data = data.ToList();
This does not work...I get an error: "o" is not in scope.
Basically the output should follow this:
MembershipChapter
---> OrderLineItems
----------> Products
I'm new to LINQ and I have been struggling on this for far too long.
If you have a one-to-many relationship, and you want the "one" items, each one with its zero or more subitems, like Schools with their zero or more Students; Customers with their zero or more Orders, or, as in your case: MembershipChapters with their OrderLineItems, consider to use one of the overloads of Queryable.GroupJoin.
If you start on the "many" side, and you want each item with its one parent item, so you want the Student with the School he attends, or the Order with the one and only Customer who placed the order, use one of the overloads of Queryable.Join.
I almost always use the overload that has a parameter resultSelector, so you can exactly define what should be in the result.
Requirement: given tables MembershipChapters, OrderLineItems and Products. There is a one-to-many relationship between MembershipChapters and OrderLineItems. Every MembershipChapter has zero or more OrderLineItems, every OrderLineItem belongs to exactly one MembershipChapter, namely the MembershipChapter that the foreign key refers to. There is a similar one to many relation between OrderLineItems and Products. Give me all (or some) MembershipChapters, each MembershipChapter with its zero or more OrderlineItems, and each OrderLineItem with its zero or more Products.
var result = dbContext.MemberShipChapters
.Where(membershipChapter => ...) // only if you don't want all MembershipChapters
.GroupJoin(dbContext.OrderLineItems,
membershipChapter => membershipChapter.Id, // from every membershipChapter get the primary key
orderlineItem => orderLineItem.MembershipChapterId, // from every OrderlineItem get the foreign key
// parameter resultSelector: from every MembershipChapter with its zero or more
// OrderLineItems, make one new:
(membershipChapter, orderLineItemsOfThisMembershipChapter) => new
{
// Select only the membershipChapter properties that you plan to use
Id = membershipChapter.Id,
Name = membershipChapter.Name,
...
// The zero or more OrderLineItems of this membershipChapter
OrderLineItems = orderLineItemsOfThisMembershipChapter
.Select(orderLineItem => new
{
// Select only the OrderLineItems that you plan to use:
Id = orderLineItem.Id,
...
// not needed, you already know the value
// MembershipChapterId = orderLineItem.MembershipChapterId,
})
.ToList(),
});
This is fairly straightforward. However, if you want to GroupJoin three tables, then this looks horrible, although it is doable.
Another method that looks simpler:
var result = dbContext.MemberShipChapters
.Where(membershipChapter => ...)
.Select((membershipChapter => new
{
Id = membershipChapter.Id,
Name = membershipChapter.Name,
...
OrderLineItems = dbContext.OrderLineItems
// keep only the OrderLineItems with a foreign key referring to this MembershipChapter
.Where(orderLineItem => orderLineItem.MemberShipChapterId == membershipChapter.Id)
.Select(orderLineItem => new
{
Id = orderLineItem.Id,
...
// do the same with the third table
Products = dbContext.Products
.Where(product => product.OrderLineItemId == orderLineItem.Id)
.Select(product => new
{
Id = product.Id,
Price = product.Price,
...
})
.ToList(),
})
.ToList(),
});
It is a little hard to read, but if the domain is linked correctly then I think you just want to end up with a query like this:
from ol in ctx.OrderLines where
ol.MembershipChapter.PartitionKey == controllerStateManager.PartitionKey
select new {ol.Whatever, ol.Product.Whatever};

The Include path expression must refer to a navigation property defined on the type (select data using LINQ)

I was trying to select data using LINQ
and I have a list called "products" and I want just these items that exist in products list
var Owner = db.Owners
.Where(m => m.ID == id)
.Include(m => m.Products.Where(item1 => products.Any(item2 => item2.ProductID == item1.ProductID)).ToList())
.FirstOrDefault();
but I'm getting this error :
System.ArgumentException: 'The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path'
Include is meant to fetch complete rows of a table, inclusive primary and foreign keys.
Usually it is not efficient to fetch the complete rows of a table. For example, suppose you have a database with Schools and Students. There is a one-to-many relation between Schools and Students: every School has zero or more Students, every Student attends exactly one School, namely the School that the foreign key refers to.
If you fetch School [10] with its 2000 Students, then every Student will have a foreign key SchoolId with a value 10. If you use Include and fetch complete Student rows you will transfer this value 10 over 2000 times. What a waste of processing power!
A DbContext has a ChangeTracker object. Whenever you fetch data without using Select, so if you fetch complete rows, then the fetched rows are stored in the ChangeTracker, together with a Clone of it. You get a reference to the Clone (or the original, doesn't matter). When you change properties of the fetched data, you change the value in the Clone. When you call SaveChanges, the values of all properties of all originals in the ChangeTracker are compared with the values in the Clones. The items that are changed are updated in the database.
So if you fetch School [10] with its 2000 Students, you are not only fetching way more data than you will ever use, but you will also store all these Students in the ChangeTracker together with a Cloned Student. If you call SaveChanges for something completely different (change of the telephone number of the School for instance), then all Students are compared by value property by property with their Clones.
Generic rule:
Whenever you fetch data using Entity Framework, always use Select, and Select only the properties that you actually plan to use. Only fetch complete rows and only use Include if you plan to update the fetched data.
Using Select will also solve your problem:
int ownerId = ...
IEnumerable<Product> products = ...
var Owner = db.Owners.Where(owner => owner.ID == ownerId)
.Select(owner => new
{
// Select only the Owner properties that you actually plan to use
Id = owner.Id,
Name = owner.Name,
// get the Products of this Owner that are in variable products
Products = owner.Products
.Where(product => products.Any(p => p.ProductId == product.ProductId)
.Select(product => new
{
// Select only the Product properties that you plan to use
Id = product.Id,
Price = product.Price,
...
// No need to fetch the foreign key, you already fetched the value
// OwnerId = product.OwnerId,
})
.ToList(),
...
})
.FirstOrDefault();
I used automatic types (new {...}). If you really want to create Owner and Properties, use:
var Owner = db.Owners.Where(...)
.Select(owner => new Owner
{
Id = owner.Id,
...
Products = owner.Products.Where(...).Select(product => new Product
{
Id = product.Id,
...
})
.ToList(),
})
.FirstOrDefault();
Try the following:
var productIds = products.Select(x => x.ProductID);
var Owner = db.Owners
.Where(m => m.ID == id)
.Include(m => m.Products.Where(product => productIds.Contains(product.ProductID))
.FirstOrDefault();

Entity Framework procedure call with navigation properties

I want to call a procedure and access the navigation properties afterwards
using (DbContext c = new DbContext())
{
// System.Data.Objects.ObjectResult<Product>
List<Product> products = c.myProcedure(id).Include("ProductOptions").ToList();
// here the .Include() isn't available
}
my current approach is loading each navigation property seperately
using (DbContext c = new DbContext())
{
List<Product> products = c.myProcedure(id).ToList();
foreach(Product p in products)
{
if(!o.ProductOptions.IsLoaded)
p.ProductOptions.Load();
}
}
is working fine but super slow because of the subselect for each item.
Question: Is there a kind of Include() function or something else to speed up my code?
While using entity framework people tend to use Include instead of Select. The result seems to be the same, however there is a slight difference.
The difference between Include and Select is that Include fetches the complete object, while Select fetches only the selected data. Another difference is that your DbContext object remembers the Included data.
Databases are extremely optimized in fetching data. One of the slower parts of the query is the transfer of the selected data from the database management system to your process. Hence it is wise to limit the amount of fetched data.
Suppose you have database with Schools and their Students: a straightforward one-to-many relations: Every School has zero or more Students, every Student studies at exactly one School, using a foreign key to the School he attends.
Let's query School [10] with its thousand Students:
var result = dbContext.Schools
.Include(school => school.Students)
.Where(school => school.Id == 10)
.FirstOrDefault();
The Complete Student is transferred. Every Student will have the same foreign key SchoolId value 10. So this same number is transferred a thousand times (1001 if you also count School.Id), while you already know the value. what a waste of processing power!
When querying data, always use Select to fetch the data. Only use Include if you plan to update the fetched data
var result = dbContext.Schools
.Where(school => school.Id == 10)
.Select(school => new
{
// Select only the School properties you plan to use
Id = school.Id,
Name = school.Name,
Address = school.Address,
Students = school.Students
.Where(student => ...) // only if you don't want all Students
.Select(student => new
{
// again: select only the properties you plan to use
Id = student.Id,
Name = student.Name,
...
// not needed, you already know the value!
// SchoolId = student.SchoolId,
})
.ToList(),
})
.FirstOrDefault();
I've use new to create an anonymous type. This is the most efficient. If you really need to create Schools and Students, use new School and new Student
.Select(school => new School
{
...
Students = schoolStudents.Select(student => new Student
{
...
})
.ToList(),
})
A last remark. include(school.Students) can be found in using System.Data.Entity

Entity Framework C# - Cloning object with children with many-to-many relationships

Using EF and C#, I'm trying to make a clone of an entity which has a lot of related records. I want to clone the related records as well. The top level object is a Bid.
class Bid
{
Collection<ItemGroup> ItemGroups {get;set;}
Collection<Evaluator> Evaluators {get;set;}
}
Children being ItemGroups and Evaluators. They are many-to-many.
class ItemGroup
{
Collection<Evaluator> Evaluators {get;set;}
}
Class Evaluator
{
Collection<ItemGroup> ItemGroups {get;set;}
}
I clone the Bid, and all of it's children, by querying the database
Bid bid = dbContext.Bids.AsNoTracking().FirstOrDefault()
.Include(b => b.ItemGroups)
.Include(b => b.ItemGroups.Select(e => e.Evaluators))
and adding the Bid back in with
database.Bids.Add(bid);
database.SaveChanges();
The problem is the many-to-many relationship between Evaluators and ItemGroups. Because their collections reference eachother, when you add the Bid back in, it DUPLICATES the records.
So, before clone, I have a Bid:
Original Bid -
Number of ItemGroups = 3
Number of Evaluators = 2
and, after clone, I have a new Bid, with:
New Bid -
Number of ItemGroups = 3
Number of Evaluators = 6
Which is obviously incorrect. How do I clone this relationship without having EF add duplicates?
Is the problem my original query? I've tried all sorts of options, using .Include()s that trace from Bid -> Evaluators -> Item Groups, or simply just go Bid -> Evaluator and Bid -> ItemGroup, but nothing seems to get me quite what I want. Any and all help is greatly appreciated, and please just let me know if I can offer some clarifications. Thanks in advance.
Well, here's my super janky solution until someone can tell me a better way to do it. Basically, I leave out the many-to-many relationships in my query Include()s, querying for bid.ItemGroups, and bid.Evaluators only.
I add those as normal, which gives me a Bid with the appropriate number of item groups, and 0 evaluators, as the ItemGroup -> Evaluator relationships weren't established because i did not query for them.
Then, after calling database.Bids.Add(bid), I re-query for the original bid and it's Evaluator/ItemGroup relationships, and manually hook up all of the Evaluator/ItemGroup references using names/id's/whatever identifiers are available.
List<Evaluator> originalEvals = db.Bids.Where(b => b.Id == entityId)
.Select(b => b.Evaluators)
.FirstOrDefault()
.ToList();
foreach(Evaluator origEval in originalEvals)
{
db.Entry(origEval).Reference(e => e.ClientUser).Load();
db.Entry(origEval).Collection(e => e.ItemGroups).Load();
}
foreach (Evaluator newEval in bid.Evaluators)
{
Evaluator originalEval = originalEvals.Where(e => e.ClientUserId == newEval.ClientUserId).FirstOrDefault();
foreach (ItemGroup ig in originalEval.ItemGroups)
{
newEval.ItemGroups.Add(bid.ItemGroups.Where(g => g.Name == ig.Name).FirstOrDefault());
}
}
Shitty, but it works.

EF Linq - how to select children in same query?

I have Oracle db with EF 5 on top of it.
Lets say I have tables Company and Orders. I have EF corresponding entities where Company has field
List<Orders> Orders.
I dont want to create an association.
I have a query which is IQuerable and I need to fill the Orders for each company using
order.CompanyId == company.Id
I cant wrap my head around this atm.. Any help will be appreciated.
EDIT: not every company has orders. The orders list could be empty.
I would consider using the .Join() method to include the Orders table. I write this from memory, so please forgive syntax errors.
//I only use this bc I don't know the full schema.
class DTOCompany {
public int ID {get;set;}
public List<Orders> Orders {get;set;}
}
public List<Companies> GetCompaniesOrders() {
using (var db = new Entities()) {
return db.Companies
.AsEnumerable() //may not be needed.
.Join(db.Orders,
c => CompanyId,
o => o.CompanyId,
(c,o) => new { Company = c, Orders = o})
)
.Select(co => new DTOCompany() {
ID = co.Companies.CompanyId,
Orders = co.Orders.ToList()
})
.ToList();
}
}

Categories

Resources