Insert entity with related entity in TPH architecture - c#

I need to insert an entity (Picture) that holds a related entity (Ad) based on TPH architecture:
Picture model:
public class Picture
{
// Primary properties
public int Id { get; set; }
public string Name { get; set; }
}
public class AdPicture : Picture
{
public Ad Ad { get; set; }
}
Ad model:
public class Ad
{
// Primary properties
public int Id { get; set; }
public string Title { get; set; }
}
public class AdCar : Ad
{
public int? CubicCapacity { get; set; }
public int? Power { get; set; }
}
I want to insert a new Picture in the AdCar, and I tried:
AdPicture picture = new AdPicture()
{
Ad = _adRepository.GetById(adId),
Filename = newFileName
};
_pictureService.CreateAdPicture(picture);
CreateAdPicture:
public void CreateAdPicture(AdPicture adPicture)
{
_adPictureRepository.Add(adPicture);
_adPictureRepository.Save();
}
But Entity Framework says
*Invalid column name 'AdCar_Id'.*
When I check the SQL command text, I can see
insert [dbo].[Pictures]([Name], [Filename], [URL], [Rank], [Discriminator], [PictureType_Id], [AdCar_Id], [Ad_Id])
values (null, #0, null, #1, #2, #3, #4, null)
It's putting AdCar_Id and Ad_Id, why? How can I insert the Picture related with the AdCar?

First up: You may need to define the key for the subclass explicitly to be Ad_Id as it is assuming the AdCar_Id (or vice-versa).
Something like this in your DbContext:
modelBuilder.Entity<AdPicture>()
.HasRequired(o => o.Ad)
.WithMany().Map(f => f.MapKey("Ad_Id"));
Second: Also, just to check - all of these classes have completely separate tables (TPH) and not tables with just the additional properties specified (TPH) I.e. an AdPicture does not have data in both a Picture and AdPicture table?
Having not done TPH (only TPT) I am unsure of if Entity Framework handles the subclass-ing differently, but I suspect it must. Someone else can hopefully answer that better if you are missing something there.

I found the problem:
I was defining the Navigation property "public IList Pictures { get; set; }" in the Ad Model that was creating an aditional field in the SQL Command "Ad_Id".
As soon as I removed it, the problem was solved and all the theory I had about TPH came togheter again :)
Thanks for your feedback.

Related

Is it possible in EF Core to make a one-way navigation property required?

I am working on a basic group chat system, for which I created these classes:
public class Role
{
public Guid Id { get; set; };
public string Username { get; set; }
}
public class Message
{
public Guid Id { get; set; };
public Role Author { get; set; }
public Conversation Conversation { get; set; }
public DateTime Date { get; set; }
public string Text { get; set; }
}
public class Conversation
{
public Guid Id { get; set; };
public IList<ConversationParticipant> ConversationParticipants { get; set; };
public IList<Message> Messages { get; set; };
}
public class ConversationParticipant
{
public Conversation Conversation { get; set; }
public Role Role { get; set; }
}
We are using EF Core 3.1 Code-First with migrations.
I am looking for a way to make Message.Author a required property, which should lead to a column in table Message that is created as AuthorId NOT NULL.
I tried:
public static void Map(this EntityTypeBuilder<Message> builder)
{
builder.HasOne(m => m.Author);
}
As this is applied using Add-Migration and Update-Database, the database column AuthorId is created, but with NULLs allowed.
There does not seem to be a method IsRequired() that I can add after HasOne().
I also tried:
public static void Map(this EntityTypeBuilder<Message> builder)
{
builder.Property(m => m.Author).IsRequired();
}
but that fails saying
The property 'Message.Author' is of type 'Role' which is not supported by current database provider. Either change the property CLR type or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Doing .HasOne(...) followed by .Property(...).IsRequired() also does not work:
'Author' cannot be used as a property on entity type 'Message' because it is configured as a navigation.
I managed to make Message.Conversation required through this:
public static void Map(this EntityTypeBuilder<Conversation> builder)
{
builder.HasMany(c => c.Messages) // A conversation can have many messages
.WithOne(e => e.Conversation) // Each message belongs to at most 1 conversation
.IsRequired(); // A message always has a conversation
}
However I'd rather not make Role aware of Messages, as I will never want to retrieve Messages directly from a Role (this will happen through Conversations and Participants).
My ultimate question is: Is there a way to make Message.Author required (NOT NULL), without linking Message and Role together in a full 1-to-many relationship with a Messages property in Role?
What about adding Role's foreign key to Message and then requiring that property to not be null? Something like:
// MessageConfiguration.cs
builder.Property(b => b.RoleId).IsRequired()
While the answer by #Ben Sampica was helpful and got me where I needed to be, the comments by #Ivan Stoev provided details and clarity that made me think that a more comprehensive answer would be useful.
There are multiple ways to make a foreign key column required (NOT NULL) in the generated table.
The simplest is to put [Required] on the navigation property:
public class Message
{
// ...
[Required] public Role Author { get; set; }
// ...
}
This will cause EF to create a shadow property AuthorId of type Guid because Message.Author is a Role and Role.Id is of type Guid. This leads to UNIQUEIDENTIFIER NOT NULL in case of SQL Server.
If you omit [Required] then EF will use Guid?, which leads to UNIQUEIDENTIFIER NULL, unless you apply one of the other options.
You can use an explicit Id property with a type that can't be null:
public class Message
{
// ...
public Guid AuthorId { get; set; }
public Role Author { get; set; }
// ...
}
Note (i) - This only works if you follow EF Core shadow property naming rules, which in this case means you must name the Id property nameof(Author) + nameof(Role.Id) == AuthorId.
Note (ii) - This will break if one day you decide to rename Author or Role.Id but forget to rename AuthorId accordingly.
If you can't or don't want to change the Model class, then you can tell EF Core that it needs to treat the shadow property as required:
builder.Property("AuthorId").IsRequired();
The same Notes apply as listed at 2, with the addition that you could now use nameof() to reduce the effort and the risks.
In the end I decided to use the [Required] approach, because
It is simple and descriptive,
No effort needed to think of which shadow property name to use,
No risk of breaking the shadow property name later on.
This may apply sometimes, not always:
Input forms may use the Model class attribute to check if a property is required. However it may be a better approach to build your forms around DTO classes, and then an attribute on an EF Model class may provide no worth for your forms.

One to One HasRequired configuration not working

I have two sets of objects: Coupon and DiscountScheme.
Each have a connected object of {Type}Action, and identical configurations.
When making a request for Coupon, I do not get anything back,
but the same query for DiscountScheme works as expected
A condensed version of the classes (The full code and sql for the tables can be found here):
public class CouponAction
{
public int Id { get; set; }
public virtual Coupon Coupon { get; set; }
}
public class Coupon
{
public int Id { get; set; }
public virtual CouponAction Action { get; set; }
}
public class DiscountSchemeAction
{
public int Id { get; set; }
public virtual DiscountScheme DiscountScheme { get; set; }
}
public class DiscountScheme
{
public int Id { get; set; }
public virtual DiscountSchemeAction Action { get; set; }
}
The configuration:
public class CouponActionMap : EntityTypeConfiguration<CouponAction>
{
public CouponActionMap()
{
ToTable("CouponAction");
}
}
public class CouponMap : EntityTypeConfiguration<Coupon>
{
public CouponMap()
{
ToTable("Coupon");
HasRequired(c => c.Action);
}
}
public class DiscountSchemeActionMap : EntityTypeConfiguration<DiscountSchemeAction>
{
public DiscountSchemeActionMap()
{
ToTable("DiscountSchemeAction");
}
}
public class DiscountSchemeMap : EntityTypeConfiguration<DiscountScheme>
{
public DiscountSchemeMap()
{
ToTable("DiscountScheme");
HasRequired(ds => ds.Action);
}
}
The query I am trying to make:
using(var context = new Context()/* My database context, using a custom wrapper framework*/)
{
Console.WriteLine(context.Coupons.ToList()); // nothing
Console.WriteLine(context.DiscountSchemes.ToList()); // the contents of the table
}
If I query the actions table, I do get the contents, but again for CouponAction I do not get the connected Coupon, and for DiscountScheme it works as expected.
The issue is with your 1-to-1 relationship. By default EF expects a 1-to-1 to be using the PKs on both tables. By putting a CouponID on your CouponAction you are not setting a 1-to-1 relationship, you are setting a 1-to-many/many-to-1. Nothing stops several CouponAction records from having the same CouponId. You could put a unique constraint on CouponID, but if that were the case then you may as well have the CouponAction's PK to be the CouponID. Hence, this is why I don't advise using "Id" as a PK name, but rather CouponId vs. DiscountId, etc.
If the relationship between coupon and action is truly 1-to-1 then get rid of the CouponId on the Action table, and ensure you're using the same ID value across both tables for the related records. You can test this by changing your mapping to configure EF to use CouponId on the CouponAction as it's PK. Once you do that, you should see your related records coming up.
Alternatively you can establish a many to 1 relationship (HasOne.WithMany()) from Action to Coupon, but no return reference without a CouponActionId on Coupon. Or you can set up a 1-to-many where Coupon contains an ICollection<CouponAction> CouponActions even though you intend to only have one action per coupon. But if it is 1-to-1 then I would highly recommend using the same PK value across both tables.

Entity Framework Core: remove a relationship but not delete the entities

How would you delete a relationship assuming you had the 2 entities, but did not have the 'relationship' entity?
Assuming the following entities...
Model classes:
public class DisplayGroup
{
[Key]
public int GroupId { get; set; }
public string Description { get; set; }
public string Name { get; set; }
public ICollection<LookUpGroupItem> LookUpGroupItems { get; set; }
}
public class DisplayItem
{
[Key]
public int ItemId { get; set; }
public string Description { get; set; }
public string FileType { get; set; }
public string FileName { get; set; }
public ICollection<LookUpGroupItem> LookUpGroupItems { get; set; }
}
public class LookUpGroupItem
{
public int ItemId { get; set; }
public DisplayItem DisplayItem { get; set; }
public int GroupId { get; set; }
public DisplayGroup DisplayGroup { get; set; }
}
Here is the code for deleting a relationship. Note: I do not want to delete the entities, they just no longer share a relationship.
public void RemoveLink(DisplayGroup g, DisplayItem d)
{
_dataContext.Remove(g.LookUpGroupItems.Single(x => x.ItemId == d.ItemId));
}
The method above causes an error:
System.ArgumentNullException occurred
Message=Value cannot be null.
It looks like this is the case because LookUpGroupItems is null, but these were called from the database. I would agree that I do not want to load all entity relationship objects whenever I do a Get from the database, but then, what is the most efficient way to do this?
Additional NOTE: this question is not about an argument null exception. It explicitly states how to delete a relationship in Entity Framework Core.
The following is not the most efficient, but is the most reliable way:
public void RemoveLink(DisplayGroup g, DisplayItem d)
{
var link = _dataContext.Find<LookUpGroupItem>(g.GroupId, d.ItemId); // or (d.ItemId, g.GroupId) depending of how the composite PK is defined
if (link != null)
_dataContext.Remove(link);
}
It's simple and straightforward. Find method is used to locate the entity in the local cache or load it the from the database. If found, the Remove method is used to mark it for deletion (which will be applied when you call SaveChanges).
It's not the most efficient because of the database roundtrip when the entity is not contained in the local cache.
The most efficient is to use "stub" entity (with only FK properties populated):
var link = new LookUpGroupItem { GroupId = g.GroupId, ItemId = d.ItemId };
_dataContext.Remove(link);
This will only issue DELETE SQL command when ApplyChanges is called. However it has the following drawbacks:
(1) If _dataContext already contains (is tracking) a LookUpGroupItem entity with the same PK, the Remove call will throw InvalidOperationException saying something like "The instance of entity type 'LookUpGroupItem' cannot be tracked because another instance with the key value 'GroupId:1, ItemId:1' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached."
(2) If database table does not contain a record with the specified composite PK, the SaveChanges will throw DbUpdateConcurrencyException saying "Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions." (this behavior is actually considered a bug by many people including me, but this is how it is).
Shorty, you can use the optimized method only if you use short lived newly create DbContext just for that operation and you are absolutely sure the record with such PK exists in the database. In all other cases (and in general) you should use the first method.

Self referencing foreign key

I'm using Entity Framework Code-First to rebuild an application that used to run from an Access database. One of the requirements is that the new data schema should be auditable, that is it should show who created a record and who updated it and when etc.
I've created a base Entity class as follows:
public class Entity
{
public int Id { get; set; }
public int CreatedByUserId { get; set; }
public int? UpdatedByUserId { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
}
Then I created a class that inherits from EntityTypeConfiguration as follows
public class BaseEntityTypeConfiguration<T> : EntityTypeConfiguration<T> where T : Entity
{
Property(e => e.Id).HasColumnName(typeof(T).Name + "Id");
HasRequired(e => e.CreatedBy).WithMany().HasForeignKey(e => e.CreatedById);
HasOptional(e => e.UpdatedBy).WithMany().HasForeignKey(e => e.UpdatedById);
}
Now I create configurations that inherit from BaseEntityTypeConfiguration for the rest of my business classes that inherit from my Entity class.
The problem comes when I try to make my User class inherit from entity as follows:
public class User : Entity
{
public string Username { get; set; }
// etc
}
I'll be adding a "ghost" user for records where the evidence isn't there to determine who created the record, but this ghost user will essentially be created by itself.
I'm getting the following error from Entity Framework when I try to add this ghost user:
Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements or store-generated values.
There may be problems in my domain model that could be causing this error, but my theory is that it's down to this user that's trying to create itself in this instance.
Is having a self-referencing foreign key constraint problematic?
Your PK is an identity column and you're setting the ghost user's CreatedByUser property with itself. This causes a chicken/egg scenario - you need the User.Id value as the User.CreatedById value to insert the record into the DB table, but you don't know what User.Id is until after the record is inserted.
If you can be sure of the identity's seed value (EF seems to default to 1), you can set the CreatedByUserId property to that value instead of CreatedByUser.
Otherwise, create your ghost user by executing a SQL statement allowing you to manually set the Id and CreatedByUserId fields to the same value then reseed the identity to Id + 1.
Example of the former:
public class UserWithCreatedBy
{
[Key]
[DatabaseGenerated( DatabaseGeneratedOption.Identity )]
public int Id { get; set; }
public int CreatedById { get; set; }
[ForeignKey( "CreatedById" )]
public UserWithCreatedBy CreatedBy { get; set; }
}
static void Main( string[] args )
{
using( var db = new TestContext() )
{
var u = new UserWithCreatedBy();
// doesn't work with identity
//u.CreatedBy = u;
// this will work as long as you know what the identity seed is
// (whatever the next identity value will be)
u.CreatedById = 1;
db.UsersWithCreatedBy.Add( u );
db.SaveChanges();
}
}

MVC 3, C# - invalid column name generated by code

I am new to asp.net C# and trying to learn by building a simple web app based on MVC 3 Music application. So far i have had a decent run but i am running into this this and i am not able to figure out the root cause. plz help
I am building a simple website where Projects are listed, then clicking on projects you see all the tables and then clicking on table you see all the columns. Projects/Tables/Column are being fetched from SQL db which has valid data and PK/FK keys defined. i am able to navigate from projects to tables and can see all columns under tables but when I click on column link, i get error as described below.
ERROR: "Invalid column name 'Tables_Id'." SQL profiler shows this column in the query but i do not understand where is it coming from as I do not have any such columm.
CONTROLLER CLASS
public class ProjectController : Controller // Inherit from base class Controller
{
DbEntities storeDB = new DbEntities(); //Create Object/instance of class //StorDB is reference to an object
public ActionResult Index()
{
var Name = storeDB.ProjectNM.ToList(); //Use 'var' coz we may have any type returned, 'var' is determined at run time
return View(Name);
}
public ActionResult BrowseTables(string Projects)
{
var ProjectModel = storeDB.ProjectNM.Include ("Tabless")
.Single(g => g.Name == Projects);
return View(ProjectModel);
}
public ActionResult BrowseColumns(string TableIs)
{
var ProjectModel1 = storeDB.TableNM.Include("Columnss")
.Single(g => g.Tbl_Name == TableIs);
return View(ProjectModel1);
//var ColumnModel = storeDB.TableNM.Find(TableIs);
// return View(ColumnModel);
}
}
Other Classes
public partial class Projects //Partial class, see comment below
{
public int Id { get; set; }
public string Name { get; set; }
public List<Tables> Tabless { get; set; } //Navigation Property, required so that we can include tables under projects
}
public class Tables
{
public int Id { get; set; }
public int ProjectId { get; set; }
public string Tbl_Name { get; set; }
public Projects Project { get; set; } //Class table can have (belong) only one project
public List<Columns> Columnss { get; set; } //Table can have more than one column
}
public class Columns
{
public int Id { get; set; }
[ForeignKey("Tables")]
public int TblId { get; set; }
public string Column_Name { get; set; }
public string IncludeFlag { get; set; }
}
View
<ul>
#foreach (var Tables in Model.Tabless)
{
<li>
#Html.ActionLink(Tables.Tbl_Name, "BrowseColumns", new { TableIs = Tables.Tbl_Name })
</li>
}
Query from SQL profiler
[Project2].[Tables_Id] AS [Tables_Id]
As you can seethe query has a column [Tables_Id] and I do nto understand why it is there as i do nto have any such column. Please help!
Basically MVC3 and EF4 do a lot of things on convention.
My suggestion to make things a little clearer for yourself is read up on EF 4.1 a little, and let it pluralize your table names for you, and use the data annotations (or property mapping if you don't like the attributes in your model) to mark your object's Id properties...
This is not necessarily the cause of your problem, but I think you will find it a lot easier to see what is going on in your profiler and models when the names/values make more logical sense.
Start by singularizing your objects: Table, Column, etc. or even using a more descriptive name... again if for no other reason it will be easier for you to read and debug, or even to get better answers here.
The convention for EF4.1 and foreign keys is to name them {TableName}_{ColumnName} so your foreign key to Tables is expecting a column names Table_Id (because Table is the name of the table, and Id is the name of the PK column.
This might help: EF 4.1 messing things up. Has FK naming strategy changed?

Categories