EF 4.1 Code First Mapping two columns in one table to list - entity-framework-4.1

I'm having an issue figuring out how to make this work for EF 4.1 Code First. I've looked around and found a similar problem, but I couldn't get it to work for me and it sounds like it didn't get answered for this person either.
Here is a simplified version of the two objects in question.
public class Team
{
public int TeamId {get; set;}
public virtual IList<Game> Games {get; set;}
}
public class Game
{
public int GameId {get; set; }
public int AwayTeamId {get; set;}
public int HomeTeamId {get; set;}
public virtual Team HomeTeam { get; set; }
public virtual Team AwayTeam { get; set; }
}
And here is my code for registering the FKs
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Game>()
.HasRequired(a => a.HomeTeam)
.WithMany()
.HasForeignKey(u => u.HomeTeamId).WillCascadeOnDelete(false);
modelBuilder.Entity<Game>()
.HasRequired(a => a.AwayTeam)
.WithMany()
.HasForeignKey(u => u.AwayTeamId).WillCascadeOnDelete(false);
}
I want to bring back all games (home or away) that a team is in. Right now EF is creating a TeamId column in my database that never gets populated. If what I want is impossible, then I could do lists of HomeGames and AwayGames and another list of Games that is a combination of the two, but I'd like to try and avoid it if possible. I'm still learning this so any extra explanations or tips would be appreciated.

What you're asking of EF is a read only collection, i.e., an add operation would be meaningless for such a collection since EF wouldnt know into which table to insert, which I believe is unsupported.
Judging by your problem description, I would so something similar to your own suggestion and create a method such as GetGames() which would return just a unioned set of both home and away games. I think this is conceptually cleaner anyway.

EF is not able to map two relations into single navigation property. Your additional TeamId column is created because Games navigation property is considered as separate relation (your mapping says that neither HomeTeam or AwayTeam are part of that relation) so you instructed EF to create three relations for your.
You must define your model this way:
public class Team
{
public int TeamId {get; set;}
public virtual ICollection<Game> HomeGames { get; set; }
public virtual ICollection<Game> AwayGames { get; set; }
}
And mapping must be:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Game>()
.HasRequired(a => a.HomeTeam)
.WithMany(t => t.HomeGames)
.HasForeignKey(u => u.HomeTeamId).WillCascadeOnDelete(false);
modelBuilder.Entity<Game>()
.HasRequired(a => a.AwayTeam)
.WithMany(t => t.AwayGames)
.HasForeignKey(u => u.AwayTeamId).WillCascadeOnDelete(false);
}
Now if you want all team's games you can use simply:
var games = team.HomeGames.Concat(team.AwayGames);
You can wrap this into method returning IEnumerable or into property with just getter returning IEnumerable.

Related

How to create a dependency graph using composite keys in EF Core

Trying to store a composite key table which is keyed for both fields to the table it defines dependencies for.
Example case
Import files: 1..10
Dependencies 1: 2,3; 2: 4,5; 4:10
Intent is to use this key-only table for code to do code first strongly typed definitions while also being light weight, and it seemed like the most straight forward way to do it before running into problems.
Current code:
public class ImportFileDependency
{
[Key]
[ForeignKey("ImportFile")]
public int ImportFileId { get; set; }
[ForeignKey("Id")]
public ImportFile ImportFile {get; set;}
[Key]
[ForeignKey("ImportFile")]
public int ImportFileDependencyId { get; set; }
[ForeignKey("Id")]
public ICollection<ImportFile> ImportFileDependencies { get; set; }
}
public class ImportFile
{
[Key]
public int Id {get; set;}
public string Name { get; set; }
public string WorkbookTab { get; set; }
public string File { get; set; }
public ICollection<ImportFileDependency> Dependencies { get; set; }
}
...
modelBuilder
.Entity<ImportFileDependency>(e =>{
e.HasKey(ifd => new { ifd.ImportFileId, ifd.ImportFileDependencyId });
e.HasOne(ifd => ifd.ImportFile)
.WithMany(i => i.Dependencies);
});
modelBuilder
.Entity<ImportFile>()
.HasMany(i => i.Dependencies)
.WithOne()
.HasForeignKey(z => z.ImportFileId);
...
After multiple revisions of following the responses of the add-migration exception response, currently on:
There are multiple properties pointing to navigation 'ImportFile' in entity type 'ImportFileDependency'. To define composite foreign key using data annotations, use ForeignKeyAttribute on navigation.
which did not update from the most recent iteration.
I seem to have recursed into a deadend so looking for guidance
Given the time you've asked it, you probably found the answer yourself or gave up on it, but if someone else struggles with this error, this solved my issue: Entity Framework Code First - two Foreign Keys from same table
You have to define the relationship using fluent API.

EntityFramework Include and possibly join?

I have the following table structure as shown in the picture. (see: Table structure). Both tables ("Batches" and "Methods") reference to a "Project" table.
When I now create a new Project I would like to get all childs created as well.
Doing so I did the follwoing:
_dbContext.Projects.Where(x => x.Id == prjId)
.Include(x => x.Batches)
.Include(x => x.Batches.Select(y => y.Measurements))
.Include(x => x.Methods).AsNoTracking().FirstOrDefault();
Now the problem is the following:
New Batch and Method instances are created - thus they get a new ID(PK). The referenced Project_Id (FK) is set correct. But in my new Measurement instance only the Batch_Id(FK) is set correct and the Method_Id remains unchanged (has the old value) (see: result).
What I need is that the Measurements.Mehtod_Id is set from the Methods table. Is there any suitable solution for that?
My entities look like the following
public class Project
{
[Key]
public long Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public virtual List<Batch> Batches { get; set; }
public virtual List<Method> Methods { get; set; }
}
public class Batch : BaseObject
{
public Batch()
{
BatchFiles = new List<FileAttachment>();
Measurements = new List<Measurement>();
}
public long Id { get; protected set; }
public long Project_Id { get; set; }
public virtual Project Project { get; set; }
public virtual List<Measurement> Measurements { get; set; }
}
public class Method : BaseObject
{
public Method()
{
Parameters = new List<Parameter>();
}
public long Id { get; protected set; }
public long Project_Id { get; set; }
public virtual Project Project { get; set; }
public virtual List<Measurement> Measurements { get; set; }
}
public class Measurement
{
public int Id { get; protected set; }
[ForeignKey("Batch")]
public long? Batch_Id { get; set; }
[Required]
public virtual Batch Batch { get; set; }
[ForeignKey("Method")]
public long? Method_Id { get; set; }
public virtual Method Method { get; set; }
}
// creation code (just a copy with new IDs for all childs)
Project newProjectVersion = _dbContext.Projects.Where(x => x.Id == prjId)
.Include(x => x.Batches)
.Include(x => x.Batches.Select(y => y.Measurements))
.Include(x => x.Methods)
.AsNoTracking().FirstOrDefault();
_dbContext.Projects.Add(newProjectVersion);
_dbContext.SaveChanges();
Thanks for any help!
The first problem is that your Select statement doesn't connect Measurements to Methods because of the AsNoTracking() addition. Only Projects and Methods are connected because they are explicitly Included off of the Project entity. The Measurements have a Method_id but this is value is not accompanied by a Method in their Method property. You could check that in the debugger if you walk through the object graph (with lazy loading disabled though!). Because of this, when all entities will be Add-ed to the context, EF won't notice that measurements receive new methods.
You could get tempted to fix that by Include-ing Measurement.Method as well:
...
.Include(x => x.Batches.Select(y => y.Measurements.Select(m => m.Method)))
...
Now you'll see that Measurement.Method will be populated everywhere in the object graph.
However, there's a gotcha here. When using AsNoTracking, EF6 doesn't keep track of entities it materialized (duh). This means that for each Measurement it creates a new Method instance, even if an identical Method (by id) was materialized before for another Measurement. (And in this case it will always materialize duplicates, because you already include Project.Methods.)
That's why you can't do this in the quick way with AsNoTracking and Add using one context instance. You'll get an error that EF tries to attach duplicate entities.
You must build the object graph using one context, with tracking, so EF will not materialize duplicates. Then you must Add this object graph to a new context. Which will look like this:
Project project;
using(var db = new MyContext())
{
db.Configuration.ProxyCreationEnabled = false;
project = db.Projects.Where(x => x.Id == prjId)
.Include(x => x.Batches)
.Include(x => x.Batches.Select(y => y.Measurements))
.Include(x => x.Methods).FirstOrDefault();
}
using(var db = new MyContext())
{
db.Projects.Add(project);
db.SaveChages();
}
Three remarks:
Proxy creation is disabled, because you can't attach a proxy to another context without explicitly detaching it first.
No, I didn't forget to include Measurement.Method. All methods are loaded by including them in the Project and now (because of tracking, and assuming that measurement will only have methods of the project they belong to), EF connects them with the Measurements by relationship fixup.
EF-core is smarter here: when adding AsNoTracking it won't track materialized entities, but still, it won't create duplicates either. It seems to have some temporary tracking during the construction of an object graph.
thanks for your answer so far. This works quite fine right now. Unfortunately I noticed that the Measurements entity has another required relationship to a table named 'MeasurementTypes':
[Required]
public virtual MeasurementType MeasurementType { get; set; }
[ForeignKey("MeasurementType")]
public long MeasurementType_Id { get; set; }
In contrast to Batches and Methods these entries must not be copied and the entries already exist in the MeasrementTypes table.
What would be a good way to put the required reference to the Measurements?

EF CodeFirst self-referential Many-to-Many...on abstract or derived classes

I'm trying to model a self-referencing many to many in EF CodeFirst with a polymorphic table structure. I'm using the October 2011 CTP which supports navigation properties on derived types (which works well in other tests I've done).
The problem:
When I set up this particular many to many relationship in the base (abstract) table's mapping and try to get related records, I get a SQL query with hundreds of K of unions and joins...just the time taken to generate the SQL statement is 30 seconds, compared to bare milliseconds to execute it. However, it does return appropriate results. When I change the many to many to exist between two derived objects, the query produced is perfect...but I can't map the same relating M2M table again for other derived objects without being informed that the joining table has "already been mapped".
Specifics:
An existing database structure has a base table--Party--which is joined 1...1 or 0 with Customer, Vendor, User, and Department (each a type of Party).
Parties are related to each other via an existing join table PartyRelationship (ID, InternalPartyID, ExternalPartyID). By convention, InternalPartyID contains a User's PartyID and ExternalPartyID contains the PartyID of the Customer, Vendor, or Department with which they are associated.
Trying to use EF CodeFirst in a new project (WCF DataServices), I have created the Party class as:
public abstract class Party
{
public Party()
{
this.Addresses = new List<Address>();
this.PhoneNumbers = new List<PhoneNumber>();
this.InternalRelatedParties = new List<Party>();
this.ExternalRelatedParties = new List<Party>();
}
public int PartyID { get; set; }
public short Active { get; set; }
//other fields common to Parties
public virtual ICollection<Address> Addresses { get; set; }
public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
public virtual ICollection<Party> InternalRelatedParties { get; set; }
public virtual ICollection<Party> ExternalRelatedParties { get; set; }
}
Then, using TPT inheritance, Customer, Vendor, Department and User are similar to:
public class Customer : Party
{
public string TermsCode { get; set; }
public string DefaultFundsCode { get; set; }
//etc
}
public class User : Party
{
public string EmployeeNumber { get; set; }
public string LoginName { get; set; }
//etc
}
The joining table:
public class PartyRelationship
{
public int PartyRelationshipID { get; set; }
public int InternalPartyID { get; set; }
public int ExternalPartyID { get; set; }
//certain other fields specific to the relationship
}
Mappings:
public class PartyMap : EntityTypeConfiguration<Party>
{
public PartyMap()
{
// Primary Key
this.HasKey(t => t.PartyID);
// Properties
this.ToTable("Party");
this.Property(t => t.PartyID).HasColumnName("PartyID");
this.Property(t => t.Active).HasColumnName("Active");
//etc
// Relationships
this.HasMany(p => p.InternalRelatedParties)
.WithMany(rp => rp.ExternalRelatedParties)
.Map(p => p.ToTable("PartyRelationship")
.MapLeftKey("ExternalPartyID")
.MapRightKey("InternalPartyID"));
}
}
public class PartyRelationshipMap : EntityTypeConfiguration<PartyRelationship>
{
public PartyRelationshipMap()
{
// Primary Key
this.HasKey(t => t.PartyRelationshipID);
// Properties
// Table & Column Mappings
//this.ToTable("PartyRelationship"); // Commented out to prevent double-mapping
this.Property(t => t.PartyRelationshipID).HasColumnName("PartyRelationshipID");
this.Property(t => t.InternalPartyID).HasColumnName("InternalPartyID");
this.Property(t => t.ExternalPartyID).HasColumnName("ExternalPartyID");
this.Property(t => t.CreateTime).HasColumnName("CreateTime");
this.Property(t => t.CreateByID).HasColumnName("CreateByID");
this.Property(t => t.ChangeTime).HasColumnName("ChangeTime");
this.Property(t => t.ChangeByID).HasColumnName("ChangeByID");
}
}
Context:
public class MyDBContext : DbContext
{
public MyDBContext()
: base("name=MyDBName")
{
Database.SetInitializer<MyDBContext>(null);
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Party> Parties { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
modelBuilder.Configurations.Add(new PartyMap());
modelBuilder.Configurations.Add(new PartyRelationshipMap());
}
}
A URL such as http://localhost:29004/Services/MyDataService.svc/Parties(142173)/SAData.Customer/InternalRelatedParties eventually returns correct oData but takes 30 seconds to produce an enormous SQL statement (189K) that executes in 600 ms.
I've also tried mapping the PartyRelationship table with a bidirectional one to many (both to Party as the "one" table), but with a similar outcome.
Do I need separate join tables for Customer-User, Vendor-User, and Department-User? Should I look at vertical table splitting or database views that separates PartyRelationship into separate logical entities (so I can remap the same table)? Is there another way the EF model should be configured in this scenario?

EF 4.1 code-first: How to design and map these entities?

I have 3 entities: Member, AuthenticationToken, and Email.
Each Member may has many AuthenticationTokens
Each AuthenticationToken may has one or zero Email
Each Member may has zero or one PrimaryEmail (from Emails table). Really the PrimaryEmail is one of the AuthenticationTokens's associated Email
So I have:
public class Member {
public int MemberId { get; set; }
public int? PrimaryEmailId { get; set; }
public virtual Email PrimaryEmail { get; set; }
public virtual ICollection<AuthenticationToken> AuthenticationTokens { get; set; }
}
public class AuthenticationToken {
public int AuthenticationTokenId { get; set; }
public int MemberId { get; set; }
public virtual Member Member { get; set; }
public virtual Email Email { get; set; }
}
public class Email {
public int EmailId { get; set; } // is same as AuthenticationTokenId that the email associated with it
}
With design I explained above, I can add Member and AuthenticationToken, but when I want to attach a Email to a Member or AuthenticationToken (or both) I give this error:
The INSERT statement conflicted with the FOREIGN KEY constraint etc.
Is this design correct???
How can I design my tables (and entities) to achieve my purpose?
And how can I map my entities in code-first? Have you any idea please?
I personally use the fluent API in EF 4.1 to configure all of my entities when I don't feel the default conventions will understand me, so I will answer using the fluent API.
Here is how I would set up the models:
public class Member
{
public Member()
{
AuthenticationTokens = new List<AuthenticationToken>();
}
public int MemberId { get; set; }
public virtual Email PrimaryEmail { get; set; }
public virtual ICollection<AuthenticationToken> AuthenticationTokens { get; set; }
}
public class AuthenticationToken
{
public int AuthenticationTokenId { get; set; }
public virtual Email Email { get; set; }
}
public class Email
{
public int EmailId { get; set; }
}
And this is my context and fluent configuration:
public class ExampleApplicationContext : DbContext
{
public ExampleApplicationContext()
: base("ExampleApplicationConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// No cascade on delete because the primary email may be held by an authentication token.
modelBuilder.Entity<Member>()
.HasOptional(x => x.PrimaryEmail)
.WithOptionalDependent()
.Map(x =>
{
x.MapKey("EmailId");
})
.WillCascadeOnDelete(false);
// Cascade on delete because an authentication token not associated with a Member makes no sense.
modelBuilder.Entity<Member>()
.HasMany(x => x.AuthenticationTokens)
.WithRequired()
.Map(x =>
{
x.MapKey("MemberId");
})
.WillCascadeOnDelete();
// No cascade on delete because an email may be held by a Member.
modelBuilder.Entity<AuthenticationToken>()
.HasOptional(x => x.Email)
.WithOptionalDependent()
.Map(x =>
{
x.MapKey("EmailId");
})
.WillCascadeOnDelete(false);
}
public DbSet<Member> Members { get; set; }
}
I will do my best here to explain my reasoning as to why I designed it like this. First of all, it appears that in your model Member should be the root aggregate (the boss of the other entities). What I mean is an Authentication Token makes no sense unless it belongs to a specific Member. An Email also makes no sense alone unless it either belongs to a Member or belongs to an AuthenticationToken. For this reason AuthenticationToken does not have a property to find out what Member it is attached to (to find this out you first need a Member and than just look at its collection). Essentially, everything revolves around a Member object. Without a Member an AuthenticationToken cannot be created. And without a Member or an AuthenticationToken an Email cannot be created.
I'm not entirely sure how comfortable you are with the fluent API in EF 4.1, so if you have any questions leave a comment and I will do my best to answer them. I have also included a small sample application that I used to build and verify the model I presented above. If you want to run the program (it is a small Console app) you just have to modify the connection string in App.config to point to your instance of SQL Server.
One thing that concerns me is the fact that Email can belong to both a Member and an AuthenticationToken. My concern comes from the fact that I had to setup some interesting cascade deletes. I don't know all of your requirements, however, and this setup appears to work just fine so that may not be an issue.
Example Console Application

Entity Framework 4.1 one to one relationship nullable

Hello everybody again,
I need some help in this logic for EF 4.1
I have one table with data for a customer. I have also another table with a survey i need to compile when needed.
So initally i could insert a new customer and after some days I'll fill the survey form. Then the relationship MUST be one-to-one and optional (just because this survey could never be compiled for a customer).
I digged in some examples online but i'm really stuck.
Thank you in advance.
Simply define your entities like:
public class Customer
{
public int Id { get; set; }
...
public virtual Survey Survey { get; set; }
}
public class Survey
{
[Key, ForeignKey("Customer")]
public int Id { get; set; }
public virtual Customer Customer { get; set; }
}
If you don't like data annotations remove them and place this into OnModelCreating in your context:
modelBuilder.Entity<Customer>()
.HasOptional(c => c.Survey)
.WithRequired(s => s.Customer);