Two many-to-one table relationships and insertion with Entity Framework - c#

I have a model similar to this:
Context: The idea is that it's a database of samples. One sample has details, and several samples can be collated together into a CollatedSample, and the details can be collated together as well in a CollatedDetail. So, one CollatedSample has many CollatedDetails, and starts from many Samples, each of which has many Details. A CollatedDetail has many Details too. It's a "nice" square.
My approach to adding records is thus:
var sample = new Sample();
var detail = new Detail();
sample.Details.Add(detail);
// suppose I add a bit more meat to these entities...
var collatedSample = new CollatedSample();
var collatedDetail = new CollatedDetail();
collatedSample.Samples.Add(sample);
collatedSample.CollatedDetails.Add(collatedDetail);
collatedDetail.Details.Add(detail);
context.CollatedSamples.AddObject(collatedSample);
context.SaveChanges();
So I've added all elements to eachother, and added Detail to both Sample and CollatedDetail. On SaveChanges, I get an Update Exception with the jolly message:
Unable to determine the principal end of the 'SamplingModel.FK_Detail_CollatedDetailId' relationship. Multiple added entities may have the same primary key.
What I think might really be happening is there is an attempt to record the Detail entity before the CollatedDetail is recorded. That Detail table, with its two relationships, is the one causing the trouble (not adding them to either Sample nor CollatedDetail confirms it). Perhaps there is a way to specify the order of insertion? I also tried the reverse, to set the parent instead of using .Add() on children collections, with the same result. Otherwise, how do I make this sort of 2-pronged insertion in one shot?
EDIT: tl;dr:

I found a workaround: I removed the Foreign Key between Detail and CollatedDetail like #JaderDiag suggested, and the reference field from the CollatedDetail table. Entity Framework creates partial classes, so it was easy to create other partial classes for both entities and manually join them. This will be much much slower, I suspect, but it provides the same fluidity in later exploitation as the entities would have provided with the foreign key.
This is a workaround, and definitely not the solution I was looking for. Would vote this down if I could.

Related

SQL LINQ query keeps looping through two tables

I am trying to call a linq query in asp.net 5. However, my query results in an infinite loop of data between two tables.
Logs (logs_historical)
meterID (FK)
log_data
-----
Meters
meter_uid (PK)
My goal is to have multiple logs related to one meter.
Relationships
Foreign key Base Table = logs
Foreign key Column = meterID
Primary Base Table = meters
Primary Key Colum = meterID
This is my linq query
records = dbContext.logs.Where(e =>(e.timestamp.Value.Month == inputDate.Month)).ToList() ;
1) This screenshot shows the table information and (foreign key table)
When I look into the foreign key table information, it also has the logs.
Going into the logs will show the same table from picture (1)
2)
I'm sure this was asked before, but I do not know what this problem is called. I am new to databases. Thanks for any advice!
As a quick fix, you can probably use dbContext.Configuration.LazyLoadingEnabled = false;
just before your query. You can disable it globally as well.
What happens is Entity Framework will eagerly load all related entities. If you have entities that go several levels deep, or that reference each other, well, as you have experienced, it will continue to follow those relationships. Disabling lazy loading will prevent this. Please note that you will have to explicitly declare anything you want included via the .include() function.
The other option is to redesign your database so that this isn't the case, but sometimes that just isn't feasible. I don't really know the intricate workings of your project, so I am speculating here.
EDIT:
I forgot to add, if you go into the model browser and explore the models that were created for you by Entity Framework, you will find those relationships that are causing the issue. If you are using code first, you can easily fix this. If you are using database first, you are somewhat at the mercy of how EF interprets relationships.

Entity Framework Updating Many to Many relationships properly

In my School EF Model, I have Kids and Tutorials in many-to-many relationship.
Let's assume both Kids and Tutorials have existing items in them, now we just want to change their existing relationships. That is, to add/delete some tutorials from a kid.
var kid; //the request target to modify relationships
//kid.Tutorials has the old existing relationships to be modified by add/del
var tutorialsToAdd; //the request to add relationships
var tutorialsToDel; //the request to del relationships
using (var conn = new SchoolEFModels(efConnectionStr)) {
conn.Kids.Attach(kid);
kid.Tutorials.ForEach(t => conn.Tutorials.Attach(t));
kid.Tutorials.AddRange(tutorialsToAdd); //simple add extension in batch
kid.Tutorials.RemoveRange(tutorialsToDel); //simple del extension in batch
conn.SaveChanges();
}
When I do this, I got an exception saying:
"Cannot insert duplicate key in object 'dbo.Tutorials'. The duplicate key value is (10)."
I can see EF is trying to create new Tutorial items instead of updating the existing relationship for me. Which is what I don't want. You misunderstood me EF!
What is wrong with my code? How do I make it update Many-to-Many relationships?
I figured it out.
Adding/removing it will make EntityState turn to Added/Deleted. Therefore, causing it to reinsert existing Ids as the article mentioned, thank Gert there for the link.
So, if you modify each of the conn.entry(kid/tutorials).State to EntityState.Modified and then call conn.ChangeTracker.DetectChanges(); then conn.SaveChanges(); it will only update the many-to-many table as expected.
UPDATE:
One thing you need to be careful tho. If the in-memory objects list of Kids and Tutorials are linked to each other. e.g. Kids[0].Tutorials[0] == Tutorials[0] && Tutorial[0].Kids[0] == Kids[0] EF will not be able to handle this dead loop for you. You need to break this circular link first.
To do so, my approach is to open a Connection and read the Kid out Includes(Tutorials), and then use the result to update many to many relationship, but not to use the in-memory objects.

EF DB-first mapping mess

I have a frustrating situation owing to this little quirk of EF. Here's a simple demo of the behavior. First the DB schema:
As you see, RestrictedProduct is a special case of product, which I'm intending to make a subclass of Product with some special code.
Now I import to an EF data model:
Oops! EF saw that RestrictedProduct had only 2 fields, both FKs, so it mapped it as a one-to-many relationship between Product and Restriction. So I go back to the database and add a Dummy field to RestrictedProduct, and now my EF model looks much better:
But that Dummy field is silly and pointless. Maybe I could delete it? I blow away the field from the DB table and the entity model, then refresh the model from the DB...
Oh, no! The Product-Restriction association is back, under a new name (RestrictedProduct1)! Plus, it won't compile:
Error 3034: Problem in mapping fragments starting at lines (x, y) :Two entities with possibly different keys are mapped to the same row. Ensure these two mapping fragments map both ends of the AssociationSet to the corresponding columns.
Is there any way to prevent this behavior, short of keeping the Dummy field on the RestrictedProduct table?
I just came across the same issue, and as an alternative to putting the dummy field in your RestrictedProduct table to force the creation of an entity you can also make your RestrictedProduct.RestrictionId field nullable and EF will then generate an entity for it. You can then modify it to use inheritance and any subsequent "Update model from database" will not cause undesired nav properties. Not really a nice solution but a work around.
Let's walk slowly into your problem.
1st thing you need to decide is if the restricted product is
really a special case of product or is it a possible extension
to each product.
From your original DB Scheme it seems that any product may have
a relation to a single restriction however a single restriction
can be shared among many products.. so this is a simple 1 to many
situation which means that restricted product is NOT a special case
of product! Restriction is an independent entity which has nothing
to do with product in a specific way.
Therefore EF is correct in the 1st importation of your scheme:
1. a product can have 0 or 1 restrictions.
2. a restriction is another entity which can be related to many products.
I do not see your problem.

How can I manage ids of entities in Linq2SQL?

Such a task: we have 2 tables in our L2S Entity classes. It needs to manage with current fields of current tables by numbering em somehow.
Exact question is How can I point to the exact field of exact table without using entity relation names? Such as TmpLinqTable[2] instead of TmpLinqTable.TableField.
Moreover if it can be managed by ids of the entity, not the table.
So my understanding of what you are trying to do is to log changes that happen to your entites. Is that correct? You might want to look into the GetModifedMembers method on the Table class. Here's an interesting link...
http://geekswithblogs.net/steveclements/archive/2008/04/15/linq-to-sql-property-changed--changing-logging.aspx

sorting on related field in llblgen 2.6

I inherited an application that uses llblgen 2.6. I have a PersonAppointmentType entity that has a AppointmentType property (n:1 relation). Now I want to sort a collection of PersonAppointmentTypes on the name of the AppointmentType. I tried this so far in the Page_Load:
if (!Page.IsPostBack)
{
var p = new PrefetchPath(EntityType.PersonAppointmentTypeEntity);
p.Add(PersonAppointmentTypeEntity.PrefetchPathAppointmentType);
dsItems.PrefetchPathToUse = p;
// dsItems.SorterToUse = new SortExpression(new SortClause(PersonAppointmentTypeFields.StartDate, SortOperator.Ascending)); // This works
dsItems.SorterToUse = new SortExpression(new SortClause(AppointmentTypeFields.Name, SortOperator.Ascending));
}
I'm probably just not getting it.
EDIT:
Phil put me on the right track, this works:
if (!Page.IsPostBack)
{
dsItems.RelationsToUse = new RelationCollection(PersonAppointmentTypeEntity.Relations.AppointmentTypeEntityUsingAppointmentTypeId);
dsItems.SorterToUse = new SortExpression(new SortClause(AppointmentTypeFields.Name, SortOperator.Ascending));
}
You'll need to share more code if you want an exact solution. You didn't post the code where you actually fetch the entity (or collection). This may not seem relevant but it (probably) is, as I'm guessing you are making a common mistake that people make with prefetch paths when they are first trying to sort or filter on a related entity.
You have a prefetch path from PersonAppointmentType (PAT) to AppointType (AT). This basically tells the framework to fetch PATs as one query, then after that query completes, to fetch ATs based on the results of the PAT query. LLBLGen takes care of all of this for you, and wires the objects together once the queries have completed.
What you are trying to do is sort the first query by the entity you are fetching in the second query. If you think in SQL terms, you need a join from PAT=>AT in the first query. To achieve this, you need to add a relation (join) via a RelationPredicateBucket and pass that as part of your fetch call.
It may seem counter-intuitive at first, but relations and prefetch paths are completely unrelated (although you can use them together). You may not even need the prefetch path at all; It may be that you ONLY need the relation and sort clause added to your fetch code (depending on whether you actually want the AT Entity in your graph, vs. the ability to sort by its fields).
There is a very good explanation of Prefetch Paths and how they were here:
http://www.llblgening.com/archive/2009/10/prefetchpaths-in-depth/
Post the remainder of your fetch code and I may be able to give you a more exact answer.

Categories

Resources