Here are 2 (minimized classes):
[Index(nameof(Name), IsUnique = true)]
public partial class ParticipantList
{
public ParticipantList()
{
Emails = new HashSet<Email>();
}
[Key]
[Column("id")]
public long Id { get; set; }
//...
[InverseProperty(nameof(Email.ParticipantList))]
public virtual ICollection<Email> Emails { get ; set; }
}
public partial class Email
{
[Key]
[Column("id", TypeName = "integer")]
public long Id { get; set; }
[Column("participant_list_id", TypeName = "integer")]
public long? ParticipantListId { get; set; }
/...
[ForeignKey(nameof(ParticipantListId))]
[InverseProperty("Emails")]
public virtual ParticipantList ParticipantList { get; set; }
}
And the DbContext OnConfiguring method contains:
optionsBuilder
.UseLazyLoadingProxies()
.UseSqlite(...);
And the DBContext OnModelCreating method contains:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Email>(entity =>
{
entity.HasOne(d => d.ParticipantList)
.WithMany(p => p.Emails)
.HasForeignKey(d => d.ParticipantListId)
.OnDelete(DeleteBehavior.ClientSetNull);
});
This is what works:
var email = _db.Emails.Find(1);
// email.ParticipantList (this lazy loads just fine).
What doesn't work:
var plist = _db.ParticipantLists.Find(1);
// plist.Emails.Count() == 0 (this doesn't lazy load at all)
// plist.Emails.Count == 0 (again no lazy loading)
// plist.Emails.ToList() (empty list, no lazy loading)
I should insert a rant here about the stupidity of having both Count() and Count with different meanings here. I mean, seriously, who is going to catch that during a code review????
This is most likely due to:
public ParticipantList()
{
Emails = new HashSet<Email>();
}
Which was created by the ef-scaffold app. I mean it makes sense, the HashSet is empty, but it seems like the model should override the access to the HashSet and do the lazy loading.
The problem is that if you remove this:
public ParticipantList()
{
// Emails = new HashSet<Email>();
}
And then call this code:
// plist.Emails.Count() == 0 (Now you get a null reference exception
// because guess what: Emails is not initialized!)
// plist.Emails.ToList() (Also gives a null reference exception)
// plist.Emails.Count == 0 (Again, a null reference exception)
The documentation shows code like this (note that Posts is NOT initialized in the example at https://learn.microsoft.com/en-us/ef/core/querying/related-data/lazy ):
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public virtual Blog Blog { get; set; }
}
Which will purportedly lazy load the Posts from the Blog. But when I follow that pattern and remove the ctor that initializes Emails/Posts I get a null reference exception.
To add to the mystery/madness is this:
var email = _db.Emails.Find(1);
var plist = _db.ParticipantLists.Find(1);
// plist.Emails.Count() == 1 (It knows about the relation and added it to
// the Emails HashSet on the ParticipantList!)
So, how do I get Emails to LazyLoad. I know about the 1 + N problem, and I'm not worried about that aspect, but I'd like the models to load when they are requested.
Related
I am working on a legacy app that mainly manages employees and contractors. Below is an excerpt of the EF Core 3.1 legacy model. The complete source code is available here.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int? ManagerId { get; set; }
public virtual Person Manager { get; set; }
}
public class Employee : Person
{
public int Grade { get; set; }
}
public class Contractor : Person
{
public int ContractorId { get; set; }
public virtual ContractingCompany Company { get; set; }
}
public class ContractingCompany
{
public int Id { get; private set; }
public string Name { get; private set; }
private readonly List<Contractor> _contractors = new List<Contractor>();
public virtual IReadOnlyList<Contractor> Contractors => _contractors;
protected ContractingCompany()
{
}
public ContractingCompany(string name) : this()
{
Name = name;
}
public void Add(Contractor contractor)
{
_contractors.Add(contractor);
}
}
These entities pull data from the same table as we are using TPH strategy.
We are extending our application and renaming the contractors to Partners instead. We decided to go with DDD this time and have new models read existing data using table splitting. As we move things to the new model, we need to keep the app working, so we can't remove the legacy model altogether until all use cases have moved to the new DDD model.
The DDD model is as follows and will pull data from the existing database:
public class Partner /* Pulls data from the Contracting Company */
{
public int Id { get; set; }
public string Name { get; set; }
private readonly List<PartnerEmployee> _employees = new List<PartnerEmployee>();
public virtual IReadOnlyList<PartnerEmployee> Employees => _employees;
protected Partner(){}
}
public class PartnerEmployee /* Pulls data from the Contractor table */
{
public int Id { get; set; }
public int ContractorId { get; set; }
}
The EF mappings are as follows:
public class PartnerConfiguration : IEntityTypeConfiguration<Partner>
{
public void Configure(EntityTypeBuilder<Partner> builder)
{
builder.ToTable("ContractingCompany");
builder.HasKey(c => c.Id);
builder.Property(c => c.Id).HasColumnName("Id");
builder.Property(c => c.Name).HasColumnName("Name");
builder.HasOne<ContractingCompany>().WithOne().HasForeignKey<Partner>(c => c.Id);
}
}
public class PartnerEmployeeConfiguration : IEntityTypeConfiguration<PartnerEmployee>
{
public void Configure(EntityTypeBuilder<PartnerEmployee> builder)
{
builder.ToTable("Person");
builder.HasKey(c => c.Id);
builder.Property(c => c.Id).HasColumnName("Id");
builder.Property(c => c.ContractorId).HasColumnName("ContractorId");
builder.Property<int?>("PartnerId").HasColumnName("CompanyId");
builder.HasOne<Contractor>().WithOne().HasForeignKey<PartnerEmployee>(c => c.Id);
}
}
Problem: we are trying to read the existing data from the database:
var contractingCompany = context.ContractingCompanies.First(); <-- Works fine
var partner = context.Partners.First(); <-- Crashes
The second line above throws an exception:
Microsoft.Data.SqlClient.SqlException:
Invalid column name 'Contractor_CompanyId'.
Invalid column name 'ContractorId1'.'
Can anyone help me understand why EF looks up columns Contractor_CompanyId ContractorId1?
Looks like configuring a column name for a property of an entity participating in table splitting (other than PK) invalidates the conventional column names for the other participating entity/entities.
Unfortunately this behavior is not explained in the table splitting documentation (it should), only a small text to to accompanying example saying
In addition to the required configuration we call Property(o => o.Status).HasColumnName("Status") to map DetailedOrder.Status to the same column as Order.Status.
and then you can see in the sample fluent configuration that Property(o => o.Status).HasColumnName("Status") is called for both Order and DetailedOrder.
Shortly, you must explicitly configure column names for shared columns for both (all if more then one) entities.
In your case, the minimal configuration needed (in addition of what you have currently) is like this (using modelBuilder fluent API directly, but you can put them in separate entity configuration classes if you wish):
modelBuilder.Entity<ContractingCompany>(builder =>
{
builder.Property(c => c.Name).HasColumnName("Name");
});
modelBuilder.Entity<Contractor>(builder =>
{
builder.Property(c => c.ContractorId).HasColumnName("ContractorId");
builder.Property("CompanyId").HasColumnName("CompanyId");
});
I am working with .NET MVC that have a model implement recursion as below:
public class Comment
{
[Key]
[Required]
public int CommentId { get; set; }
[Required]
public string Content { get; set; }
public bool Anonymous { get; set; }
[Required]
public DateTime Created_date { get; set; }
[Required]
public DateTime Last_modified { get; set; }
public virtual Comment Reply { get; set; }
public virtual ICollection<Comment> Replys { get; set;}
public virtual Idea Idea { get; set; }
}
In explanation, each Idea contains different Comments inside it, and each comment also has several smaller Comments that reply to the previous one. However, I have no idea how to do recursion to get replies for a comment and smaller reply for each reply until the last one in controller and also how to display them in view. Feel free to ask me any question since my explanation is not clear sometimes.
public ActionResult Index(int? i)
{
List<Idea> Ideas = db.Ideas.ToList();
foreach(Idea idea in Ideas)
{
idea.Comments = db.Comments.Include(x => x.Reply).ToList();
idea.Comments=idea.Comments.Where(x => x.Idea == idea).ToList();
foreach (Comment comment in idea.Comments)
{
comment.Replys = GetComments(comment.CommentId); //This function use to get list of reply for comment
foreach (Comment reply in comment.Replys)
{
reply.Replys=GetComments(reply.CommentId);
foreach (Comment reply2 in reply.Replys)
{
reply2.Replys=GetComments(reply2.CommentId);
foreach(Comment reply3 in reply2.Replys)
{
reply3.Replys = GetComments(reply3.CommentId);
//When would this stop ?
}
}
}
}
}
return View(Ideas.ToPagedList(i ?? 1, 5));
}
Use this loop;
foreach(Idea idea in Ideas)
{
idea.Comments = db.Comments.Include(x => x.Reply).ToList();
idea.Comments= idea.Comments.Where(x => x.Idea == idea).ToList();
foreach (Comment comment in idea.Comments)
{
RecursionGetComments(comment);
}
}
Add this function;
public void RecursionGetComments(Comment comment){
comment.Replys = GetComments(comment.CommentId);
foreach(var reply in comment.Replys){
RecursionGetComments(reply);
}
}
If the RecursionGetComments above didn't fill properly, you might need to use the ref keyword which passes the current instance of the class (pass by reference);
public void RecursionGetComments(ref Comment comment){
comment.Replys = GetComments(comment.CommentId);
foreach(var reply in comment.Replys){
RecursionGetComments(reply);
}
}
I also noticed that in your model, you are using the keyword virtual.
Virtual signals entity framework that the navigation property will be loaded automatically / lazy loading, since you are loading your comments manually, you could remove that.
I am having trouble saving children entities via Entity Framework / ASP Identity. It seems to be adding duplicates of everything that is added.
I have tried using a detached graph of the DrivingLicenceModel by TeamMember.DrivingLicence = null in the TeamMemberModel and then working with a detached graph by looking if there is new or old DrivingLicenceCategories but because DrivingLicence links back to TeamMember it causes TeamMember.DrivingLicenceId to be null as it cannot link back to TeamMember.
I have tried Manually adding the EntityState to the DrivingLicence and DrivingLicenceCategories but when I do that it complains that it cannot save two entities with the same primary key.
I assume this is because they way I am copying the entities but I after a lot of looking I am drawing a blank.
If there anyway to copy from TeamMemberRequestModel to TeamMemberModel and then save without the children trying to create clone copies of themselves?
Models
public class TeamMemberModel : IdentityUser
{
public virtual DrivingLicenceModel DrivingLicence { get; set; }
public void ShallowCopy(TeamMemberRequestModel src)
{
this.DateOfBirth = src.DateOfBirth;
if (src.DrivingLicence != null)
{
if (this.DrivingLicence == null)
{
this.DrivingLicence = new DrivingLicenceModel(src.DrivingLicence);
}
else
{
this.DrivingLicence.ShallowCopy(src.DrivingLicence);
}
}
}
public TeamMemberModel() { }
}
public class DrivingLicenceModel
{
[Key]
public int Id { get; set; }
[ForeignKey("TeamMember")]
public string TeamMemberId { get; set; }
[JsonIgnore]
public TeamMemberModel TeamMember { get; set; }
public virtual List<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
public DrivingLicenceModel() { }
public DrivingLicenceModel(DrivingLicenceModel src)
{
this.ShallowCopy(src);
}
public void ShallowCopy(DrivingLicenceModel src)
{
this.Id = src.Id;
this.IsFullLicence = src.IsFullLicence;
this.IssueDate = src.IssueDate;
this.ExpiryDate = src.ExpiryDate;
this.IssuingAuthority = src.IssuingAuthority;
this.LicenceNumber = src.LicenceNumber;
this.DrivingLicenceCategories = src.DrivingLicenceCategories;
this.DrivingLicencePoints = src.DrivingLicencePoints;
}
}
public class DrivingLicenceCategoryModel
{
[Key]
public int Id { get; set; }
[ForeignKey("DrivingLicence")]
public int DrivingLicenceId { get; set; }
[JsonIgnore]
public DrivingLicenceModel DrivingLicence { get; set; }
}
public class TeamMemberRequestModel
{
public string Id { get; set; }
public virtual DrivingLicenceModel DrivingLicence { get; set; }
}
Context
public class TIERDBContext : IdentityDbContext<TeamMemberModel, RoleModel, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
public TIERDBContext() : base("SARDBConnection") { }
public DbSet<DrivingLicenceModel> DrivingLicences { get; set; }
public DbSet<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
}
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TeamMemberModel CurrentTeamMember = await this.TIERUserManager.FindByIdAsync(id);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
you have to create clone property into context class
.
In the context clases you could to use clone method that retiran the entity you send by parameters this duplicarse any entity you pass. Sorry for my english
hope you help
After far to many hours working over this. I have come to an answer. The best way to deal with this is to simply deal with it is to add or attach all entities down the tree.
The controller now attaches all children unless they have an ID of 0, therefore new and uses add instead. Then I use this very useful extension I found here http://yassershaikh.com/c-exceptby-extension-method/ to compare lists to see added and deleted entities in the list. While I don't need the added part as the entity will already be marked to an add state as I use add() it does not harm and I want to use it later with add and delete state changing.
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TIERDBContext IdentityContext = (TIERDBContext)this.TIERUserManager.UserStore().Context;
foreach (DrivingLicenceCategoryModel DrivingLicenceCategory in teamMember.DrivingLicence.DrivingLicenceCategories)
{
if (DrivingLicenceCategory.Id == 0)
{
IdentityContext.DrivingLicenceCategories.Add(DrivingLicenceCategory);
}
else
{
IdentityContext.DrivingLicenceCategories.Attach(DrivingLicenceCategory);
}
}
foreach (DrivingLicencePointModel DrivingLicencePoint in teamMember.DrivingLicence.DrivingLicencePoints)
{
if (DrivingLicencePoint.Id == 0)
{
IdentityContext.DrivingLicencePoints.Add(DrivingLicencePoint);
}
else
{
IdentityContext.DrivingLicencePoints.Attach(DrivingLicencePoint);
}
}
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicenceCategories.AsQueryable(),teamMember.DrivingLicence.DrivingLicenceCategories, IdentityContext);
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicencePoints.AsQueryable(),teamMember.DrivingLicence.DrivingLicencePoints, IdentityContext);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
I then use a generic that uses ExceptBy to work out what is added and delete from the old team member model to the new team member model.
protected void DetectAddedOrRemoveAndSetEntityState<T>(IQueryable<T> old, List<T> current, TIERDBContext context) where T : class, IHasIntID
{
List<T> OldList = old.ToList();
List<T> Added = current.ExceptBy(OldList, x => x.Id).ToList();
List<T> Deleted = OldList.ExceptBy(current, x => x.Id).ToList();
Added.ForEach(x => context.Entry(x).State = EntityState.Added);
Deleted.ForEach(x => context.Entry(x).State = EntityState.Deleted);
}
It works but it is far from great. It takes two DB queries, getting the original and updating. I just cannot think of any better way to do this.
I'm trying to add a new sub-entity, product component ProductRevComp to an existing entity ProductRev. However when I retrieve an instance of the ProductRev class, the Comps collection is never populated (even when explicitly Including() it). I BELIEVE I have mapped everything correctly, but it has taken more fiddling than I want and this is the most likely place for a mistake to be hiding. However profiling the SQL statements show the relevent columns are being populated with the correct data.
Checking db.ProductRevComps (i.e. the DbSet of all my comps) shows the records can be loaded, and that mapping is working as expected.
Mappings:
public class ProductRevConfiguration : EntityTypeConfiguration<ProductRev>
{
public ProductRevConfiguration()
{
HasKey(p => p.ProductRevId);
HasMany(p => p.Comps).WithRequired().HasForeignKey(p => p.ParentProductRevId);
Ignore(p => p.ProgrammedParts);
}
}
public class ProductRevCompConfiguration : EntityTypeConfiguration<ProductRevComp>
{
public ProductRevCompConfiguration()
{
HasKey(p => new { p.ParentProductRevId, p.CompProductRevId });
HasRequired(p => p.ParentProductRev).WithMany().HasForeignKey(p => p.ParentProductRevId);
HasRequired(p => p.CompProductRev).WithMany().HasForeignKey(p => p.CompProductRevId);
}
}
Product entity (amazingly simplified):
public class ProductRev
{
public string ProductRevId { get; set; }
public virtual List<ProductRevComp> Comps { get; set; }
public virtual List<ProductRevComp> ProgrammedParts { get { return Comps; } }//Will be filtered once I get this working
public ProductRev() { }
}
Comp entity:
public class ProductRevComp
{
public string ParentProductRevId { get; set; }
public virtual ProductRev ParentProductRev { get; set; }
public string CompProductRevId { get; set; }
public virtual ProductRev CompProductRev { get; set; }
public int CompTypeValue { get; set; }
public ProductRevCompType CompType
{
get { return (ProductRevCompType)CompTypeValue; }
set { CompTypeValue = (int)value; }
}
public enum ProductRevCompType { ProgrammedPart = 1 };
public ProductRevComp() { }
public override string ToString()
{
return base.ToString();
}
}
Removing the extra prog parts collection doesn't change anything.
How can I get the ProductRev entity to populate the Comps property without resorting to a manual DB hit?
(Must run as the office is closing and I don't have a key - I hope I have included all details, please comment if anything is missing.)
I am trying to figure out the best practices when updating an entity and all it's children. For example; I have an "Employer" update service that would update the employer entity and the "Address" entities of the employer and the "Phone" entities of each "Address". User may add new addresses to the existing employer or they may update the current addresses or they can delete some, same applies for the phones of each addresses. Could you help me to write the ideal code that would handle this scenario.
I am using EF7 rc1 and I use Automapper to map the Dto to Entity in my service.
public partial class Employer
{
public int EmployerId { get; set; }
public int Name { get; set; }
[InverseProperty("Employer")]
public virtual ICollection<Address> Address { get; set; }
}
public partial class Address
{
public int AddressId { get; set; }
public int Address1{ get; set; }
public int City { get; set; }
[ForeignKey("EmployerId")]
[InverseProperty("Address")]
public virtual Employer Employer { get; set; }
[InverseProperty("Address")]
public virtual ICollection<Phone> Phone { get; set; }
}
public partial class Phone
{
public int PhoneId { get; set; }
public string Number { get; set; }
[ForeignKey("AddressId")]
[InverseProperty("Phone")]
public virtual Address Address { get; set; }
}
My service method;
public async Task<IServiceResult> Update(EmployerDto employer)
{
var employerDbEntity = await _db.Employer
.Include(a=>a.Address).ThenInclude(p=>p.Phone)
.SingleOrDefaultAsync (a=>a.EmployerId == employer.EmployerId);
//How to handle the update operation for children?
var entity = Mapper.Map<Employer>(employer);
HandleChildren(employerDbEntity,entity);
await _db.SaveChangesAsync();
...
...
}
private void HandleChildren(Employer employerDbEntity,Employer entity)
{
//Delete
foreach (var existing in employerDbEntity.Address.ToList())
{
if (!entity.Address.Any(a => a.AddressId == existing.AddressId))
employerDbEntity.Address.Remove(existing);
}
//Update or Insert
foreach (var address in entity.Address)
{
var existing = employerDbEntity.Address.SingleOrDefault(a =>a.AddressId == address.AddressId);
//Insert
if (existing == null)
{
employerDbEntity.Address.Add(address);
}
//Update
else
{
Mapper.Map(address, existing);
}
}
}
This example looks like a decent way to handle child collections. Each collection has to be checked manually for the action performed. (Using generics sounds good, but always bites back in some way. Typically, performance.)
With that in mind, here's a few recommendations:
Move the child collection handling into separate methods/services.
If querying for existing entities, retrieve the whole collection in one query then iterate the results in memory.
Since you're writing async code, you can take advantage of processing the child collections in parallel! To do this, each operation should create it's own context. This explains why it's faster.
Here's an example using the recommendations:
private async Task UpdateAddresses(List<Address> addressesToUpdate)
{
using(var context = new Context())
{
var existingAddressIds = await context.Addresses
.Where(a => addressesToUpdate.Contains(a.AddressId))
.ToListAsync()
.ConfigureAwait(false);
existingAddressIds.ForEach(a => context.Addresses.Remove(a));
await context.SaveChangesAsync().ConfigureAwait(false);
}
}