Updating Graph with One-to-One relation - c#

I have a class Ricevuta which holds a collection of VoceRicevuta:
public partial class Ricevuta : GestPreBaseBusinessObject
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
...
[InverseProperty("Ricevuta")]
public virtual ObservableListSource<VoceRicevuta> Voci { get; set; }
}
A VoceRicevuta holds optional references to different classes, let's consider only the one to Prestazione class:
public partial class VoceRicevuta
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
public long IdRicevuta { get; set; }
....
public virtual Prestazione Prestazione { get; set; }
[ForeignKey("IdRicevuta")]
public virtual Ricevuta Ricevuta { get; set; }
}
Ok, the problem now is the following: I create an Instance of Prestazione (let's call it PreInst) and save it to the database. I detach PreInst from that DbContext instance.
Then, I pass PreInst to another form (another DbContext - I must do that), create a Ricevuta, a VoceRicevuta (added to the just created Ricevuta) and assign PreInst to VoceRicevuta Prestazione property. I also made some changes to PreInst.
Now I want to save to database the new Ricevuta, the new VoceRicevuta, its relation to PreInst and the changes to PreInst.
I run the following:
db.UpdateGraph(Ricevuta, map =>
map.OwnedCollection(ric => ric.Voci,
with => with
.OwnedEntity(voce => voce.Prestazione)
));
But i get the error:
"Violation of PRIMARY KEY constraint 'PK_dbo.Prestazioni'. Cannot insert duplicate key in object 'dbo.Prestazioni'. Vlue of duplicate key: (12115)"
I can't understand why! Isn't Graph diff purpose cheching if any part of the graph is already present in Db and behave consquently?
I tried to attach PreInst to DbContext before running the code above and saving. Now the error thrown is:
Multiplicity constraint violated. The role 'Prestazione_VoceRicevuta_Source' of the relationship 'Gestione_Prestazioni.Prestazione_VoceRicevuta' has multiplicity 1 or 0..1.
Any hint?

Related

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.

EF Core - Reuse the same join table entity for several relationships

I want to reuse the same many-to-many relationship table (FileInEntity) for several other objects (Course, Lecture, Game), since they all can have files. Since we have to manually create the many-to-many relationships by creating a join entity, I want to reuse the join entity for the objects (Course, Lecture, Game).
If we look at the table structure, I would like to have the following:
Course: Id, ...
Lecture: Id, ...
Game: Id, ...
FileInEntity: EntityId (this can be either Course.Id, Lecture.Id or Game.Id), FileId
File: Id, ...
(File is base class type with two derived types: Image and Audio)
When I try this approach in .NET Core, I receive the following error message:
Entity type 'FileInEntities' is in shadow-state. A valid model requires
all entity types to have corresponding CLR type.
Is this even possible?
This is my setup:
ModelBase.cs
public class ModelBase
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
}
Course.cs
[Table("Courses")]
public class Course : ModelBase
{
private ICollection<FileInEntity> IconsInCourse { get; set; } = new List<FileInEntity>();
[NotMapped]
public File Image => IconsInCourse.Select(e => e.File).FirstOrDefault();
}
Lecture.cs
// Same as Course.cs
Game.cs
// Same as Course.cs
FileInEntity.cs
[Table("FilesInEntities")]
public class FileInEntity
{
public Guid FileId { get; set; }
public Guid EntityId { get; set; }
public virtual ModelBase Entity { get; set; }
public virtual File File { get; set; }
}
File.cs
[Table("Files")]
public class File : ModelBase
{
// This is the property for which the error occured
private ICollection<FileInEntity> FileInEntities { get; set; } = new List<FileInEntity>();
public IEnumerable<ModelBase> Entities => FileInEntities.Select(e => e.Entities);
}
FilesInEntitiesMap.cs (Relationship Configuration)
builder.HasOne(p => p.Entity)
.WithMany()
.HasForeignKey(k => k.EntityId);
builder.HasOne(p => p.File)
.WithMany()
.HasForeignKey(k => k.FileId);
FileMap.cs
// This is the key to which the error references to
builder.HasMany("FileInEntities")
.WithOne("Entity")
.HasForeignKey("EntityId");
You won't be able to use the base class ModelBase as the object in the mapping class because c# wont know the actual type coming back from the db. You can look at table per hierarchy inheritance, but I'm still not sure you would be able to use that in a mapping table either. Here is a good article
If your Course.cs, Lecture.cs, and Game.cs are the same and the only difference is type, you could combine them into one class and add an enum property to set the type.
public enum EntityType{
Game = 1,
Lecture = 2,
Course = 3
}
public class MyEntity : ModelBase{
private ICollection<FileInEntity> Icons { get; set; } = new List<FileInEntity>();
[NotMapped]
public File Image => Icons.Select(e => e.File).FirstOrDefault();
public EntityType EntityType {get;set;} //course, lecture, or game
}
When you care about the type just use a where clause.
Be sure to use Fluent Api in DbContext's OnModelCreating to determine One to One relationship for this tables. (and be sure again correct reference properties are selected)
Missing parts of your codes.
public class ModelBase
{
[Key]// add for primary key
//set none always for primary keys (because guid has no auto increment)
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id { get; set; }
}
[Table("Files")]
public class File : ModelBase
{
//make it public
public ICollection<FileInEntity> FileInEntities { get; set; } = new List<FileInEntity>();
[NotMapped] //set not mapped
public IEnumerable<ModelBase> Entities => FileInEntities.Select(e => e.Entities);
//do it same changes for `Lacture.cs`, `Game.cs` and `Course.cs`...
}

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();
}
}

One-To-One Entity Framework Code-First Mapping With Composite Keys

Is it possible to create a one-to-one relationship on already defined entity properties? I've got the following entities:
public class Asset
{
public int AssetId { get; set; }
public int OwningCustomerId { get; set; }
public virtual Customer OwningCustomer { get; set; }
}
public class CustomerAsset
{
public int CustomerId { get; set; } // Primary key 0
public int AssetId { get; set; } // Primary key 1
public int CustomerAssetStatusId { get; set; }
public string Name { get; set; }
public virtual Customer Customer { get; set; }
public virtual Asset Asset { get; set; }
}
CustomerAsset represents various states of ownership of an asset. Although the same AssetId can exist multiple times in this table, there will only ever be one instance where CustomerAssetStatusId = 1. In order to have quick access to the asset's current owner, I also maintain OwningCustomerId in the Asset table.
I would like to have a navigation property from Asset to CustomerAsset called OwningCustomerAsset. This is a one-to-one relationship. Unfortunately, I don't know how to define it since the foreign key fields are already defined in Asset. Typically, I would create a relationship like this on Asset:
HasRequired(e => e.OwningCustomerAsset).WithRequiredDependent().Map(m => m.MapKey("OwningCustomerId", "AssetId"));
Of course this results in the following error: "Each property name in a type must be unique. Property name 'OwningCustomerId' was already defined."
How can I tell EF that OwningCustomerId/AssetId is the foreign key to CustomerAsset?
If you can modify your schema, I suggest making asset ownership a property of the asset itself and not of the customer-asset relationship. Having a non-nullable FK field from Asset to Customer (e.g. Owner_CustomerID) enforces the "one owner only" constraint and the owner can be easily loaded from CustomerAsset.Asset.Owner (or whatever you choose to name it). This schema change will greatly simplify your queries.
Furthermore, this will allow you to add a navigation property to Customer that references all owned assets; Customer.OwnedAssets, for example
Update:
Also add navigation property Asset.OwningCustomerAsset (or whatever you wish) from Asset to CustomerAsset using the new Owner_CustomerID field by way of compound FK ( AssetId, Owner_CustomerId ) and resulting navigation property
Since I came down this road needing a navigation property on the other end (which I don't need anymore), I was stuck with the one-to-one mindset. The following gets me what I want:
HasRequired(e => e.OwningCustomerAsset).WithMany().HasForeignKey(e => new { e.OwningCustomerId, e.AssetId });

Categories