For some reason, the lazy loading of a related entity always turns out NULL.
I think I followed all the needed rules to apply for the proxy generation, however I don't seem to be able to resolve this issue.
I have an object tblOrder_Procedure that has a tblRoom and a tblProcedure. For some reason tblProcedure always loads automatically, but tblRoom doesn't. The FK however is there, but the related entity is just not loaded.
All classes have public parameterless constructors.
These are the field definitions:
public int? Procedure_UID { get; set; }
public int? Room_UID { get; set; }
public virtual tblRoom tblRoom { get; set; }
public virtual tblProcedure tblProcedure { get; set; }
I don't use annotations, but use the OnModelCreating method of the context with fluent API.
The FK's are done like this:
modelBuilder.Entity<tblRoom>()
.HasMany(e => e.tblOrder_Procedure)
.WithOptional(e => e.tblRoom)
.HasForeignKey(e => e.Room_UID);
modelBuilder.Entity<tblProcedure>()
.HasMany(e => e.tblOrder_Procedure)
.WithOptional(e => e.tblProcedure)
.HasForeignKey(e => e.Procedure_UID);
ProxyCreationEnabled and LazyLoadingEnabled are both true by default for the context.
What am I missing?
I'll write down the two most likely possibilities.
-Maybe you are using the .AsNoTracking() extension somewhere explicitly or hidden by another helper that you've written.
-You can try to add optionsBuilder.UseLazyLoadingProxies();
to Context.OnConfiguring(DbContextOptionsBuilder optionsBuilder).
I figured out what the problem was.
It had 2 origins:
I was using an object that was newly created and inserted in the database to try to use lazy loading on. It turns out that you then need to use .Create() instead of 'new' for creating that object.
Turns out I was using a too-long-lived databasecontext.
Somewhere before having the problem, the tblProcedure object was already loaded into the context cache and thus the foreign key relation could be resolved. This made it look like it was lazy loaded, but it actually wasn't.
afaik, if you are using virtual keyword, you should use "include" method.
modelBuilder.Entity<tblRoom>()
.Include(f=>f.tbl_room)
.HasMany(e => e.tblOrder_Procedure)
.WithOptional(e => e.tblRoom),
.HasForeignKey(e => e.Room_UID);
Related
I have an entry removal problem with the EntityFramework and a many-to-many relationship for the same entity. Consider this simple example:
Entity:
public class UserEntity {
// ...
public virtual Collection<UserEntity> Friends { get; set; }
}
Fluent API Configuration:
modelBuilder.Entity<UserEntity>()
.HasMany(u => u.Friends)
.WithMany()
.Map(m =>
{
m.MapLeftKey("UserId");
m.MapRightKey("FriendId");
m.ToTable("FriendshipRelation");
});
Am I correct, that it is not possible to define the Cascade Delete in Fluent API?
What is the best way to delete a UserEntity, for instance Foo?
It looks for me now, I have to Clear the Foo's Friends Collection, then I have to load all other UserEntities, which contain Foo in Friends, and then remove Foo from each list, before I remove Foo from Users. But it sounds too complicateda.
Is it possible to access the relational table directly, so that I can remove entries like this
// Dummy code
var query = dbCtx.Set("FriendshipRelation").Where(x => x.UserId == Foo.Id || x.FriendId == Foo.Id);
dbCtx.Set("FriendshipRelation").RemoveRange(query);
Thank you!
Update01:
My best solution for this problem for know is just to execute the raw sql statement before I call SaveChanges:
dbCtx.Database.ExecuteSqlCommand(
"delete from dbo.FriendshipRelation where UserId = #id or FriendId = #id",
new SqlParameter("id", Foo.Id));
But the disadvantage of this, is that, if SaveChanges failes for some reason, the FriendshipRelation are already removed and could not be rolled back. Or am I wrong?
Problem 1
The answer is quite simple:
Entity Framework cannot define cascade delete when it doesn't know which properties belong to the relationship.
In addition, in a many:many relationship there is a third table, that is responsible for managing the relationship. This table must have at least 2 FKs. You should configure the cascade delete for each FK, not for the "entire table".
The solution is create the FriendshipRelation entity. Like this:
public class UserFriendship
{
public int UserEntityId { get; set; } // the "maker" of the friendship
public int FriendEntityId { get; set; }´ // the "target" of the friendship
public UserEntity User { get; set; } // the "maker" of the friendship
public UserEntity Friend { get; set; } // the "target" of the friendship
}
Now, you have to change the UserEntity. Instead of a collection of UserEntity, it has a collection of UserFriendship. Like this:
public class UserEntity
{
...
public virtual ICollection<UserFriendship> Friends { get; set; }
}
Let's see the mapping:
modelBuilder.Entity<UserFriendship>()
.HasKey(i => new { i.UserEntityId, i.FriendEntityId });
modelBuilder.Entity<UserFriendship>()
.HasRequired(i => i.User)
.WithMany(i => i.Friends)
.HasForeignKey(i => i.UserEntityId)
.WillCascadeOnDelete(true); //the one
modelBuilder.Entity<UserFriendship>()
.HasRequired(i => i.Friend)
.WithMany()
.HasForeignKey(i => i.FriendEntityId)
.WillCascadeOnDelete(true); //the one
Generated Migration:
CreateTable(
"dbo.UserFriendships",
c => new
{
UserEntityId = c.Int(nullable: false),
FriendEntityId = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.UserEntityId, t.FriendEntityId })
.ForeignKey("dbo.UserEntities", t => t.FriendEntityId, true)
.ForeignKey("dbo.UserEntities", t => t.UserEntityId, true)
.Index(t => t.UserEntityId)
.Index(t => t.FriendEntityId);
To retrieve all user's friends:
var someUser = ctx.UserEntity
.Include(i => i.Friends.Select(x=> x.Friend))
.SingleOrDefault(i => i.UserEntityId == 1);
All of this works fine. However, there is a problem in that mapping (which also happens in your current mapping). Suppose that "I" am a UserEntity:
I made a friend request to John -- John accepted
I made a friend request to Ann -- Ann accepeted
Richard made a friend request to me -- I accepted
When I retrieve my Friends property, it returns "John", "Ann", but not "Richard". Why? because Richard is the "maker" of the relationship not me. The Friends property is bound to only one side of the relationship.
Ok. How can I solve this? Easy! Change your UserEntity class:
public class UserEntity
{
//...
//friend request that I made
public virtual ICollection<UserFriendship> FriendRequestsMade { get; set; }
//friend request that I accepted
public virtual ICollection<UserFriendship> FriendRequestsAccepted { get; set; }
}
Update the Mapping:
modelBuilder.Entity<UserFriendship>()
.HasRequired(i => i.User)
.WithMany(i => i.FriendRequestsMade)
.HasForeignKey(i => i.UserEntityId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UserFriendship>()
.HasRequired(i => i.Friend)
.WithMany(i => i.FriendRequestsAccepted)
.HasForeignKey(i => i.FriendEntityId)
.WillCascadeOnDelete(false);
There are no migrations necessary.
To retrieve all user's friends:
var someUser = ctx.UserEntity
.Include(i => i.FriendRequestsMade.Select(x=> x.Friend))
.Include(i => i.FriendRequestsAccepted.Select(x => x.User))
.SingleOrDefault(i => i.UserEntityId == 1);
Problem 2
Yes, you have to iterate the collection and remove all children objects. See my answer in this thread Cleanly updating a hierarchy in Entity Framework
Following my answer, just create a UserFriendship dbset:
public DbSet<UserFriendship> UserFriendships { get; set; }
Now you can retrieve all friends of a specific user id, just delete all of them in one shot, and then remove the user.
Problem 3
Yes, it is possible. You have a UserFriendship dbset now.
Hope it helps!
1) I don't see any straightforward way to control the cascade on the many-to-many relationships using FluentApi.
2) The only available way I can think of to control that is by using the ManyToManyCascadeDeleteConvention, which I guess is enabled by default, at least it is for me. I just checked one of my migrations including a many-to-many relationship and indeed the cascadeDelete: true is there for both keys.
EDIT: Sorry, I just found that the ManyToManyCascadeDeleteConvention does not cover the self-referencing case. This related question's answer says that
You receive this error message because in SQL Server, a table cannot appear more than one time in a list of all the cascading referential actions that are started by either a DELETE or an UPDATE statement. For example, the tree of cascading referential actions must only have one path to a particular table on the cascading referential actions tree.
So you end up having to have a custom delete code (like the sql command that you already have) and execute it in a transaction scope.
3) You should not be able to access that table from the context. Usually the table created by a many-to-many relationship is a by-product of the implementation in a relational DBMS and is considered a weak table respective to the related tables, which means that its rows should be cascade-deleted if one of the related entities is removed.
My advice is that, first, check if your migration is setting your table foreign keys to cascade delete. Then, if for some reason you need to restrict the deletion of a record which has related records in the many-to-many relationship, then you just check for it in your transactions.
4) In order to do that, if you really want to (FluentApi enables by default ManyToManyCascadeDeleteConvention), is to enclose the sql command and your SaveChanges in a transaction scope.
I want my Donut to optionally relate to another Donut, and if so, the other donut would relate back. From what I've read, I believe I need to set this up as a Parent/Child relationship, even though in the real world, it's just an optional pairing (Donuts can exist by themselves happily, or exist in pairs). Here is my current setup:
public class Donut {
public int Id { get; set; }
public int? ParentDonutId { get; set; }
public int? ChildDonutId { get; set; }
// virtual properties
public virtual Donut ParentDonut { get; set; }
public virtual Donut ChildDonut { get; set; }
}
This statement in my mapping file gets me close, but insists on creating a new key named ParentDonut_Id on the table instead of using my existing ParentDonutId:
this.HasOptional(t => t.ChildDonut)
.WithOptionalPrincipal(t => t.ParentDonut);
But when I try this mapping:
this.HasOptional(t => t.ChildDonut)
.WithOptionalPrincipal(t => t.ParentDonut)
.Map(m => m.MapKey("ParentDonutId")); // or "ChildDonutId"
I get this error message when trying to Add-Migration:
ParentDonutId: Name: Each property name in a type must be unique. Property name 'ParentDonutId' is already defined.
How should I set up this relationship? Is it possible? It seems logical enough to me, but maybe it's one of these things DB's don't let you do?
EDIT: I came across this hack, which will probably allow me to do what I need, but it doesn't allow navigating backwards from a child to a parent Donut, which would be nice to have:
this.HasOptional(t => t.ChildDonut)
.WithMany() // haxx
.HasForeignKey(t => t.ChildDonutId);
What if you map both sides? I think this should work:
modelBuilder.Entity<Donut>()
.HasOptional(e => e.ChildDonut)
.WithMany()
.HasForeignKey(t => t.ChildDonutId);
modelBuilder.Entity<Donut>()
.HasOptional(e => e.ParentDonut)
.WithMany()
.WithMany(t => t.ParentDonutId);
To understand more about self-referencing take a look at this thread Second Self-To-Self relationship in Entity Framework
I have started breaking up my "uber" context into smaller focused ones. In a simple scenario, I have Student and Lectures POCOS and my EntityTypeConfiguration defines a many to many relationship between the two in a new table called StudentsAndLectures.
These tables are part of a relationship network of tables defined in my uber context. However, I'd like to manage students and their lectures in a more targeted fashion with a focused context.
My POCO classes below.
public class Student
{
public Student()
{
Lecture = new List<Lecture>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Lecture> Lectures { get; set; }
}
public class Lecture
{
public Lecture()
{
Students = new List<Student>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
Finally, My entity type mappers.
public class StudentMapper : EntityTypeConfiguration<Student>
{
public StudentMapper()
{
HasKey(x => x.Id);
HasMany(x => x.Lectures)
.WithMany(x => x.Students)
.Map(m =>
{
m.MapLeftKey("LectureId");
m.MapRightKey("StudentId");
m.ToTable("StudentsAndLectures");
});
Property(x => x.Name);
}
}
public class LectureMapper : EntityTypeConfiguration<Lecture>
{
public LectureMapper()
{
HasKey(x => x.Id);
HasMany(x => x.Students)
.WithMany(x => x.Lectures)
.Map(m =>
{
m.MapLeftKey("LectureId");
m.MapRightKey("StudentId");
m.ToTable("StudentsAndLectures");
});
Property(x => x.Name);
}
}
Also, My Focused context contains DbSets for only the Students and Lectures.
My problem, If I query for a specific student like below, using my focused context, my Navigation property for .Lectures returns empty. However if I use the full(uber) context that created the db my navigation property gets lazy loaded or eager loaded as i wish. Anyone know why this could be?
using(FocusedStudentContext db = new FocusedStudentContext())
{
var student = db.Students.Include(s => s.Lectures)
.FirstOrDefault(s => s.StudentID == 1234);
// Inspecting student here for the Lectures navigation property
// collection has 0 elements.
}
After further testing and experimenting I found that if I included One particular (none others) additional DbSet that exists in my model and it's related ModelBuilder configurations then all works fine. The DbSet is for an entity, Registration, and it's one that has a navigation property to Student with a HasRequired (x => x.Student). Another twist is, if i leave the ModelBuilder configurations for the Registration entity, but remove the DbSet<Registration> from my focused context, then my navigation property for Lectures stops getting added again. (The collection has 0 elements).
My confusion, how can adding a DbSet to my Focused context affect the way my navigation properties get resolved for tables/entities described above? And how can I resolve this issue. Any help will be appreciated.
You only need one many-to-many mapping, not two. But even though you could have two mappings, they should be identical. In your case, they aren't. Both mappings have the same columns in MapLeftKey and MapRightKey, but they start at different ends. Only the LectureMapper is correct.
Apparenty, the StudentMapper takes precedence, which I think is determined by the order in which mappings are added to the configuration. The effect is that EF is looking for Lectures by the StudentId value in the junction table: very wrong. I can't really explain the effect of including the other mappings and entities that you describe. I just assume that under different circumstances makes EF takes the other mapping first.
But it's just too easy to get MapLeftKey and MapRightKey wrong. I try to keep them apart by picturing it:
Lecture HasMany Student
Left: LectureId Right: StudentId
The MSDN description isn't too helpful, e.g. MapLeftKey:
Configures the name of the column(s) for the left foreign key. The left foreign key points to the parent entity of the navigation property specified in the HasMany call
The navigation property specified in the HasMany call is Students, the parent (or owner) of the property is Lecture, which is identitfied by LectureId... I go for the visualization.
UPDATE I guess I resolved this but not really. I found that if I remove the explicit mapping on the Student and Lectures many to many table and let EF do it that things work fine now.
HasMany(x => x.Students).WithMany(x => x.Lectures);
I am using Entity Framework with Code First. I have a table in my database that stores relationships between users. The table structure looks a lot like this:
RequestID
UserFromID
UserToID
Both UserFromID and UserToID are foreign keys to my User table.
In my User entity I have a virtual property called Relationships setup. I want this property to be a collection of of all RelationshipRequests from the table listed above where the current users UserID is either the UserFromID OR the UserToID.
These are my bindings in the context:
modelBuilder.Entity<UserProfile>()
.HasMany(e => e.Relationships).WithRequired(e => e.UserFrom)
.HasForeignKey(e => e.UserFromID).WillCascadeOnDelete(false);
modelBuilder.Entity<UserProfile>()
.HasMany(e => e.Relationships).WithRequired(e => e.UserTo)
.HasForeignKey(e => e.UserToID).WillCascadeOnDelete(true);
But once the relationships are retrieved for a user, the only relationships in the collection are the one's where the users ID is the UserToID. I have tried swapping them around in which case the collection only contains the relationships where the users ID is the UserFromID. It seems as though the second binding is overriding the first instead of appending to it like I expected. I'm obviously doing this wrong. My question, is there another way to do this binding so that both keys are bound the way I want, or is this something I will have to implement another way?
Thanks!
the problem is that you use twice the same property, and of course your second binding overrides the first. You have to create 2 navigation properties on your user model, one for each foreign key
modelBuilder.Entity<UserProfile>()
.HasMany(e => e.FromRelationships).WithRequired(e => e.UserFrom)
.HasForeignKey(e => e.UserFromID).WillCascadeOnDelete(false);
modelBuilder.Entity<UserProfile>()
.HasMany(e => e.ToRelationships).WithRequired(e => e.UserTo)
.HasForeignKey(e => e.UserToID).WillCascadeOnDelete(true);
And on the model you will have something like this
public virtual List<Relationships> FromRelationships { get; set; }
public virtual List<Relationships> ToRelationships { get; set; }
I'm using Automapper to map my NHibernate proxy objects (DTO) to my CSLA business objects
I'm using Fluent NHibernate to create the mappings - this is working fine
The problem I have is that the Order has a collection of OrderLines and each of these has a reference to Order.
public class OrderMapping : ClassMap<OrderDTO>
{
public OrderMapping()
{
// Standard properties
Id(x => x.OrderId);
Map(x => x.OrderDate);
Map(x => x.Address);
HasMany<OrderLineDTO>(x => x.OrderLines).KeyColumn("OrderId").Inverse();
Table("`Order`");
}
}
public class OrderDTO
{
// Standard properties
public virtual int OrderId { get; set; }
public virtual DateTime OrderDate { get; set; }
public virtual string Address { get; set; }
// Child collection properties
public virtual IList<OrderLineDTO> OrderLines { get; set; } <-- this refs the lines
}
and:
public class OrderLineMapping : ClassMap<OrderLineDTO>
{
public OrderLineMapping()
{
// Standard properties
Id(x => x.OrderLineId);
References<OrderDTO>(x => x.Order).Column("OrderId");
Map(x => x.Description);
Map(x => x.Amount);
Table("`OrderLine`");
}
}
public class OrderLineDTO
{
// Standard properties
public virtual int OrderLineId { get; set; }
public virtual string Description { get; set; }
public virtual decimal Amount { get; set; }
public virtual OrderDTO Order { get; set; } // <-- this refs the order
}
These DTO objects map to Order and OrderLines CSLA objects respectively
When auto-mapping to OrderLines a list of OrderLinesDTO is mapped. Auto mapper is then mapping the "Order" property on of the lines, which maps back to Order which then circularly maps back to OrderLine, then to Order and so on
Does anyone know if Automapper can avoid this circular reference?
In your Automapper configuration:
Mapper.Map<OrderLine, OrderLineDTO>()
.ForMember(m => m.Order, opt => opt.Ignore());
Mapper.Map<Order, OrderDTO>()
.AfterMap((src, dest) => {
foreach(var i in dest.OrderLines)
i.Order = dest;
});
I was having the same issue using EF 6 and AutoMapper 6. Apparently what Kenny Lucero posted led me to the solution. Here's an extract from AM site:
// Circular references between users and groups
cfg.CreateMap<User, UserDto>().PreserveReferences();
Adding PreserveReferences() to both models made it work.
I was having the same issue and solved it by downgrading to version 4.2.1.
apparently the checks for circular references was expensive so they made it default to not check.
Migrating to AutoMapper 5 - Circular references
Supposedly these are supposed to be the settings methods for v 5+ but it didn't work for my data model because we opt'd for complex dto relationships instead of single use dtos for each action.
// Self-referential mapping
cfg.CreateMap<Category, CategoryDto>().MaxDepth(3);
// Circular references between users and groups
cfg.CreateMap<User, UserDto>().PreserveReferences();
http://docs.automapper.org/en/stable/5.0-Upgrade-Guide.html#circular-references
Automapper is supposed to be able to statically determine if the circular reference settings in v6.1+, So if it doesn't work for you automatically in version v6.1+ contact the automapper team.
Since this is the #1 google search result, I think there might be some people, like me, coming here who don't get a stackoverflow exception, but find trouble when sending the object (via ASP.NET) to the client, and thus it being JSON serialized.
So I had the same structure in place, Invoices has multiple InvoiceLines, when I load an Invoice and use the Linq-to-SQL .Include(x => x.InvoiceLines) I get errors when I try to load the object from the Api because each InvoiceLine contains the same Invoice again.
To solve this, do the following in ASP.NET Core Startup class:
services.AddMvc().AddJsonOptions(o =>
{
o.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
o.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
o.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
// ^^ IMPORTANT PART ^^
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
So include o.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects; in your JsonConfiguration when adding MVC to your application.
JSON.Net is taking the extra step to setup each reference with an additional meta-property called “$id”. When JSON.Net encounters the same instance in another place in the object graph, it simply drops a reference to the original instance, instead of duplicating the data, and thus not causing circular reference issues!
Source: https://johnnycode.com/2012/04/10/serializing-circular-references-with-json-net-and-entity-framework/
So now I don't have to further edit my AutoMapper configuration.
If anyone using Mapster (a mapping library for C# same as AutoMapper)
TypeAdapterConfig<TSource, TDestination>
.NewConfig()
.PreserveReference(true);
need to be used for preventing stack overflow error.
Not sure if I should post it here:
I had the same error after doing an automapper.map in a method.
The answer of CularBytes got me thinking that the issue was not automapper related but json related.
I did:
Return ok(_service.getDataById(id));
instead of
Return ok(await _service.getDataById(id));
(I forgot to await an asyc call... rookie mistake I know)