Linq to Sql Many to Many relationships - c#

I have just started a new project using a Linq to Sql model and I'm implementing our first many to many relationship. I have found this blog that gives great info on how implement this:
http://blogs.msdn.com/mitsu/archive/2008/03/19/how-to-implement-a-many-to-many-relationship-using-linq-to-sql-part-ii-add-remove-support.aspx
When I try to add some child objects in and then remove one before saving I get an error,
System.InvalidOperationException:
Cannot remove an entity that has not
been attached.
Any ideas? Someone has already commented on this to the author of the blog, but there has been no response.
Much appreciated!

You are calling DeleteOnSubmit, on an entity that has not been sucked into the DataContext.
To check for this (in a rather hacky way), do the following:
var e = some_entity;
var cs = dc.GetChangeSet();
if (cs.Inserts.Any( x => x == e))
{
dc.SomeTable.DeleteOnSubmit(e);
}
dc.SubmitChanges();

I suspect that's why the author mentions that this particular post is working 'in memory' and not with linq to sql. Another approach would be to modify the onAdd event to Insert the added object into the context, that should at least alleviate the error you are seeing.

Take a look at the way PLINQO handles many to many relationships. There is a List of each entity that relates to another entity through a many to many relationship. You can add and remove without receiving the error you mention here.

Related

Eager-loading using LINQ to SQL with Include()

I have spent 2 days bashing my head against this problem, and I can't seem to crack it (the problem that is). The same code was working fine until I added database relationships, and I have since read a lot about lazy-loading.
I have two database tables with a 1:1 relationship between them. PromoCode table tracks codes, and has a PK column named id. CustomerPromo table has a column PromoId which is linked to the PromoCode table id. These two tables have no other relationships. I generated all this in SQL Server Management Studio, then generated the model from the database.
To make matters slightly more complicated, I'm doing this inside a WCF data service, but I don't believe that should make a difference (it worked before database relationships were added). After enabling logging, I always get an Exception in the log file with text:
DataContext accessed after Dispose.
My function currently returns all entries from the table:
using (MsSqlDataContext db = new MsSqlDataContext())
{
// This causes issues with lazy-loading
return db.PromoCodes.ToArray();
}
I have read numerous articles/pages/answers and they all say to use the .Include() method. But this doesn't work for me:
return db.PromoCodes.Include(x => x.CustomerPromos).ToArray();
I've tried the "magic string" version as well:
return db.PromoCodes.Include("CustomerPromos").ToArray();
The only code I've managed to get to work is this:
PromoCode[] toReturn = db.PromoCodes.ToArray();
foreach (var p in toReturn)
p.CustomerPromos.Load();
return toReturn;
I've tried added a .Where() criteria to the query, I've tried .Select(), I've tried moving the .Include() after the .Where() (this answer says to do it last, but I think that's only due to nested queries). I've read about scenarios where .Include() will silently fail, and after all this I'm no closer.
What am I missing? Syntax problem? Logic problem? Once I get this "simple" case working, I also need to have nested Includes (i.e. if CustomerPromo table had a relationship to Customer).
Edit
Including all relevant code. The rest is either LINQ to SQL, or WCF Data Services configuration. This is all there is:
[WebGet]
[OperationContract]
public PromoCode[] Test()
{
using (MsSqlDataContext db = new MsSqlDataContext())
{
return db.PromoCodes.Include(x => x.CustomerPromos).ToArray();
}
}
If I call that through a browser directly (e.g. http://<address>:<port>/DataService.svc/Test) I get a reset connection message and have to look up the WCF logs to find out "DataContext accessed after Dispose.". If I make the same query through an AJAX call in a webpage I get an AJAX error with status error (that's all!).
I prematurely posted the previous answer when I didn't actually have any child data to fetch. At the time I was only interested in fetching parent data, and that answer worked.
Now when I actually need child data as well I find it didn't work completely. I found this article which indicates that .Include() (he says Including() but I'm not sure if that's a typo) has been removed, and the correct solution is to use DataLoadOptions. In addition, I also needed to enable Unidirectional Serialisation.
And to top it off, I no longer need DeferredLoadingEnabled. So now the final code looks like this:
using (MsSqlDataContext db = new MsSqlDataContext())
{
DataLoadOptions options = new DataLoadOptions();
options.LoadWith<PromoCode>(p => p.CustomerPromos);
db.LoadOptions = options;
return db.PromoCodes.ToArray();
}
After setting Unidirectional Serialisation it will happily return a parent object without having to load the child, or explicitly set DeferredLoadingEnabled = false;.
Edit: This did not solve the problem entirely. At the time of testing there wasn't any child data, and I wasn't trying to use it. This only allowed me to return the parent object, it doesn't return child objects. For the full solution see this answer.
Contrary to everything I've read, the answer is not to use .Include() but rather to change the context options.
using (MsSqlDataContext db = new MsSqlDataContext())
{
db.DeferredLoadingEnabled = false; // THIS makes all the difference
return db.PromoCodes.ToArray();
}
This link posted in the question comments (thanks #Virgil) hint at the answer. However I couldn't find a way to access LazyLoadingEnabled for LINQ to SQL (I suspect it's for EntityFramework instead). This page indicated that the solution for LINQ to SQL was DeferredLoadingEnabled.
Here is a link to the MSDN documentation on DeferredLoadingEnabled.

Is there any way to call a none LINQ method inside the select new part of an IQueryable object in entity framework?

I have this issue when declaring a IQueryable<T> object inside the:
select new { };
part when I want to call a none LINQ method like Tostring() or any other functions that I have as follows:
select new
{
TFPricep = CurrancyHelper.DecimalToCurrency(TFPrice),
TFDatep = TFDate.Tostring()
};
I get this famous error message that says:
LINQ to Entities does not recognize the method 'foo'.
So what happens is that I perform a ToList() and get the data from database and after that I will Have to perform a foreach loop on the list and call functions like ToString() and etc on each list record which has performance issues . as far as I have tested there was no such issue in LINQ to SQL but this issue still exists in Entity Framework
I wonder if there is any way to call none LINQ methods when querying and prevent performing such for each loop ?
When I faced this issue I found two possible solutions but finally decided to avoid using anything that Linq to Entities could not translate.
I found two options:
1) If you are using an .edmx file look at this:
http://msdn.microsoft.com/en-us/library/vstudio/dd456857(v=vs.110).aspx
2) This other option is a little uglier but does not require an .edmx file:
http://damieng.com/blog/2009/06/24/client-side-properties-and-any-remote-linq-provider
https://www.nuget.org/packages/Microsoft.Linq.Translations/
https://github.com/peschuster/PropertyTranslator
One final comment: I never tried these options so I'm not 100% sure that they even work but they seem legit.
No

EF5 ObjectContext : How to replace IQueryable<T>.Include(Path) with context.T.Attach()

I'm using Entity Framework 5 with ObjectContext on a relatively big and complex data model.
I would like to work around big queries generated when chaining multiple IQueryable.Include(Path) to eager load related objects.
For example, I'm doing something like this :
var queryPe = context.Person.Where(p => p.Id == 110).Include(#"AA");
queryPe = queryPe.Include(#"BB.CC.DD");
queryPe = queryPe.Include(#"EE.FF");
It could be made generic by using a string array and chaining each graph at runtime in a foreach loop.
Instead, I would like to do something like this :
Person pe = context.Person.Where(p => p.Id == 110).First();
context.LoadProperty(pe, "AA");
pe.BB.Attach(pe.BB.CreateSourceQuery().Include(#"CC.DD"));
pe.EE.Attach(pe.EE.CreateSourceQuery().Include(#"FF"));
Instead of having one big query we would have 4 smaller queries hitting the database.
Of course I still want to leverage the power of the graph path as a string.
I may be wrong, but this means that I should use relexion to get the navigation properties by name and executing the CreateSourceQuery() on it because there is no such extension method to do this.
Am I correct ?
EDIT 1 : Well, I have an additionnal constraint, that is, I'm using Self Tracking Entities (STE). This means that Related objects are not materialized as EntityCollection or EntityReference. So Attach() and CreateSourceQuery() are not available !
So I'm stuck with Context.LoadProperty to deal with object graphs.
Is it only possible ?
EDIT 2 : Problem exposed in EDIT 1 solved, thanks to the help of DbContext class. See the code below :
Person pe = context.Person.Where(p => p.Id == 110).First();
context.LoadProperty(pe, "AA");
DbContext dbc = new DbContext(context, false);
dbc.Entry(pe).Collection(#"BB").Query().Include(#"CC.DD").Load();
dbc.Entry(pe).Reference(#"EE").Query().Include(#"FF").Load();
EDIT 3 02/11/2013 : There is an issue with the code presented above (EDIT 2). If the last entity in the path is a reference and not a collection, the code doesn't fail but it is not loaded :-(
EDIT 4 : Instead of using reflection, right now, I'm generating the code by looking at the Entity Data Model with the help of a T4 template.
Sometimes stored procedures are best. Write a stored procedure that returns multiple result sets, one for each type of entity you wish to eagerly load. This is highly performant compared to what you're trying to accomplish and the stored procedure will be far more readable than this jumble of includes and separate load statements per reference/collection. And yes, EF will hook up related entities automatically!
Here's a reference for sprocs with multiple result sets for both EDMX and code first:
http://msdn.microsoft.com/en-us/data/jj691402.aspx
Try to separate your aggregates in multiple contexts. Each Bounded context should have a separate context. This way you create a loosely coupled entity framework solution.
Julie Lerman has a good video on plural sight about this concept.
I would prefer to use stored procedures. Easy maintainance, works faster etc.

updating related data in entity framework

I have an update function in my repository which updates TerminalCertification entity. But this entity has a many to many relation to another class ( GomrokJustification ).
my update function update entity correctly but does not anything on related entity.
my update function is below:
public void UpdateTerminalCertification(TerminalCertification terminalCertification)
{
var lastCertification =
db.terminalCertifications.Include("TimeInfo").Include("GomrokJustifications").Where(item=>item.TerminalCertificationID==terminalCertification.TerminalCertificationID).ToList();
if (lastCertification.Count==0)
throw new TerminalCertificationNotFoundException(terminalCertification);
terminalCertification.TimeInfo = lastCertification[0].TimeInfo;
((IObjectContextAdapter)db).ObjectContext.Detach(lastCertification[0]);
((IObjectContextAdapter)db).ObjectContext.AttachTo("terminalCertifications", terminalCertification);
foreach (var gomrokJustification in terminalCertification.GomrokJustifications)
{
((IObjectContextAdapter)db).ObjectContext.AttachTo("gomrokJustifications", gomrokJustification);
((IObjectContextAdapter)db).ObjectContext.ObjectStateManager.ChangeObjectState(gomrokJustification, EntityState.Modified);
}
((IObjectContextAdapter) db).ObjectContext.ObjectStateManager.ChangeObjectState(terminalCertification,EntityState.Modified);
}
and my TerminalCetrification has a list of GomrokJustifications which was filled before by some entities. I want to those last entity being replaced by new ones. but this was not happen.
does anyone have any idea?
Instead of doing this:
var lastCertification = db.terminalCertifications
.Include("TimeInfo")
.Include("GomrokJustifications")
.Where(item=>item.TerminalCertificationID==terminalCertification.TerminalCertificationID)
.ToList();
if (lastCertification.Count==0)
throw new TerminalCertificationNotFoundException(terminalCertification);
you could just do this:
var lastCertification = db.terminalCertifications
.Include("TimeInfo")
.Include("GomrokJustifications")
.Where(item=>item.TerminalCertificationID==terminalCertification.TerminalCertificationID)
.FirstOrDefault();
if (lastCertification == null)
throw new TerminalCertificationNotFoundException(terminalCertification);
First throws an exception if there are no elements in the collection, so if you don't care about the terminalcertificationnotfoundexception you could even remove that custom exception. Your logic even seems to assume that there will be only one element in the returned list so you could even use Single(). That expresses more what you want to achieve compared to calling tolist and then retrieving the first item.
After looking carefully at your code I actually don't get the point you are trying to achieve here. You have an existing terminalcertification entity to start with, you then retrieve it again in that first query, why? You then take the timeinfo from conceptually the same entity (cause you did a get by id) to the one you get as input parameter. Why not continue working on the one that was retrieved from the database? You then detach the entity you received from the database, why? And continue working with the input terminalcertification. I think you need to look a bit more carefully on the entity framework documentation about entity state etc. Take a look at ApplyCurrentValues and detaching and attaching objects here: http://msdn.microsoft.com/en-us/library/bb896271.aspx
We'll need some more info to help you along.

LINQ-to-SQL + One-to-Many + DataBinding deleting

I use LINQ-to-SQL to load data from a database that has two tables in a one-to-many relationship (one Recipe has many Ingredients).
I load a Recipe and LINQ retrieves Ingredient objects into an EntitySet that is binded into a ListBox.
If I want to delete some Ingredients off a Recipe, I get a "An attempt was made to remove a relationship between a Recipe and a Ingredient. However, one of the relationship's foreign keys (Ingredient.RecipeID) cannot be set to null.
I SOLVED this problem using the well known solution by adding 'DeleteOnNull="true"' to the DBML file. But adding this setting only removes the problem when we are deleting Ingredient objects that were retrieved from the DB.
The problem is with the Ingredient objects that were created in code (added to a Recipe) and added to the EntitySet collection of Ingredients and then deleted BEFORE SubmitUpdates is called. Then, the same exception happens again. This usually happens on a new, unsaved recipe when user is adding ingredients to it, makes a mistake and erases an ingredient off a recipe. I added the DeleteOnNull to both 'Association Name="Recipe_Ingredient"' lines in DBML.
How am I supposed to remove such objects? The only solution I see at the moment is that I would load the ingredients into a collection not under the DataContext and then when saving, delete all ingredients off a recipe and add then again from that cache..
try
{
// Needed for existing records, but will fail for new records
yourLINQDataContext.Ingredients.DeleteOnSubmit(ingredient);
}
catch (Exception)
{
// Swallow
}
yourRecipeObject.Ingredients.Remove(ingredient);
It seems that you're looking for something that I was looking for myself just a few days back when I asked "How do I design backing data types for a databound WPF dialog with Ok/Cancel buttons?".
The answer is an intriguing post from Paul Stovell describing a sample IEditable adapter for Linq to Sql. This will let you create your desired "Apply/Cancel" semantics in a generalized manner without completely dissociating yourself from the underlying ORm-generated classes through a full custom-written layer.
It's a pretty slick trick, overall, that will essentially let you sidestep the problems you're fighting right now. :)
On a different note, I'm curious as to why your recipe to ingredient relationship is 1:n instead of m:n. Is it for simplicity's sake? I use garlic in a lot of recipes. :)
// Create new entities
Cart c = new Cart();
CartEntry ce = new CartEntry();
ce.Cart = c;
// Delete the entry
c.CartEntries.Remove(ce);
dc.Cartentries.Attach(ce);
dc.CartEntries.DeleteOnSubmit(ce);
// Insert the cart into database
dc.Carts.InsertOnSubmit(c);
dc.SubmitChanges();
Explaination of the issue: Both entities, c and ce, are not related to a data context - they are not being tracked. EntitySet.Remove() (first delete line) only removes the relation between c and ce. While c can exist without associated cart entries, ce can't exist without an assiciated cart because of a foreign key constraint. When submitting changes to the database, the disconnected ce is dealt with as well, causing a constraint violation and the exception.
In order to get rid of that untracked and disconnected cart entry you need to attach it to your data context (causing it to be tracked) and then mark it for delete on submit. The moment you submit your changes the cart entry will be deleted properly and not cause the exception.
For more details on that issue check this out:
http://msdn.microsoft.com/en-us/library/bb546187%28v=VS.100%29.aspx
you need to decouple the save code from the events in your GUI, it seems like you're a little to eager to save things to the db before the dust has settled and you're queuing and removing things from the db that never got there in the first place, it would be best if you could identify a point when the user will "commit" their changes, and at that moment, process the full condition of the GUI - this will save you a bunch of spaghetti code.
I would also be curious to know if your entities have autonumber IDs or if you're using some other ID mechanism. You're probably sending DELETEs to the database for the as-yet-uncommitted Ingredient records, if those include NULL IDs, I think the linq could get nasty.
Have you hooked up a textwriter to your DataContext.Log to see what sorts of SQL is generated just before you get your exeception?
Thank you for your answer, I will examine the posts and see what I can do. I must say I'm surprised to even see this problem occuring, it seems quite natural to me that one could add records to the LINQ-provided "cache" of data, then decide to erase some of them and then commit. Change tracking should be able to handle that. I just starting with LINQ so I might be doing a stupid mistake somewhere in the code (wouldn't be the first).
On the other note: You are quite correct that garlic can belong to many recipes (not my coctail recipes thought!). I actually model that with an Article object/table. But for a recipe, you need quantities. So in my model, you have a Recipe that has 1:n Ingredients, each of them having a Quantity, a 1:1 link to an Article (which has a Name, an AlcoholContent and some data to establish an interchangeability hierarchy) and a 1:1 link to an Unit (for the quantity to make sense).
So in a sense, Ingredient table makes a M:N relationship between Recipe and Article, and at the same time adding some additional information to each individual linked pair.
I had exactly the same problem. I had a parent / child hierarchy, and when adding and removing the child entity without saving to the database I received the "An attempt was made to remove a relationship" exception.
I discovered that this problem only arose when I set an object style property of the child to another linq-sql entity before saving. eg
1. This creates the error
RetailAccountCustomerCard racc = new RetailAccountCustomerCard();
Card addedCard = _idc.Cards.Where(c => c.CardId == card.CardId).ToList().First();
racc.Card = addedCard;
this.CurrentCustomer.RetailAccountCardsBindingList.Add(racc);
// Some code triggered by the user before saving to the db
CurrentCustomer.RetailAccountCardsBindingList.Remove(racc);
2. This doesn't create the error
RetailAccountCustomerCard racc = new RetailAccountCustomerCard();
racc.CardId = card.CardId; // note that I have set the Id property not the object
this.CurrentCustomer.RetailAccountCardsBindingList.Add(racc);
// Some code triggered by the user before saving to the db
CurrentCustomer.RetailAccountCardsBindingList.Remove(racc);
Strangely enough, the error that arises in 1. specifies the problem is to do with the relationship is on the RetailAccountCustomerId property of RetailAccountCustomerCard. IT HAS NOTHING to do with the Card object I added. It seems that simply setting any object property of the new entity triggers the problem.
NB. Example 1 works fine in terms of saving, it only causes a problem if the the new entity is deleted before saving.
I am running into a similar issue, as a workaround, I need to call DataContext.GetChanges(), then everything seems to have caught on again :)
Another problem you could have it that you are binding to columns and not entity properties, and hence the referential collections are not updated (already stated by someone else, but enforcing the fact).

Categories