How to map different navigation properties in TPH types to the same table? - entity-framework-4.1

I have this existing databse schema which implies self-reference many-to-many relationship using a joint table. The Location table may contain information Country, City, District, or Area according to the Disciminator field. The table RelatedLocation holds the self-reference relations.
My domain model is as follows, where the Location class is abstract one, and each inherited class conatins related navigation properties.
public abstract class Location
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Country : Location
{
public virtual ICollection<District> Districts { get; set; }
}
public class District : Location
{
public virtual ICollection<Country> Countries { get; set; }
public virtual ICollection<City> Cities { get; set; }
}
public class City : Location
{
public virtual ICollection<District> Districts { get; set; }
public virtual ICollection<Area> Areas { get; set; }
}
public class Area : Location
{
public virtual ICollection<City> Cities { get; set; }
}
On OnModelCreating I use the following to map each of inherited class many-to-many relation
modelBuilder.Entity<Country>()
.HasMany(c => c.Districts)
.WithMany(d => d.Countries)
.Map(t => t.ToTable("RelatedLocations").MapLeftKey("ParentId").MapRightKey("RelatedId"));
modelBuilder.Entity<City>()
.HasMany(c => c.Districts)
.WithMany(d => d.Cities)
.Map(t => t.ToTable("RelatedLocations").MapLeftKey("ParentId").MapRightKey("RelatedId"));
Upon creating the model I receive and exeption with "Each EntitySet must refer to a unique schema and table", that is EF complains about Mapping different relations to the same table "RelatedLocaions" more than once.
I do not know mapping this way is not supported in EF4.1 or I am mapping it in a wrong approach!

I doubt that the mapping you are trying is possible. I would try something similar to this:
public abstract class Location
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Location> ParentLocations { get; set; }
public virtual ICollection<Location> RelatedLocations { get; set; }
}
public class Country : Location
{
// readonly = not mapped
public IEnumerable<District> Districts
{
get { return RelatedLocations.OfType<District>(); }
}
}
public class District : Location
{
public IEnumerable<Country> Countries
{
get { return ParentLocations.OfType<Country>(); }
}
public IEnumerable<City> Cities
{
get { return RelatedLocations.OfType<City>(); }
}
}
// same approch for the other collections
And then this mapping:
modelBuilder.Entity<Location>()
.HasMany(l => l.ParentLocations)
.WithMany(l => l.RelatedLocations)
.Map(t => t.ToTable("RelatedLocations")
.MapLeftKey("ParentId")
.MapRightKey("RelatedId"));
The many-to-many mapping goes always between ParentLocations and RelatedLocations but these collections are populated with different instances of the derived classes according to the concrete type you are working with. The readonly collections are only helpers which perform a type cast in memory (based on the lazily loaded ParentLocations and RelatedLocations) of the Location entities.
Edit
Perhaps instead of using .OfType<T>() which filters all objects of type T from the source collection, .Cast<T>() is preferable which tries to cast all objects in the source collection to type T and throws an exception if casting is not possible. It should basically lead to the same result because ICollection<Location> in your base class should always only be populated by the same derived type. For example: Country.RelatedLocations should only contain entities of type District. But maybe the exception is good in this case because it indicates that something is wrong instead of silently ignoring the entities of another type in the collections (which OfType would do).
Edit 2
I want to emphasize that the IEnumerable collections are helpers which allow you to retrieve the entities with the derived type. The collections just perform a type cast, nothing more. They have nothing to do with the mapping to the database, EF even doesn't "see" that they exist. You can remove them and nothing would change in the EF model and the database table columns, relationships and referential constraints.
How would you add and retrieve entities in this model? Examples:
Add a new Country with a list of District entities:
var country = new Country() { RelatedLocations = new List<Location>() };
country.Name = "Palau";
// ParentLocations stays empty because Country has no parents
var district1 = new District { Name = "District1" };
var district2 = new District { Name = "District2" };
country.RelatedLocations.Add(district1); // because District is a Location
country.RelatedLocations.Add(district2);
context.Locations.Add(country); // because Country is a Location
context.SaveChanges();
Retrieve this entity again:
var country = context.Locations.OfType<Country>()
.SingleOrDefault(c => c.Name == "Palau");
// now get the districts, RelatedLocations is lazily loaded
var districts = country.RelatedLocations.Cast<District>();
// What type is districts? It's an IEnumerable<District>.
// So we can also use a helper property:
// var districts = country.Districts;
Retrieve a district:
var district = context.Locations.OfType<District>()
.SingleOrDefault(d => d.Name == "District1");
var countries = district.ParentLocations.Cast<Country>();
// or with the helper: var countries = district.Countries;
// countries collection contains Palau, because of many-to-many relation
Edit 3
Instead of creating a Country with new you can create a lazy loading proxy. Then you don't need to initialize the RelatedLocations collection. I was wondering how this could work with a derived type, but I just found that there is an overload of Create with a generic parameter for this purpose:
var country = context.Locations.Create<Country>();

Related

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?

Don't get data to my list from my model (Table)

I'm trying to get the data from my database in SQL, I use entity. This is my function in my controller:
public JsonResult getProductCategories()
{
List<Categories> category = new List<Categories>();
using (MasterDetailsEntities1 dc = new MasterDetailsEntities1())
{
category = dc.Categories.OrderBy(a => a.CategoryName).ToList(); -- I make a break here and doesn't pass anything and I have data in my table Categories.
}
return new JsonResult { Data = category, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
What I want to do is pass all the data from my model or table Categories to the list but it does pass me anything, i'm new doing this I don't know if I'm doing the right way.
This is my model:
public partial class Categories
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Categories()
{
this.Products = new HashSet<Products>();
}
public int CategoryID { get; set; }
public string CategoryName { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Products> Products { get; set; }
}
}
So, your problem here is not related to JSON result..it's for db not returning data from the table.
Please check your connection string for the MasterDetailsEntities1 context.
Do you get data from any other table using the same context? Please check.
Nothing looks improper here.

Retrieve tree structured json in ASP.NET Web Api for master-detail entities

I am new to using Asp.Net MVC 5 Web Api2 and I am developing a very small sample in which I've faced a weird issue. I have two very simple entity classes, "Student" and "Course" and each one of them has a "virtual ICollection" reference to the other one (making their relationship many-to-many). the code below shows the Student entity and the Course entity is very similar, and it refers to the student class.
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int AdmissionYear { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
I also have this small Api Controller:
public class StudentController : ApiController
{
private EFStudentRepository rep = new EFStudentRepository();
public IEnumerable<Student> Get()
{
return rep.Students;
}
public Student Get(int id)
{
Student s = rep.Students.First(x => x.Id == id);
if (s == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return s;
}
}
The problem is, when I run the app and invoke this controller, the json output shows the data for the student but does not show the courses that are associated with that student, something like this:
{"ID":1, "Name":"Mikel", "AdmissionYear":2010, "Courses":null}
While in fact in the database, the "StudentCourse" table has been automatically created and I have also filled it with proper data. It is especially weird as when I write a normal controller that renders HTML, all the data properly shows up, but in case of the Api controller it doesn't. So basically it does show the Master record's data but not it's detail records' data.
Thanks in advance.
You need to 'include' the navigation property in the query...
Student s = rep.Students.Include("Courses").First(x => x.Id == id);

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?

Deleting in EF Code first causes navigational properties to be set to null and empty

I noticed something interesting when I was performing a delete using EF code first. I use the following domain model:
public class User
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Playlist> Playlists { get; set; }
}
public class Playlist
{
public virtual long Id { get; set; }
public virtual string Title { get; set; }
public virtual User User { get; set; }
public virtual ICollection<Track> Tracks { get; set; }
}
public class Track
{
public virtual long Id { get; set; }
public virtual string Title { get; set; }
public virtual Playlist Playlist { get; set; }
}
The model is configured using:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasMany(x => x.Playlists).WithRequired(x => x.User).Map(x => x.MapKey("UserId"));
modelBuilder.Entity<Playlist>().HasMany(x => x.Tracks).WithRequired(x => x.Playlist).Map(x => x.MapKey("PlaylistId"));
}
I use a generic repository:
public virtual void Delete(T entity)
{
Database.Set<T>().Remove(entity);
}
I also have a dto that looks like:
public class PlaylistDTO
{
public PlaylistDTO(Playlist playlist)
{
Id = playlist.Id;
Title = playlist.Title;
User = playlist.User.Name;
}
}
In one of my services I am trying to do the following:
public PlaylistDTO Delete(long id)
{
Playlist playlist = playlistRepository.GetById(id);
playlistRepository.Delete(playlist);
unitOfWork.Commit();
return PlaylistDTO(playlist);
}
This code fails. When I stepped through the debugger I noticed something interesting. The moment I call playlistRepository.Delete the navigational properties (User and Tracks) get set to null and empty respectively. Playlist however stays in memory. So when I pass in the playlist to the DTO the code will fail when it is trying to access playlist.User.Name. I wanted to pass this data to the client to display a verification.
Is this behavior correct? Is this by design?
This is how EF works. The problem is that your Playlist forms entity graph with other relations and EF uses very simple rule for tracking entity graphs: All entities in the graph must be tracked - there cannot be reference to entity which is not tracked. I don't give you reference to description of this rule, it is just my observation but I didn't find any single exception to this rule.
Edit: Updated version - I just checked internal implementation and relations are indeed nulled during calling Delete
So what happened in your code.
You marked your Playlist as deleted
EF passes delete operation to the state manager which does the fixup - it will null all relations
You saved changes to the database
Because there are no cascade deletes from Playlist all related objects remain undeleted
Once you saved changes EF internally accepted them and set change tracker to current state
Because the current state of Playlist is non existing (deleted in the database) it was detached from the context
Detaching has broken entity graph and EF fixed it by modifying navigation properties on both ends
The code responsible for nulling from System.Data.Objects.EntityEntry.Delete(doFixup) (doFixup is true) - the class is internal:
if (doFixup && (base.State != EntityState.Deleted))
{
this.RelationshipManager.NullAllFKsInDependentsForWhichThisIsThePrincipal();
this.NullAllForeignKeys();
this.FixupRelationships();
}
In your scenario this should have simple workaround - create DTO before you delete entity.