EF 5, one to many, more than one table - collections

I'm having a little of trouble with the following classes:
public class TwoVariableDetails
{
public TwoVariableDetails()
{
MovementsPerBlocks = new HashSet<MovementsRow>();
MovementsPerShiftTypes = new HashSet<MovementsRow>();
MovementsPerMachines = new HashSet<MovementsRow>();
MovementsPerShifts = new HashSet<MovementsRow>();
}
[Key]
public Guid TwoVariableDetailsId { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
[Required]
[MaxLength(1000)]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
public virtual ICollection<MovementsRow> MovementsPerBlocks { get; set; }
public virtual ICollection<MovementsRow> MovementsPerShiftTypes { get; set; }
public virtual ICollection<MovementsRow> MovementsPerMachines { get; set; }
public virtual ICollection<MovementsRow> MovementsPerShifts { get; set; }
}
[Table("Movement")]
public class MovementsRow
{
public MovementsRow()
{
MovementsCells = new HashSet<MovementsCell>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[CsvField(Ignore = true)]
public Guid MovementId { get; set; }
[Required]
public int RowNo { get; set; }
[Required]
[CsvField(Ignore = true)]
public Guid ModelId { get; set; }
[ForeignKey("ModelId")]
[CsvField(Ignore = true)]
public virtual TwoVariableDetails Model { get; set; }
[TypeConverter(typeof(MovementsCellTypeConverter))]
public virtual ICollection<MovementsCell> MovementsCells { get; set; }
}
[Table("MovementCell")]
public class MovementsCell
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[CsvField(Ignore = true)]
public Guid CellId { get; set; }
[Required]
public int ColumnNo { get; set; }
[Required]
public int Count { get; set; }
[Required]
[CsvField(Ignore = true)]
public Guid MovementId { get; set; }
[ForeignKey("MovementId")]
[CsvField(Ignore = true)]
public virtual MovementsRow Model { get; set; }
}
When I try to save it to the database I get the following error:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Movement_dbo.TwoVariableDetails_ModelId". The conflict occurred in database "aspnet-GreenCranes.UI-20130516", table "dbo.TwoVariableDetails", column 'TwoVariableDetailsId'.
The statement has been terminated.
This is the code I'm using for saving:
twoVariableDetails.TwoVariableDetailsId = Guid.NewGuid();
_context.TwoVariableDetailsModels.Add(twoVariableDetails);
_context.SaveChanges();
My table looks like this:
Movement
- Column
- MovementId
- RowNo
- ModelId(FK, uniqueidentifier, not null)
- TwoVariableDetails_TwoVariableDetailsId(FK, uniqueidentifier, null)
- TwoVariableDetails_TwoVariableDetailsId2(FK, uniqueidentifier, null)
- TwoVariableDetails_TwoVariableDetailsId3(FK, uniqueidentifier, null)
- TwoVariableDetails_TwoVariableDetailsId4(FK, uniqueidentifier, null)
- Keys
- FK_dbo.Movement_dbo.TwoVariableDetails_ModelId
- FK_dbo.Movement_dbo.TwoVariableDetails_TwoVariableDetails_TwoVariableDetailsId
- FK_dbo.Movement_dbo.TwoVariableDetails_TwoVariableDetails_TwoVariableDetailsId1
- FK_dbo.Movement_dbo.TwoVariableDetails_TwoVariableDetails_TwoVariableDetailsId2
- FK_dbo.Movement_dbo.TwoVariableDetails_TwoVariableDetails_TwoVariableDetailsId3
I'm not sure what is the problem with my approach. Should I change the MovementsRow class to have four Model properties and four modelid fk and then use InverseProperty attribute?

MovementsRow.Model belongs to another relationship than the four collections in TwoVariableDetails. That's the reason why you don't have four, but five foreign keys in the database table. When you insert twoVariableDetails into the DB and it contains a MovementRow instance in one of the collections EF expects that its ModelId is set to a Guid that references an existing TwoVariableDetails row - which it doesn't apparently. Hence the exception.
Should I change the MovementsRow class to have four Model properties
and four modelid fk and then use InverseProperty attribute?
I'd say yes. It's probably the best solution. The alternative is to have no Model property at all in MovementRow. It's working but you would not be able to navigate from MovementRow to TwoVariableDetails then.

Your FK_dbo.Movement_dbo.TwoVariableDetails_ModelId is being violated, simply put - the ModelId that the Movement record is using doesn't yet exist in TwoVariableDetails.
If you wanted to keep it simple, and transactional, then you could use TransactionScope along with your database context, save the TwoVariableDetails first in the transaction, and then the records that relate back to it:
using (var context = new MyDbContext())
using (var tranScope = new TransactionScope(TransactionScopeOption.Required) {
// don't save the Movement records yet
twoVariableDetails.TwoVariableDetailsId = Guid.NewGuid();
_context.TwoVariableDetailsModels.Add(twoVariableDetails);
_context.SaveChanges();
// now create the movement records, add them to twoVariableDetails
...
_context.SaveChanges();
// commit the transaction
scope.Complete();
}

Related

SQLiteNetExtensions - InsertWithChildrenAsync not working when Foreign Key constraint exists on child table

Xamarin.Forms project, using SQLiteNetExtensions 2.0.0 and sqlite-net-pcl 1.5.231
I am trying to insert new records into a sqlite database. My scenario works when I drop the NOT NULL and Foreign Key constraint on the child table. When I re-add the constraints I get the following exception:
SQLite.SQLiteException: 'Constraint'
Parent Model
[Table("RetailItem")]
public class RetailItemDTO
{
[PrimaryKey, AutoIncrement]
public int RetailItemId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool Taxable { get; set; }
public int DepartmentId { get; set; }
public int RetailTypeId { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<RetailItemRetailerPriceDTO> Prices { get; set; }
}
Child Model
[Table("RetailItemRetailerPrice")]
public class RetailItemRetailerPriceDTO
{
[PrimaryKey, AutoIncrement]
public int RetailItemRetailerPriceId { get; set; }
[ForeignKey(typeof(RetailItemDTO)), NotNull]
public int RetailItemId { get; set; }
public int RetailerId { get; set; }
public decimal Price { get; set; }
}
Insert/Update logic
public async Task Save(RetailItem entity)
{
var dto = GetDTOFromBusinessModel(entity);
if (dto.RetailItemId == 0)
{
await _sqliteRepository.Database.InsertWithChildrenAsync(dto, true);
entity.Id = dto.RetailItemId;
}
else
await _sqliteRepository.Database.InsertOrReplaceWithChildrenAsync(dto, true);
}
If I have an existing RetailItem, with a newly added price, the InsertOrReplaceWithChildrenAsync call works without issue fails with the following: SQLite.NotNullConstraintViolationException: 'NOT NULL constraint failed: RetailItemRetailerPrice.RetailItemId'
Anytime I add a new RetailItem with a new RetailItemRetailerPrice the Constraint exception occurs.
If I drop the NOT NULL and Foreign Key constraint for the RetailItemId on the RetailItemRetailerPrice table then both calls work.
I want to keep the proper constraints on my table.
Am I missing some attributes that will make my scenario work?
You have set the RetailItemIdnot null ,when you add a new item in RetailItemRetailerPrice table, you must set a RetailItemId for the new price.
[ForeignKey(typeof(RetailItemDTO)), NotNull]
public int RetailItemId { get; set; }
If you do not want to keep the constraints on your table. Before you add a new item in RetailItemRetailerPrice table, you can query the RetailItem table, get the RetailItemId, In the end , you can add this new item in RetailItemRetailerPrice table.

asp.net - LINQ Query with relational Data

I have two tables Category and Document. See relationships in picture
See picture
I wrote the following query to select data from both tables based on relationship
public List<DocumentViewModel> All()
{
var docs = _context.Document.ToList();
List<DocumentViewModel> docList = docs.Select(x => new DocumentViewModel
{ DocumentId = x.DocumentId,
DocumentPath = x.DocumentPath,
CategoryId = x.CategoryId,
CategoryName = x.Category.CategoryName }).ToList();
return docList;
}
when this function is called , I get the following error
System.NullReferenceException: 'Object reference not set to an instance of an object.'
Here are my modals
document
public class Document
{
[Key]
public int DocumentId { get; set; }
[Required]
public string DocumentPath { get; set; }
public Nullable<int> CategoryId { get; set; }
public virtual Category Category { get; set; }
}
Category
public class Category
{
[Key]
public int CategoryId { get; set; }
[Required]
public string CategoryName { get; set; }
public virtual ICollection<Document> Documents { get; set; }
}
DocumentViewModel
public class DocumentViewModel
{
public int DocumentId { get; set; }
public string DocumentPath { get; set; }
public int? CategoryId { get; set; }
public string CategoryName { get; set; }
}
Any Idea where am doing mistake?
In this case there is no reason to get a List in memory and then do the projection, you can do this directly from EF instead. Even if there is no relationship defined EF will return null for CategoryName if you project the the results. If you go to memory first then an NRE is expected if there is no Category relationship.
public List<DocumentViewModel> All()
{
return _context.Document.Select(x => new DocumentViewModel
{ DocumentId = x.DocumentId,
DocumentPath = x.DocumentPath,
CategoryId = x.CategoryId,
CategoryName = x.Category.CategoryName}).ToList();
}
Original reason why it is failing.
There is at least one entity that does not have a corresponding relationship with Category.
You do not have lazy loading enabled (which is a good thing) and if that is the case you should use Include to return the relationship.

ef core one-to-one property is always null

I'm trying to define (using data annotations, that's the project pattern, not using the fluent api) a one-to-one relationship but the navigation property (public virtual HolidayType HolidayType) is always null. The db table is creating the ForeignKey correctly using the annotation [ForeignKey("HolidayTypeId")].
Holiday Model
[Table("Holidays", Schema = "PTO")]
public class Holiday : EntityBase
{
...
public long HolidayTypeId { get; set; }
[ForeignKey("HolidayTypeId")]
public virtual HolidayType HolidayType { get; set; }
...
}
db FK constraint
ALTER TABLE [PTO].[Holidays] WITH CHECK ADD CONSTRAINT [FK_Holidays_HolidayTypes_HolidayTypeId] FOREIGN KEY([HolidayTypeId])
REFERENCES [PTO].[HolidayTypes] ([Id])
ON DELETE CASCADE
GO
Holiday Types Table
[Table("HolidayTypes", Schema = "PTO")]
public class HolidayType : EntityBase
{
[Required]
[MaxLength(200)]
public string Description { get; set; }
public bool IsActive { get; set; }
}
EntityBase
public class EntityBase
{
[Key]
public long Id { get; set; }
[Required]
[MaxLength(200)]
public string Name { get; set; }
public Guid CreatedByGuidId { get; set; }
public DateTime DateCreated { get; set; }
public Guid UpdatedByGuidId { get; set; }
public DateTime? DateUpdated { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
Here's the repository call, the holidays are retrieved but they are missing the HolidayType property - always null. My understanding, using the ForeignKey annotation, the query does not need to have an .include statement. My understanding is obviously flawed so my next step is to try the include statement to verify. I'm hoping that an include statement doesn't require an addition db call if that is the answer to my issue.
public async Task<IEnumerable<T>> ListAsync(Expression<Func<T, bool>> predicate)
{
return await ApplicationDbContext.Set<T>().Where(predicate).ToListAsync();
}
Looks like the .Include statement is needed but it breaks my repository implementation so I need to rethink that.
This now hydrates the HolidayType:
public async Task<List<Holiday>> GetHolidaysByEmployerId(long employerId, int? year = null)
{
Expression<Func<Holiday, bool>> predicate = holiday => holiday.EmployerId == employerId;
var result = await ApplicationDbContext.Set<Holiday>().Where(predicate)
.Include(holiday => holiday.HolidayType)
.ToListAsync();
return result.ToList();
}

How to handle validation for Relationship PK and FK in MVC5

Hi I have 2 table name tblGroup and tblSubGroup and tblGroup has GroupId which is primary and tblSubGroup has Groupid which is foreign key.
Below are the model generated for them
tblGroup Model
public partial class tblGroup
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public tblGroup()
{
this.tblSubGroups = new HashSet<tblSubGroup>();
}
public int GroupID { get; set; }
[Required(ErrorMessage = "Group Name is Required")]
public string Title { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<tblSubGroup> tblSubGroups { get; set; }
}
tblSubGroup Model
public partial class tblSubGroup
{
public int SubGroupID { get; set; }
[Display(Name = "tblGroup")]
public int GroupID { get; set; }
[Required(ErrorMessage = "SubGroup Name is Required")]
public string Title { get; set; }
public virtual tblGroup tblGroup { get; set; }
}
Now on deleting record of From GroupTable it is giving issue. Instead I need to validate a message that "This record is bind with another table or entity. So it cannot be deleted". I need to show this kind of message.
As I am new I don't know this things is possible or not
Since you need to verify with the database you move this type of validation to the server.
[HttpPost]
public ActionResult Delete(Group group)
{
var grp = db.Group.FirstOrDefault(g => g.Id == group.Id);
if (HasSubGroups(grp))
{
ModelState.AddError("DeleteValidation", "Cannot delete while sub-groups exists");
return View(group);
}
// delete normally ...
}
Then you could display the errors on the view in several ways. The simplest is just to show the collection.
#Html.ValidationSummary()

Junction Table in ASP .NET code first approach

I am creating a junction table between Identity User and a Game table. This table is called UserGame and has two foreign keys (UserID, GameID) and one additional field to store the score.
public class UserGame
{
[Key]
public string ApplicationUserID { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
public int GameID { get; set; }
public virtual Game Game { get; set; }
public int Score { get; set; }
}
My confusion lies with creating a new UserGame record. How would I go about doing this and is my approach correct?
Update (This worked):
[HttpPost]
public ActionResult SubmitScore(int gameID, int score)
{
var playerRecord = new UserGame();
playerRecord.ApplicationUserID = User.Identity.GetUserId();
playerRecord.GameID = gameID;
playerRecord.Score = score;
return Json(new { success = true });
}
Both ApplicationUserId and GameId must have a [Key, Column(Order = 0)] attribute. Just set the first to Order 0 and the other to 1.
public class UserGame
{
[Key, Column(Order = 0)]
public string ApplicationUserID { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
[Key, Colum(Order = 1)]
public int GameID { get; set; }
public virtual Game Game { get; set; }
public int Score { get; set; }
}
Then you have the choice to add new record, go through nav property from Game or ApplicationUser or directly with your UserGame class.
Example of configuring many-to-many relationship in entity framework
Examle of inserting related objects
A couple of tips:
I wouldn't declare a primary key in the junction entity as junctions are usually defined by composite keys.
Keep in mind that dbcontext.SaveChanges() will look for child entities and save those as well.

Resources