Semi-recursive associations with Entity Framework Code First - c#

I'm hoping you can help me with a small issue I'm having.
I have an entity framework Code First setup where I have the following setup:
public class UserDetails{
public int Id {get;set;}
//... some other properties .. //
//This represents the approval group the user is a member of.
public virtual ApprovalGroup {get;set;}
//This represents the approval groups that the user is resposible for approving
public virual ICollection<ApprovalGroup> ApprovalGroups {get;set;}
}
public class ApprovalGroup
{
public int Id {get;set;}
public string Name {get;set;}
public virtual UserDetails Approvee {get;set;}
public virtual ICollection<UserDetails> Members {get;set;}
}
In my db context I have the following:
modelBuilder.Entity<ApprovalGroup>().ToTable("ApprovalGroup")
.HasKey(t=>t.Id)
.HasRequired(t=>t.Approver);
modelBuilder.Entity<ApprovalGroup>().HasMany(t=>t.Members)
.WithOptional().WillCascadeOnDelete(false);
So an approval group MUST have an approvee set-up, however it could potentially have no members (particularly when first configured).
This code is running, however when I examine the database that it creates the approval group table has an extra column in it that is called "UserDetails_Id". It is set up as a foreign key, but it is always null.
The schema of the table it creates has the following columns:
Id, Name, UserDetails_Id, Approver_Id
I have no idea why it is creating the unnecessary table "UserDetails_Id" and I'd like it not to as there is no reason for it. I suspect I have something wrong with my configuration/mapping behaviour but I can't figure out what it is.
As of yet, google has failed to shed light on what I'm doing wrong so if anyone here can help it would be greatly appreciated.
Nik

The issue you are experiencing is because of the improper/incomplete relationships mappings, combined with EF default conventions.
I would suggest you always configuring the relationships separately, only once per relationship and use the overloads that match exactly the presence/absence of the navigation and explicit FK properties.
In this particular case you have two one-to-many bidirectional (i.e. with navigation properties on both ends) relationships with no explicit FK properties. So the correct configuration should be like this:
// Entities
modelBuilder.Entity<ApprovalGroup>()
.ToTable("ApprovalGroup")
.HasKey(t => t.Id);
modelBuilder.Entity<UserDetails>()
.ToTable("UserDetails")
.HasKey(t => t.Id);
// Relationships
modelBuilder.Entity<ApprovalGroup>()
.HasRequired(t => t.Approver)
.WithMany(t => t.ApprovalGroups);
modelBuilder.Entity<ApprovalGroup>()
.HasMany(t => t.Members)
.WithOptional(t => t.ApprovalGroup) // or whatever the name of the navigation property is (it's missing in the posted code)
.WillCascadeOnDelete(false);

Related

How to properly control cascading deletion in EF Core using Fluent API?

I have two entities, Tag and Member. A member can be marked with multiple tags. A tag can be used to mark multiple members. It's a clear case of many-to-many relation and since I'm using EF Core, I have to declare an explicit connector, which I call Tag_Member. I configure it in the following way.
private void OnModelCreating(EntityTypeBuilder<Tag_Member> entity)
{
entity.HasKey(e => new { e.TagId, e.MemberId });
entity.HasOne(e => e.Tag);
entity.HasOne(e => e.Member)
.WithMany(e => e.Tag_Member)
.HasForeignKey(e => e.MemberId);
}
The behavior I wish to enforce when deleting is as follows.
When removing an instance of Tag_Member, nothing is changed.
When removing an instance of Tag, any connected instances of Tag_Member are deleted.
When removing an instance of Member, any connected instances of Tag_Member are deleted.
I'm confused on two points. When I add the condition for deletion as shown below, I have a lot of options to pick from and, despite reading the intellisense, I don't feel certain which to use to enforce the above behavior.
entity.HasOne(e => e.Member)
.WithMany(e => e.Tag_Member)
.HasForeignKey(e => e.MemberId)
.OnDelete(DeleteBehavior.NoAction);
Should I use NoAction, ClientNoAction, Restrict or someting else? I'm not even clear on which of hte entities that the deletetion behavior affects. Which is it?
The second point of confusion is that I don't get OnDelete() to appear for the tag configuration. I haven't used WithMany() because that entity lacks references back to the interlinking entity. Can I still manage its deletion behavior? Do I need to explicitly declare it to achieve the requested behavior?
entity.HasOne(e => e.Tag)
.OnDelete(DeleteBehavior.NoAction);
The classes look roughly like this.
public class Tag { public Guid Id { get; set; } }
public class Member { public Guid Id { get; set; } }
public class Tag_Member
{
public Guid TagId { get; set; }
public Guid MemberId { get; set; }
public Tag Tag { get; set; }
public Member Member { get; set; }
}
My references are mainly this and this.
edit: Based on the suggestions in the answer, this is the final version of the relation between members and tag.
private static void OnModelCreating(EntityTypeBuilder<Member> entity)
{
entity.HasKey(e => e.Id); ...
}
private static void OnModelCreating(EntityTypeBuilder<Tag> entity)
{
entity.HasKey(e => e.Id); ...
}
private static void OnModelCreating(EntityTypeBuilder<Member_Tag> entity)
{
entity.HasKey(e => new { e.MemberId, e.TagId });
entity.HasOne(e => e.Member).WithMany().OnDelete(DeleteBehavior.NoAction);
entity.HasOne(e => e.Tag).WithMany().OnDelete(DeleteBehavior.NoAction);
}
I'm not even clear on which of hte entities that the deletetion behavior affects. Which is it?
That's easy. The cascade delete always affects the dependent entity (i.e. the entity containing the FK).
I don't feel certain which to use to enforce the above behavior. Should I use NoAction, ClientNoAction, Restrict or someting else?
You seem to be using EF Core 3.0 preview which adds more options not documented yet. But the option for classic cascade delete implemented at the database level has always been Cascade.
I haven't used WithMany() because that entity lacks references back to the interlinking entity.
In order to be able to fluently configure the relationship aspects, you have to fully specify the relationship parties by using the Has + With pair. Since navigation properties are not mandatory for either side of the relationship, all you need it to pass the correct argument to Has / With method - if you do have navigation property, pass the name or lambda expression accessor, otherwise don't pass anything (but still include the call). e.g.
entity.HasOne(e => e.Tag)
.WithMany() // <--
.OnDelete(DeleteBehavior.Cascade); // now you can do this
But note that DeleteBehavior.Cascade is the default for required relationships (in other words, when the FK is non nullable type), so you normally don't need fluent configuration for that. And if the property names follow the EF Core naming conventions, you don't need fluent configuration at all.
Simple example can be seen here.

Intermittent Issue With Entity Framework Creating Unwanted Rows

I have an MVC application that uses Entity Framework v6. We have a class
public class ChildObject
{
public string Name { get; set; }
....
}
that maps to a table in the database. This table has 6 rows that are never changed. Neither will there ever be any additions. We have a second class defined along the lines of the following:
public class ParentClass
{
public int ChildObjectId { get; set; }
public ChildObject ChildObject { get; set; }
....
}
Whenever a ParentClass object is created or updated the logic only references the ChildObjectId property. The ChildObject property is only referenced when data is pulled back for viewing. However about once per month an extra row appears in the ChildObject table that is a duplicate of an existing row. This obviously causes issues. However I can't see how this could happen seeing as we only ever save using the Id value. Any thoughts on how this could be occurring would be very much appreciated.
The typical culprit for behavior like you describe is when a new child entity is composed based on existing data and attached to the parent rather than the reference associated to the context. An example might be that you load child objects as a set to select from, and send the data to your view. The user wants to change an existing child reference to one of the 6 selections. The call back to the server passes a child object model where there is code something like:
parent.ChildObject = new ChildObject{ Name = model.Name, ... }
rather than:
var child = context.Children.Single(x => x.Id = model.ChildObjectId);
parent.ChildObject = child;
Depending on how your domain is set up you may run into scenarios where the EF context creates a new child entity when a navigation property is set. Check with a FindUsages on the ChildObject property and look for any use of the setter.
In general you should avoid combining the use of FK properties (ChildObjectId) with navigation properties (ChildObject) because you can get confusing behavior between what is set in the navigation reference vs. the FK. Entities should be defined with one or the other. (Though at this time EF Core requires both if Navigation properties are used.)
A couple notables from your example:
Mark the navigation property as virtual - This ensures that EF assigns a proxy and recognizes it.
Option A - Remove the FK child ID property. For the parent either use an EntityTypeConfiguration or initialize the DbContext to map the FK column:
EntityTypeConfiguration:
public class ParentClassConfiguration : EntityTypeConfiguration<ParentClass>
{
public ParentClassConfiguration()
{
ToTable("ParentTable");
HasKey(x => x.ParentObjectId)
.Property(x => x.ParentObjectId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.ChildObject)
.WithMany()
.Map(x => x.MapKey("ChildObjectId"));
.WillCascadeOnDelete(false);
}
}
or on context model generation: (Inside your DbContext)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ParentObject>().HasRequired(x => x.ChildObject).WithMany().Map(x => x.MapKey("ChildObjectId")).WillCascadeOnDelete(false);
}
or Option B - Ensure the FK is linked to the reference, and take measures to ensure that the two are always kept in sync:
public class ParentClassConfiguration : EntityTypeConfiguration<ParentClass>
{
public ParentClassConfiguration()
{
ToTable("ParentTable");
HasKey(x => x.ParentObjectId)
.Property(x => x.ParentObjectId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.ChildObject)
.WithMany()
.HasForeignKey(x => x.ChildObjectId));
.WillCascadeOnDelete(false);
}
}
or on context model generation: (Inside your DbContext)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ParentObject>().HasRequired(x => x.ChildObject).WithMany().HasForeignKey(x => x.ChildObjectId)).WillCascadeOnDelete(false);
}
Option B is the only one currently available with EF Core to my knowledge, and it may help mitigate your issue but you still have to take care to avoid discrepancies between the navigation property and the FK. I definitely recommend option A, though it will likely require a bit of change if your code is commonly accessing the FK column.

Entity Framework 6 Lazy Loading returns null

I have a class with the following properties
public class Booking
{
public long BookingId {get;set;}
public string RoomNumber {get;set;}
[ForeignKey("BookingCustomer")]
public long? BookingCustomerId {get;set;}
public virtual Customer BookingCustomer {get;set;}
}
public class Customer
{
public long CustomerId {get;set;}
public string FirstName {get;set;}
}
if in a method I reference properties of the customer class am getting object null reference exception while BookingCustomerId is populated.i.e.,
hotel.BookingCustomerId=2
For instance,
string customerFirstName = hotel.BookingCustomer.FirstName;
if I peek at the hotel.BookingCustomer i get null
How do I go about this Lazy Loading?
If the related entity is coming back as null this means the relationship as understood by entity framework can't find any related entities.
It appears you are using data annotations to flag properties with properties such as foreign keys. You may also need to flag primary keys with the [key] attribute.
You will also need to add a related booking entity to your customer data.
Alternatively you can you the fluent api to do the following in your context.
// Configure the primary key for the Booking
modelBuilder.Entity<Booking>()
.HasKey(t => t.BookingID);
modelBuilder.Entity<Booking>()
.HasRequired(t => t.customer)
.WithRequiredPrincipal(t => t.booking);
More on the fluent a picture here:
https://msdn.microsoft.com/en-us/data/jj591620.aspx#RequiredToRequired
Lazy loading implies that the related objects are retreived when the getter of that object is used for the first time.
At just that time a query to the database is executed to retreive for that object ,for example Hotel.BookingCustomer.
Try to see if the query is indeed executed e.g. with Sql Server profiler.
Examine the query to makes sure everything is correct
If you can't see the query triggered, try to it without the virtual keyword (eager loading) and see if it's working then.

Entity framework map entity to multiple tables in cascade

I'm facing a problem using EF.
I have the following situation:
From this database schema i'd like to generate the following entity by merge tables data:
// Purchases
public class Purchase
{
//Fields related to Purchases
public int IdPurchase { get; set; }
public string CodPurchase { get; set; }
public int IdCustomer { get; set; }
public decimal Total { get; set; }
//Fields related to Customers table
public string CodCustomer { get; protected set; }
public string CompanyTitle { get; protected set; }
public string CodType { get; protected set; }
//Fields related to CustomersType table
public string DescrType { get; protected set; }
}
As you can see, in my context i don't want 3 separated entities for each table. I want a single one with the fields related to all tables. All fields of Customers and CustomersType tables must be readonly (so i've set the relative setters protected) and the others must be editables so that EF can track changes. In particular, i'd like to have the ability to change the "IdCustomer" field and let EF to automatically update "CodCustomer", "CompanyTitle", "DescrType"....and so on by doing cross table select.
To do that, i wrote this configuration class:
internal class PurchaseConfiguration : EntityTypeConfiguration<Purchase>
{
public PurchaseConfiguration(string schema = "dbo")
{
ToTable(schema + ".Purchases");
HasKey(x => x.IdPurchase);
Property(x => x.IdPurchase).HasColumnName("IdPurchase").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.IdCustomer).HasColumnName("IdCustomer").IsRequired();
Property(x => x.Total).HasColumnName("Total").IsRequired().HasPrecision(19, 4);
Map(mc =>
{
mc.Properties(n => new
{
n.CodCustomer,
n.CompanyTitle,
n.CodType
});
mc.ToTable("Customers");
});
Map(mc =>
{
mc.Properties(n => new
{
n.DescrType,
});
mc.ToTable("CustomersType");
});
}
}
I've tested it but it doesn't work as expected. I always get this message:
Properties for type 'Purchase' can only be mapped once. The non-key
property 'CodCustomer' is mapped more than once. Ensure the
Properties method specifies each non-key property only once.
Maybe there's something wrong or i forget something (for example the join fields of Map<> that i don't know where to specify them).
How can i accomplish in the correct way this task?
I don't want to have "Customers" and "CustomersType" DBSets in my context.
Is there a way to avoid it?
I even thought to add into the "IdCustomer" setter a custom query to update manually "Customers" and "CustomersType" related fields, but i don't want to do that for 2 reasons:
I don't have any DbConnection avaiable into the "Purchases" class, so i can't create a DbCommand to read data from DB.
I want entity class to be persistent-ignorant
EF seems to be a powerfull tool that can do these sort of things and i don't want to reinvent the wheel by writing custom procedures.
I've uploaded the example C# source and the tables CREATE scripts (MS SQLServer) here.
All entities are autogenerated by the "EF reverse POCO generator" T4 template (the T4 template is disabled, to activate it set CustomTool = TextTemplatingFileGenerator).
Do not forget to update the ConnectionString in the app.config.
Thanks in advance.
Not the right mapping
I'm afraid the bad news is that this mapping is not possible with this table structure. What you're trying to achieve here is known as entity splitting. However, entity splitting requires 1:1 associations, because sets of records in the involved tables represent one entity. With this mapping, you can't have a Customer belonging to more than one Purchase. That would mean that you could modify multiple Purchase entities by modifying a Customer property of only one of them.
Maybe the news isn't that bad, because I think you actually want to have 1-n associations. But then you can't have these "flattened" properties in Purchase.
As an alternative you could create delegated properties like so:
public string CodCustomer
{
get { return this.Customer.CodCustomer; }
set { this.Customer.CodCustomer = value; }
}
You'd have to Include() Customers and CustomersTypes when you fetch Purchases.
Another alternative is to use a tool like AutoMapper to map Purchase to a DTO type having the flattened properties.
But what does the exception tell me?
You map the Purchase entity to the Purchases table. But you don't specify which properties you want to map to this table. So EF assumes that all properties should be mapped to it. So that's the first (implicit) mapping of CodCustomer. The second one is the one in the mc.ToTable statement. (EF only reports the first problem.)
To fix this, you should add a mapping statement for the left-over Purchase properties:
Map(mc =>
{
mc.Properties(n => new
{
n.IdPurchase,
n.CodPurchase,
n.IdCustomer,
n.Total,
});
mc.ToTable("Purchases");
});
By the way, you should also remove the mapping configuration classes of Customer and CustomersType, they're redundant.
But, as said, the database schema doesn't match the required structure. If you try to save a Purchase you will get a foreign key constraint exception. This is because EF expects the following table structure:
Where the columns IdPurchase in Customer and CustomersType are both primary key and foreign key to Purchase. I don't think this is what you had in mind when designing the database.

How to define Many-to-Many relationship through Fluent API Entity Framework?

Below is my model:
public class TMUrl
{
//many other properties
//only property with type Keyword
public List<Keyword> Keywords{get;set;}
}
public class Keyword
{
//many other properties
//only property with type TMUrl
public List<TMUrl> Urls{get;set;}
}
So clearly, both the entities have many-to-many relationship.
I chose fluent api to tell the entity-framework about this relationship i.e.
modelBuilder.Entity<TMUrl>
.HasMany(s => s.Keywords)
.WithMany(s => s.URLs).Map(s =>
{
s.MapLeftKey("KeywordId");
s.MapRightKey("UrlId");
s.ToTable("KeywordUrlMapping");
});
but when I do
url.Keywords.Add(dbKey); //where url is object of TMUrl,
//dbKey is an existing/new object of Keyword
db.SaveChanges();
I get exception
An error occurred while saving entities that do not expose foreign key
properties for their relationships....
InnerException:
The INSERT statement conflicted with the FOREIGN KEY constraint
"KeywordMaster_Keyword". The conflict occurred in database "DbName",
table "dbo.KeywordMaster", column 'Id'.The statement has been terminated.
but when I add Configuration from the otherside aswell, everything works fine.
i.e.
modelBuilder.Entity<KeyWord>
.HasMany(s => s.URLs)
.WithMany(s => s.Keywords)
.Map(s =>
{
s.MapLeftKey("KeywordId");
s.MapRightKey("UrlId");
s.ToTable("KeywordUrlMapping");
});
Why?. Why I've to add configuration from both the entities, where I've read here and many other places, configuration for one of the entities should do.
What is the case, when I should add configuration for both of the entities involved in the relationship?
I need to understand this. Why. Please help.
The terms Left and Right in MapLeftKey and MapRightKey in the many-to-many mapping with Fluent API can be misunderstood and I guess your problem is caused by this misunderstanding.
One might think that it means they describe the columns that are "left" and "right" in the many-to-many join table. That's actually the case if you let EF Code-First create the database and join table based on your Fluent mapping.
But it's not necessarily the case when you create a mapping to an existing database.
To illustrate this with the prototypic many-to-many example of a User-Role model assume you have an existing database with a Users, Roles and RoleUsers table:
Now, you want to map this table schema to a simple model:
public class User
{
public User()
{
Roles = new List<Role>();
}
public int UserId { get; set; }
public string UserName { get; set; }
public ICollection<Role> Roles { get; set; }
}
public class Role
{
public int RoleId { get; set; }
public string RoleName { get; set; }
}
And you add the Fluent mapping for the Users entity (you must do it this way, because by convention the model above would be one-to-many and you can't start from the Role entity side because it has no Users collection):
modelBuilder.Entity<User>()
.HasMany(u => u.Roles)
.WithMany()
.Map(m =>
{
m.MapLeftKey("RoleId"); // because it is the "left" column, isn't it?
m.MapRightKey("UserId"); // because it is the "right" column, isn't it?
m.ToTable("RoleUsers");
});
This mapping is wrong and if you try to put "Anna" into role "Marketing"...
var anna = ctx.Users.Find(1);
var marketing = ctx.Roles.Find(2);
anna.Roles.Add(marketing);
ctx.SaveChanges();
...SaveChanges will throw exactly the exception you are having. The reason becomes clear when you capture the SQL command that is sent with SaveChanges:
exec sp_executesql N'insert [dbo].[RoleUsers]([RoleId], [UserId])
values (#0, #1)
',N'#0 int,#1 int',#0=1,#1=2
So, EF wants to insert here a row into the join table RoleUsers with a RoleId of 1 and a UserId of 2 which is causing the foreign key constraint violation because there is no user with UserId 2 in the Users table.
In other words, the mapping above has configured the column RoleId as the foreign key to table Users and the column UserId as the foreign key to table Roles. In order to correct the mapping we have to use the "left" column name in the join table in MapRightKey and the "right" column in MapLeftKey:
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
Actually looking at Intellisense the description makes it clearer what "Left" and "Right" really mean:
MapLeftKey
Configures the name of the column(s) for the left foreign key. The
left foreign key represents the navigation property specified in the
HasMany call.
MapRightKey
Configures the name of the column(s) for the right foreign key. The
right foreign key represents the navigation property specified in the
WithMany call.
So, "Left" and "Right" refer to the order in which the entities appear in the Fluent mapping, not to the column order in the join table. The order in the table actually doesn't matter, you can change it without breaking anything because the INSERT sent by EF is an "extended" INSERT that also contains the column names and not only the values.
Perhaps MapFirstEntityKey and MapSecondEntityKey would have been a less misleading choice of those method names - or maybe MapSourceEntityKey and MapTargetEntityKey.
This was a long post about two words.
If my guess is right that it has anything to do with your problem at all then I would say that your first mapping is incorrect and that you only need the second and correct mapping.

Categories