I have a very strange problem when using Entity Framework with Code First approach.
I have disabled lazy loading and are using include statements when I need.
Unfortunately I cannot show you the code, but will try to explain as detailed I can.
I have two tables where one is referring to the other:
TableA (ID, Name)
TableB (ID, ForeignKeyTableA)
The corresponding classes:
public class A
{
public int ID { get; set; }
public string Name { get; set; }
}
public class B
{
public int ID { get; set; }
public A ClassA { get; set; }
}
I can load A and B by themselves without and problem, but when I write for example:
Include(p => p.A) when loading B and finally calling ToList() it will cause an OutOfMemoryException after a while.
The tables aren't that big. Table A have around 600 records and table B 5 records.
The mapping for class A looks like this:
this.ToTable("TableA");
this.HasKey(p => p.ID);
this.Property(p => p.ID).HasColumnName("ID");
this.Property(p => p.ID).HasColumnName("Name");
The mapping for class B looks like this:
this.ToTable("TableB");
this.HasKey(p => p.ID);
this.Property(p => p.ID).HasColumnName("ID");
HasRequired(p => p.A).WithOptional()
.Map(m => m.MapKey("ForeignKeyTableA"));
I need to map them because the column and table names aren't by convention. Include can be used on other objects without any problems.
The weirdest thing is that when I am using Include() the database will never be hit.
Anyone got suggestions? Because I am really running out of ideas now.
Related
We are building an application using .NET 6 and EF Core 6 with an existing SQL Server database. We are using the database first approach and running the Scaffold-DbContext tool we were able to generate the dbcontex class. Everything works fine, a part for a parent child relation between two tables:
The scaffold tool, for the above tables generated the following two classes:
public partial class TreeNode
{
public TreeNode()
{
TreeNodeHierarchyChildren = new HashSet<TreeNodeHierarchy>();
TreeNodeHierarchyParents = new HashSet<TreeNodeHierarchy>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public bool IsLeaf { get; set; }
public int? OrganisationId { get; set; }
public bool IsDeleted { get; set; }
public virtual ICollection<TreeNodeHierarchy> TreeNodeHierarchyChildren { get; set; }
public virtual ICollection<TreeNodeHierarchy> TreeNodeHierarchyParents { get; set; }
}
public partial class TreeNodeHierarchy
{
public int Id { get; set; }
public int ParentId { get; set; }
public int ChildId { get; set; }
public virtual TreeNode Child { get; set; }
public virtual TreeNode Parent { get; set; }
}
And in the dbcontext class the following mapping:
modelBuilder.Entity<TreeNode>(entity =>
{
entity.ToTable("TreeNode");
entity.Property(e => e.Code).HasMaxLength(100);
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(255);
});
modelBuilder.Entity<TreeNodeHierarchy>(entity =>
{
entity.ToTable("TreeNodeHierarchy");
entity.HasOne(d => d.Child)
.WithMany(p => p.TreeNodeHierarchyChildren)
.HasForeignKey(d => d.ChildId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_TreeNodeHierarchy_TreeNode_Child");
entity.HasOne(d => d.Parent)
.WithMany(p => p.TreeNodeHierarchyParents)
.HasForeignKey(d => d.ParentId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_TreeNodeHierarchy_TreeNode_Parent");
});
Here is the issue, when I write the following:
var nodes = _context.TreeNodes.Include(th => th.TreeNodeHierarchyChildren)
.Where(tn => tn.IsLeaf)
.....
it loads the child but not the parent.
This relation works properly in the current application (.net 4.7) using LINQ to SQL.
Am I missing something?
Updated
as suggested from #SpruceMoose, I included also the TreeNodeHierarchyParents property in the query but it didn't fix the issue.
var nodes = _context.TreeNodes
.Include(th => th.TreeNodeHierarchyChildren)
.Include(th => th.TreeNodeHierarchyParents)
.Where(tn => tn.IsLeaf)
Updated #2
I applied the mapping suggested from #Dave which in my opinion it makes sense (at the end the relation is like the Windows folders/files system).
Anyway there is still something that's not working properly. When I debug the following code:
var nodes = _context.TreeNodes
.Include(th => th.TreeNodeHierarchyChildren)
.Include(th => th.TreeNodeHierarchyParents)
.Where(tn => tn.IsLeaf)
.ToList();
I still see that the parent has not been loaded
Updated #3
I applied the change to the query as suggested from #Moho
var nodes = _context.TreeNodes
.Include(th => th.TreeNodeHierarchyChildren)
.ThenInclude(tnhc => tnhc.Child)
.Include(th => th.TreeNodeHierarchyParents)
.ThenInclude(tnhp => tnhp.Parent)
.Where(tn => tn.IsLeaf)
.ToList();
and finally we got the Parent value
Now we are missing the last step, the parents of a parent
You need to explicitly (eagerly) load the Parent elements by using an Include() on the TreeNodeHierarchyParents navigation property (as you are currently for the TreeNodeHierarchyChildren navigation property).
Change your linq query to the following:
var nodes = _context.TreeNodes
.Include(th => th.TreeNodeHierarchyChildren)
.Include(th => th.TreeNodeHierarchyParents)
.Where(tn => tn.IsLeaf)
.....
I think your relationship mapping is wrong. You say one child has many children and one parent has many parents. It should be one child has many parents, and one parent has many children.
I think it's also a good idea to define these kinds of relationships on both sides, so that if you get something wrong it shows up as an error faster. Note also that I think some of these statements would already be the default.
Also, important, note that I think you need to use Nullable Reference Types to indicate nullability. Anything that is supposed to be nullable should have a ? on its type name in the entity types. Though I think it's possible you should cascade delete, not set null. It depends how your model works.
Something like this, though I can't guarantee compilation:
modelBuilder.Entity<TreeNode>(tnb => {
tnb.ToTable("TreeNode");
tnb.Property(tn => tn.Code).HasMaxLength(100);
tnb.Property(tn => tn.Name).IsRequired().HasMaxLength(255);
tnb
.HasMany(tn => tn.TreeNodeHierarchyParents)
.WithOne(tnh => tnh.Child);
tnb
.HasMany(tn => tn.TreeNodeHierarchyChildren)
.WithOne(tnh => tnh.Parent);
});
modelBuilder.Entity<TreeNodeHierarchy>(tnhb => {
tnhb.ToTable("TreeNodeHierarchy");
tnhb
.HasOne(tnh => tnh.Child)
.WithMany(tn => tn.TreeNodeHierarchyParents)
.HasForeignKey(tnh => tnh.ChildId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_TreeNodeHierarchy_TreeNode_Child");
tnhb
.HasOne(tnh => tnh.Parent)
.WithMany(tn => tn.TreeNodeHierarchyChildren)
.HasForeignKey(tnh => tnh.ParentId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_TreeNodeHierarchy_TreeNode_Parent");
});
One thing you can do to try to make sure your model definition is correct is to create an empty second database with it and compare its model against the real one, and then keep fine tuning it until you get it right.
Regarding Update #2:
You’re eager loading the relationship entities of type TreeNodeHierarchy but you are not eager loading the TreeNode entities they reference. You need to add .ThenInclude calls to do so.
var nodes = _context.TreeNodes
.Include(th => th.TreeNodeHierarchyChildren)
.ThenInclude(tnhc => tnhc.Child)
.Include(th => th.TreeNodeHierarchyParents)
.ThenInclude(tnhp => tnhp.Parent)
.Where(tn => tn.IsLeaf)
The Child property in your example is populated in your current query because the TreeNode entity is loaded by your base query and EF Core will automatically hook it up to relevant navigation properties in other tracked entities. Thus, any “parent” that is not IsLeaf (nor any “child” that is not IsLeaf) is not loaded without the additional ThenInclude.
Another (not recommended) alternative is to enable lazy loading.
I have a typical master/detail (User / Settings table) table schema (SQL Server) and setup Entity Framework using Fluent API to work with those tables.
I define this as an independent association, so the UserProfileSetting class doesn't include the UserId property, but I understand is correctly mapped in the configuration.
Well, my problem is that when one item of Settings is updated for a profile, at the database level that settings is updated for all users. Basically USER_ID is not considered.
The SQL query produced is this:
UPDATE [dbo].[T_USERPROFILE_SETTING]
SET [VALUE] = #0
WHERE ([KEY] = #1)
Any idea what could be wrong? I guess that if I finally add the UserId property to UserProfileSettings, that will fix the problem, but I wanted to try to fix this without it.
Current code below...
Code updating the data
var entry = profile.Settings.Where(s => s.Key == key).SingleOrDefault();
if (entry != null)
{
entry.Value = value;
} else {
var setting = /* Here create a new setting */
profile.Settings.Add(setting);
}
DataContext.SaveChanges();
Entities:
public partial class UserProfile
{
[Key]
public string UserId { get; set; }
public DateTimeOffset LastLogin { get; set; }
public ICollection<UserProfileSetting> Settings { get; set; }
}
public class UserProfileSetting
{
public UserProfileSetting() { }
public string Key { get; set; }
public string Value { get; set; }
}
Entity configuration:
public class UserProfileConfiguration : EntityTypeConfiguration<UserProfile>
{
public UserProfileConfiguration()
{
ToTable("T_USERPROFILE");
HasKey<string>(p => p.UserId);
Property(p => p.UserId)
.HasColumnName("USER_ID")
.HasMaxLength(50)
.IsUnicode()
.IsRequired();
Property(p => p.LastLogin)
.HasColumnName("LAST_LOGIN_AT")
.IsRequired();
HasMany<UserProfileSetting>(p => p.Settings)
.WithOptional()
.Map(m => m.MapKey("USER_ID"));
}
}
public class UserProfileSettingConfiguration : EntityTypeConfiguration<UserProfileSetting>
{
public UserProfileSettingConfiguration()
{
ToTable("T_USERPROFILE_SETTING");
HasKey(p => p.Key );
Property(p => p.Key)
.HasColumnName("KEY")
.HasMaxLength(50)
.IsUnicode()
.IsRequired();
Property(p => p.Value)
.HasColumnName("VALUE")
.IsUnicode()
.IsRequired();
}
}
From EF documentation...
When foreign key columns are not included in the model, the association information is managed as an independent object. Relationships are tracked through object references instead of foreign key properties. This type of association is called an independent association. The most common way to modify an independent association is to modify the navigation properties that are generated for each entity that participates in the association.
So, I was wrong. In my code, UserProfile should include UserProfileSetting either as a FK (Just the ID) or as an independent Object.
In the 1st case a UserId should be mapped into UserProfileSetting and the navigation property in UserProfile should be changed to...
HasMany<UserProfileSetting>(p => p.Settings)
.WithOptional()
.HasForeignKey(s => s.UserId);
In the 2nd case, (this is what is called an Independent Association) a new navigation property should be added into UserProfileSetting for UserProfile.
Entity framework maps to relational database and so it must stick with some of it concepts. The main thing here is, that each entity is mapped to a table containing all the records of that entity and it needs some data to distinguish the relation.
Therefore you need to add USER_ID to tell which record is for which user (to define the relation). In other words you need to have it in table and also in C# entity.
I don’t think it is possible in code first to not have the relation property on entity. On the other hand, you can create some extra DTO layer to hide it.
I am writing a small bookmarking tool in ASP.NET MVC, and each bookmark is stored in a folder. I am currently representing this by simply storing a path as a string in the database, but when it comes to beginning to display items by folder and list folders this seems like an efficient way to do it.
Is there any built-in way to represent and then manipulate paths in a database? Or an accepted best practice?
If you are talking paths like in filesystem paths, then you might want to start thinking about about a trees and a hierachical system.
In essence, you will have an additional table in a form named like Category or something, which is self referencing with a ParentId. With this you can iterate along your caegories until, there is no parent or child item, depending on the direction of your query and build a path with the result of your query.
This of course can get ugly, if your bookmarkings have more then 1 category. You will want to get some additional validation into this.
The following code is acutally from an article on the web, which I used myself, but I can't find the article again, to quote it as the source.
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual Category Parent { get; set; }
public virtual ICollection<Category> Children { get; set; }
}
public class CategoryMapping : EntityTypeConfiguration<Category>
{
public CategoryMapping()
{
HasKey(x => x.CategoryId);
Property(x => x.CategoryId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.Name)
.IsRequired()
.HasMaxLength(255);
//Parent is optional, because root node has no parent
HasOptional(x => x.Parent)
.WithMany(x => x.Children)
.HasForeignKey(x => x.ParentId)
.WillCascadeOnDelete(false);
}
}
References:
http://sqllessons.com/categories.html
http://www.sitepoint.com/hierarchical-data-database/
Entity Framework Code First Self Referencing Parent Child with Payload
I am not sure if fluent n hibernate can do this or not, but I cannot figure out how.
I have a table - cases and some properties
ownerId, brokerId, shipperId
I want to map this to my property:
int[] OrgsWithAccess
Is this possible?
This way when I am checking if an org has access to the case, I can check the property OrgsWithAccess rather than OwnerId == myorg.id or brokerId == myorg.id etc.
If I understand your question correctly, I wouldn't recommend trying to map in the way that you have asked.
cases table looks like it is some form of junction table between other tables. I'll assume that these other tables each contain data that are represented as entities in the application, and that there are three tables, Owner, Broker and Shipper.
OrgsWithAccess should be mapped using the references to the entities that is has in the application i.e. assume the class looks something like
public class OrgsWithAccess
{
public virtual Owner { get; set; }
public virtual Broker { get; set; }
public virtual Shipper { get; set; }
}
Then the mapping will look like
public class OrgsWithAccessMap : ClassMap<OrgsWithAccess>
{
public OrgsWithAccessMap()
{
References(x => x.Owner);
References(x => x.Broker);
References(x => x.Shipper);
}
}
Then when querying, you would simply look at the properties on OrgsWithAccess
session.QueryOver<OrgsWithAccess>().Where(x => x.Owner.Id == id);
i have 3 tables in my database:
Projects (id, name)
Tags (id, name)
ProjectsTagss (id, projectId, tagid)
As you can see the ProjectsTags table is a bridge table
here is my fluent nhibernate mapping
ProjectMap.cs:
Map(x => x.Name).Not.Nullable();
HasMany(x => x.ProjectsTags).AsBag().Inverse()
.Cascade.AllDeleteOrphan().Fetch.Select().BatchSize(80);
ProjectsTagsMap.cs:
References(x => x.Project).Not.Nullable();
References(x => x.Tag).Not.Nullable();
TagMap.cs:
Map(x => x.Name).Not.Nullable();
As you can see, i historically didn't have the Tag table linked to anything else. I now need to generate a report to show Tag and how often that tag is used so i need to join from Tag to ProjectsTag. i tried adding this line into the tagsmap:
HasMany(x => x.ProjectsTags).AsBag().Inverse()
.Cascade.AllDeleteOrphan().Fetch.Select().BatchSize(80);
but when i go to update the name on a tag object and commit, i get this error:
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance
can anyone see anything wrong with what i added that would be causing this nhibernate exception when i simply update the Tag table. Again my goal is to be able to do something like:
Tag.ProjectTags.Count();
Here is some additional code as requested:
my Tag Class:
public class Tag
{
public virtual IList<ProjectTag> ProjectTags { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
}
Somewhere in your code, you should have dereferenced the original collection on your Project domain. I suspect that your code goes like this:
var project = Session.Get<Project>();
project.ProjectsTags = new List<ProjectsTags> { someProjectsTagsInstance };
Session.Save(project);
If this is the case, you should do this instead:
var project = Session.Get<Project>();
project.ProjectsTags.Clear();
project.ProjectsTags.Add(someProjectsTagsInstance);
Session.Save(project);
While a collection is not modified, NH can still think that it is. Something like this could be caused by a ghost update. From NHibernate 3.0 Cookbook, Jason Dentler (page 184): "As part of automatic dirty checking, NHibernate compares the original state of an entity to
its current state. An otherwise unchanged entity may be updated unnecessarily because a
type conversion caused this comparison to fail".
Ghost update of collection can be caused by code that looks like this:
public class Tag
{
private IList<ProjectTag> projectsTags;
public virtual IEnumerable<ProjectTag> ProjectsTags
{
get
{
return new ReadOnlyCollection<ProjectTag>(projectsTags);
}
set
{
projectsTags = (IList<ProjectTag>)value;
}
}
}
ProjectsTags property returns the collection in readonly wrapper, so client code cannot add or remove elements to/from the collection.
The error will appear even when name of a tag is not changed:
private void GhostTagUpdate(int id)
{
using (var session = OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var tag = session.Get<Tag>(id);
transaction.Commit();
}
}
}
ProjectsTags collection should be mapped with CamelCaseField access strategy to avoid ghost updated:
HasMany(x => x.ProjectsTags)
.Access.CamelCaseField()
.AsBag().Inverse().Cascade.AllDeleteOrphan().Fetch.Select().BatchSize(80);
Anyway...
Your association seems to be diabolically complex. If ProjectsTags table should contains only id of tag and id of project, then it would be simpler to use FNH many-to-many bidirectional mapping:
public class Tag2Map : ClassMap<Tag2>
{
public Tag2Map()
{
Id(x => x.Id);
Map(x => x.Name);
HasManyToMany(x => x.Projects)
.AsBag()
.Cascade.None()
.Table("ProjectsTags")
.ParentKeyColumn("TagId")
.ChildKeyColumn("ProjectId");
}
}
public class Project2Map : ClassMap<Project2>
{
public Project2Map()
{
Id(x => x.Id);
Map(x => x.Name);
HasManyToMany(x => x.Tags)
.AsBag()
.Cascade.None()
.Inverse()
.Table("ProjectsTags")
.ParentKeyColumn("ProjectId")
.ChildKeyColumn("TagId");
}
}
Now there is no need for ProjectTag entity in the model. The count of how many times is given tag used can be retrieved in two ways:
Direct way: tag.Projects.Count() - but it retrieves all projects from database.
Query way:
var tag = session.Get<Tag2>(tagId);
var count = session.Query<Project2>().Where(x => x.Tags.Contains(tag)).Count();