saveChanges problem - c#

I am trying to delete the project from the database but I get the following exception:
"DbUpdateException was unhandled"
------------------------------------------------------------
public class Project
{
public Project()
{
Customers = new List<Customer>();
Materials = new List<Material>();
Workers = new List<Worker>();
}
[Key]
public long ProjectID { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateFinished { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
//Customer TheCustomer = new Customer();
public ICollection<Customer> Customers { get; set; }
public ICollection<Material> Materials { get; set; }
public ICollection<Worker> Workers { get; set; }
}
------------------------------------------------------------------------
if (cb_Projects.SelectedValue != null)
{
using (var db = new ProjectContext())
{
Project p = db.Projects.Find(cb_Projects.SelectedValue);
if (db.Entry(p).State == EntityState.Detached)
{
db.Projects.Attach(p);
}
p.Customers.Clear();
p.Workers.Clear();
p.Materials.Clear();
db.Projects.Remove(p);
db.SaveChanges();

When you called this:
p.Customers.Clear();
p.Workers.Clear();
p.Materials.Clear();
You did noting because it only works if collections are populated moreover if those relations are one-to-many you will also need to delate (call Remove) on every single dependent entity. To populate those collections you must either use eager loading
long selectedValue = cb_Projects.SelectedValue;
Project p = db.Projects.Include(p => p.Customers)
.Include(p => p.Workers)
.Include(p => p.Materials)
.Single(p => p.ProjectID == selectedValue);
or mark all three properties as virtual to enable lazy loading.
Your current code should be handled by cascade delete.
This also doesn't make much sense:
if (db.Entry(p).State == EntityState.Detached)
{
db.Projects.Attach(p);
}
You are searching for the project in the new instance of the context so it will always be loaded from the database and its state will be Unchanged.

Related

Linq with Lambda - how do I restrict joined table rows?

I want to use Linq to duplicate this T-SQL query on a sports teams database, to look up the experienced players in handball teams:
Select TE.TeamName, PL.FirstName, PL.LastName
From T_Team as TE
Inner Join T_Player As PL
On PL.Team_ID = TE.Team_ID
And PL.ExpLevel = 'Experienced'
Where TE.SportName = 'Handball'
I've tried creating two entities for my two tables:
public class TTeam
{
public int TeamId { get; set; }
public string TeamName { get; set; }
public string SportName { get; set; }
public virtual List<TPlayer> TeamPlayers { get; set; }
// Called in the context OnModelCreating() method
public static void CreateModel(EntityTypeBuilder<TTeam> p_ebpTable)
{
p_etbTable.ToTable("T_TEAM");
p_etbTable.HasKey(t => new { t.TeamId }).HasName("PK_TEAMID_T_TEAM");
// Column definitions
// Foreign Keys
p_etbTable.HasMany(t => t.TeamPlayers).
WithOne(p => p.CurrentTeam).
HasPrincipalKey(t => t.TeamId).
HasForeignKey(p => p.TeamId);
}
}
and
public class TPlayer
{
public int PlayerId { get; set; }
public int TeamId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string ExpLevel { get; set; }
public virtual TTeam CurrentTeam { get; set; }
// Called in the context OnModelCreating() method
public static void CreateModel(EntityTypeBuilder<TPlayer> p_ebpTable)
{
p_etbTable.ToTable("T_PLAYER");
p_etbTable.HasKey(t => new { t.PlayerId }).HasName("PK_PLAYERID_T_PLAYER");
// Column definitions
// Foreign Keys
p_etbTable.HasOne(p => p.CurrentTeam).
WithMany(t => t.TeamPlayers).
HasForeignKey(p => p.TeamId).
HasPrincipalKey(t => t.TeamId);
}
}
then use them in
using Microsoft.EntityFrameworkCore;
IEnumerable<TTeam> z_enbHandballTeams = z_dbcDbContext.TTeamRepository
.Where(te => te.SportName == "Handball")
.Include(te => te.TeamPlayers.Where(pl => pl.ExpLevel == "Experienced"));
but looping through z_enbHandballTeams in a foreach, throws an InvalidOperationException with the message "Lambda expression used inside Include is not valid".
(I guess it goes without saying that ExpLevel is a number and SportName is actually SportId, but I felt it would look easier to read that way.)
What am I doing wrong?
EF Core 3.1.x do not support filtered Include. Workaround is to do that via Select
var z_enbHandballTeams = z_dbcDbContext.TTeamRepository
.Where(te => te.SportName == "Handball")
.Select(te => new TTeam
{
TeamId = te.TeamId,
TeamName = te.TeamName,
SportName = te.SportName,
TeamPlayers = te.TeamPlayers.Where(pl => pl.ExpLevel == "Experienced")
.ToList()
});

Entity Framework Core (Postgres) Multiple Includes creates ghost property

I'm having an issue with a series of Include/ThenInclude in a query.
Here is my EntityFrameworkCore Query :
var fund = await funds.Where(x => x.Id == fundId)
.Include(f => f.Compositions.Where(compo => compo.Date == compositionDate))
.ThenInclude(c => c.CompositionItems)
.ThenInclude(item => item.Asset)
.FirstOrDefaultAsync(token)
?? throw new NotFoundException(nameof(Fund), fundId);
I recieve a 'CompositionDate does not exists' error.
As you can see the CompositionDate property is at the Compositions Level.
When I check the SQL generated I get this in a subquery Select statement :
SELECT f1."CompositionFundId", f1."CompositionDate", f1."AssetId", f1."Amount", a."Id", a."CountryCode", a."Currency", a."FundCompositionDate", a."FundCompositionFundId", a."Isin", a."Name", a."SecurityType", a."Ticker", a."Coupon", a."GicsSector", a."InvestmentCase", a."IpoDate", a."Theme"
FROM "FundCompositionItem" AS f1
INNER JOIN "Asset" AS a ON f1."AssetId" = a."Id"
Those 2 properties a."FundCompositionDate", a."FundCompositionFundId" doesn't exists at the 'Asset' level.
They exists in the parent (at the 'Where' level on the first Include).
I'm using Postgres provider for EFcore. Could this be the issue?
Should I be using the select anonymous type .Select(x => new { Fund = x, Compo = x.Compo.Where(...), etc... }?
I would like to preserve the navigation properties if possible. (accessing assets from compositionItems)
Any help would be much appreciated.
Edit:
Models as requested by Atiyar:
public class Portfolio : AuditableEntity
{
public Guid Id { get; set; }
public Guid Name{ get; set; }
}
public class Fund : Portfolio
{
// Irrelevant properties
public IList<FundComposition> Compositions { get; } = new List<FundComposition>();
}
public class FundComposition
{
public Fund Fund { get; set; }
// Primary key / Foreign key
public Guid FundId { get; set; }
// Primary Key
public DateTime Date { get; set; }
public List<FundCompositionItem> CompositionItems { get; set; } = new();
}
public class FundCompositionItem
{
public FundComposition Composition { get; set; }
// Primary Key
public Guid CompositionFundId { get; set; }
// Primary Key
public DateTime CompositionDate { get; set; }
public Asset Asset { get; set; }
// Primary Key
public Guid AssetId { get; set; }
public double Amount { get; set; }
}
public class Asset : BaseEntity
{
// Primary Key
public Guid Id { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
// more properties
}
In my experience, I've applied the Include() and ThenInclude() first and then applied the any conditional clauses afterwards. I'm also not sure if using Where inside of an include method does what you expect it to.
You can also apply your conditional in the first parameter of .FirstOrDefaultAsync().
var fund = await funds.Where(x => x.Id == fundId)
.Include(f => f.Compositions)
.ThenInclude(c => c.CompositionItems)
.ThenInclude(item => item.Asset)
.FirstOrDefaultAsync(x =>
x.Id == fundId && x.Compositions.Any(compo => compo.Date == compositionDate),
token
)

How to update entity with IntersectionTable in c# ef5 db first approach?

I created my database and started developing a web application in c# with EF5 and the DB First approach. I can modify my entities on their own data fields but donĀ“t get it to work when it comes to updating relationships. A simple relationship example is Project <- ProjectCategoryIntersection -> Category
Model:
public class Project
{
public TProject project { get; private set; }
public List<string> Categories { get; set; }
}
public partial class TProject //generated table object
{
public virtual ICollection<TProjectCategoryIntersection> TProjectCategoryIntersection { get; set; }
}
public partial class TProjectCategoryIntersection
{
public int Id { get; set; }
public int ProjectId { get; set; }
public int ProjectCategoryId { get; set; }
public virtual TProject T_Project { get; set; }
public virtual TCategory T_ProjectCategory { get; set; }
}
Save:
public void SaveProject(Project project)
{
var context = new ProjectManagementEntities();
TProject projectToUpdate = new TProject();
projectToUpdate.Id = project.Id;
foreach (var category in project.Categories)
{
var cat = (from c in context.TProjectCategory
where c.Name == category
select c).FirstOrDefault();
var inters = new TProjectCategoryIntersection() { ProjectCategoryId = cat.Id, ProjectId = project.project.Id, TProject = project.project, TProjectCategory = cat };
projectToUpdate.TProjectCategoryIntersection.Add(inters);
}
var entry = context.Entry(projectToUpdate).State = EntityState.Modified; //throws exceptions
context.SaveChanges();
}
exception:
Conflicting changes to the role 'TProject' of the relationship 'ProjectManagementModel.FK_TProjectCategoryIntersection_TProject' have been detected.
I also receive a multiple instances ChangeTracker exception when i try to add the categories directly to the project object:
project.project.TProjectCategoryIntersection.Add(inters);
Should i remove the generated table object from my model?
public class Project
{
public TProject project { get; private set; } //remove this?
public List<string> Categories { get; set; }
}
Solution
I ended up removing the generated table object public TProject project { get; private set; } and changed my code to:
public void SaveProject(Project project)
{
var context = new ProjectManagementEntities();
var projectToUpdate = context.T_Project.Find(project.Id);
foreach (var item in projectToUpdate.T_ProjectCategoryIntersection.ToList())
{
var oldCat = context.T_ProjectCategoryIntersection.Find(item.Id);
context.T_ProjectCategoryIntersection.Remove(oldCat);
}
foreach (var category in project.Categories)
{
var cat = (from c in context.T_ProjectCategory
where c.Name == category
select c).FirstOrDefault();
var inters = new T_ProjectCategoryIntersection() { ProjectCategoryId = cat.Id, ProjectId = project.Id };
context.T_ProjectCategoryIntersection.Add(inters);
}
//more code...
context.Entry(projectToUpdate).State = EntityState.Modified;
context.SaveChanges();
}
Apperantly this happens when you use reference to an object and also an Integer for the ID within the same object and change both of them. When this happens EF can not know which one is the correct reference
Try setting only Ids and set null for references like
var inters = new TProjectCategoryIntersection() { ProjectCategoryId = cat.Id,
ProjectId = project.project.Id};

Entity Framework 6 custom many-to-many child with implicit insert and delete

I have parent object (LoanApplication) with a child (LoanApplicationQualificationTypes) that is a custom many-to-many table. The reason I have a custom is that it has two audit columns that need to be populated (ModifiedBy, ModifiedDate).
To get the children that were added or removed from the child collection to be persisted in the database correctly, I had to explicitly handle.
Below is the code (simplified by removing other properties that were germane to the question).
Parent (part of many-to-many):
[Serializable]
[Table("LoanApplication")]
public class LoanApplication : BaseDomainModelWithId, ILoanApplication
{
[Key]
[Column("LoanApplicationId")]
public override int? Id { get; set; }
[ForeignKey("LoanApplicationId")]
public virtual ICollection<LoanApplicationQualificationTypes> LoanApplicationQualificationTypes { get; set; }
IReadOnlyCollection<ILoanApplicationQualificationTypes> ILoanApplication.LoanApplicationQualificationTypes
{
get
{
var loanApplicationQualificationTypes = new List<ILoanApplicationQualificationTypes>();
if (LoanApplicationQualificationTypes == null) return loanApplicationQualificationTypes;
loanApplicationQualificationTypes.AddRange(LoanApplicationQualificationTypes);
return loanApplicationQualificationTypes.AsReadOnly();
}
set
{
foreach (var item in value)
{
LoanApplicationQualificationTypes.Add((LoanApplicationQualificationTypes)item);
}
}
}
public LoanApplication() : base()
{
LoanApplicationQualificationTypes = new List<LoanApplicationQualificationTypes>();
}
}
public interface ILoanApplication : IDomainModel, ILoanApplicationBase, IKeyIntId
{
IReadOnlyCollection<ILoanApplicationQualificationTypes> LoanApplicationQualificationTypes { get; set; }
}
Object part of many-to-many:
[Serializable]
[Table("QualificationType")]
public class QualificationType : IQualificationType
{
[Key]
[Column("QualificationTypeId")]
public override int? Id { get; set; }
[Required]
public string TypeName { get; set; }
[Required]
public bool IsActive { get; set; }
public virtual string ModifiedBy { get; set; }
public virtual DateTimeOffset? ModifiedDate { get; set; }
public QualificationType() : { }
}
Custom Many-to-Many:
[Serializable]
[Table("LoanApplicationQualificationTypes")]
public class LoanApplicationQualificationTypes : ILoanApplicationQualificationTypes
{
[Key]
[Column(Order = 1)]
public int? LoanApplicationId { get; set; }
[ForeignKey("LoanApplicationId")]
public virtual LoanApplication LoanApplication { get; set; }
ILoanApplication ILoanApplicationQualificationTypes.LoanApplication
{
get
{
return this.LoanApplication;
}
set
{
this.LoanApplication = (LoanApplication)value;
}
}
[Required]
[Key]
[Column(Order = 2)]
public int QualificationTypeId { get; set; }
[ForeignKey("QualificationTypeId")]
public virtual QualificationType QualificationType { get; set; }
IQualificationType ILoanApplicationQualificationTypes.QualificationType
{
get
{
return this.QualificationType;
}
set
{
this.QualificationType = (QualificationType)value;
}
}
public virtual string ModifiedBy { get; set; }
public virtual DateTimeOffset? ModifiedDate { get; set; }
public LoanApplicationQualificationTypes() { }
}
Update method in LoanApplication Repository:
public bool Update(ILoanApplication entity)
{
using (var db = new MainContext())
{
entity.ModifiedDate = DateTime.UtcNow;
entity.ModifiedBy = UserOrProcessName;
// Add / Remove LoanApplicationQualificationTypes and populate audit columns
if (entity.LoanApplicationQualificationTypes?.Count > 0)
{
var existingItems = db.LoanApplicationQualificationTypes.Where(q => q.LoanApplicationId == entity.Id.Value).ToList();
var newItems = entity.LoanApplicationQualificationTypes.Where(q => existingItems.All(e => e.QualificationTypeId != q.QualificationTypeId));
var deletedItems = existingItems.Where(q => entity.LoanApplicationQualificationTypes.All(e => e.QualificationTypeId != q.QualificationTypeId));
foreach (var newItem in newItems)
{
newItem.ModifiedBy = UserOrProcessName;
newItem.ModifiedDate = DateTime.UtcNow;
db.LoanApplicationQualificationTypes.Add((LoanApplicationQualificationTypes)newItem);
}
foreach (var deletedItem in deletedItems)
{
db.LoanApplicationQualificationTypes.Remove((LoanApplicationQualificationTypes)deletedItem);
}
// Need to clear to avoid duplicate objects
((LoanApplication)entity).LoanApplicationQualificationTypes.Clear();
}
db.Entry(entity).State = EntityState.Modified;
db.SaveChanges();
}
return true;
}
Is there a way implement the Update without the explicitly handling adds/updates?
The way I understand it, the question is how to apply the (potential) modifications to the link table without explicitly detecting added/removed links. Also I assume the other part of the link must exist.
It's possible with the following sequence of operations:
First load the actual entity from the database into context, including the links:
var dbEntity = db.LoanApplication
.Include(e => e.LoanApplicationQualificationTypes)
.FirstOrDefault(e => e.Id == entity.Id);
This will allow change tracker to determine the correct add/update/delete link operations for you later.
Then apply the primitive master data changes:
db.Entry(dbEntity).CurrentValues.SetValues(entity);
dbEntity.ModifiedDate = DateTime.UtcNow;
dbEntity.ModifiedBy = UserOrProcessName;
Finally, replace the links with the ones from the incoming entity. To avoid navigation property references pointing to different objects (and in particular to prevent EF trying to create the new records for the other side objects of the relation), do not use directly the incoming objects, but create stub objects with only FK properties set:
dbEntity.LoanApplicationQualificationTypes = entity.LoanApplicationQualificationTypes
.Select(e => new LoanApplicationQualificationTypes
{
LoanApplicationId = e.LoanApplicationId,
QualificationTypeId = e.QualificationTypeId,
ModifiedDate = DateTime.UtcNow,
ModifiedBy = UserOrProcessName,
})
.ToList();
And that's it. At this point the change tracker has all the necessary information to produce the correct commands when you call db.SaveChanges().
One thing to mention. If you look at db.ChangeTracker.Entries at this point, you'll probably notice that all the old links are marked as Deleted, all the incoming as Added and there are no Modified entries. Don't worry. EF is smart enough and will convert Deleted + Added pairs with the same PK to single update commands.
The whole method:
public bool Update(ILoanApplication entity)
{
using (var db = new MainContext())
{
var dbEntity = db.LoanApplication
.Include(e => e.LoanApplicationQualificationTypes)
.FirstOrDefault(e => e.Id == entity.Id);
if (dbEntity == null) return false;
db.Entry(dbEntity).CurrentValues.SetValues(entity);
dbEntity.ModifiedDate = DateTime.UtcNow;
dbEntity.ModifiedBy = UserOrProcessName;
dbEntity.LoanApplicationQualificationTypes = entity.LoanApplicationQualificationTypes
.Select(e => new LoanApplicationQualificationTypes
{
LoanApplicationId = e.LoanApplicationId,
QualificationTypeId = e.QualificationTypeId,
ModifiedDate = DateTime.UtcNow,
ModifiedBy = UserOrProcessName,
})
.ToList();
db.SaveChanges();
return true;
}
}

Universal Windows App 10 - EF7 SQLite One-to-many relationship

I'm new to developing UWA. I'm trying to develop an app that stores information on a local database.
I can store and get information without problem, but I can't find how to work with relationships.
I have:
public class ToDoItem
{
public int ToDoItemId
{
get; set;
}
public int ToDoItemCategoryId
{
get; set;
}
public virtual ToDoItemCategory ToDoItemCategory
{
get; set;
}
[Required]
public string Description
{
get; set;
}
}
And:
public class ToDoItemCategory
{
public int ToDoItemCategoryId
{
get; set;
}
public virtual ICollection<ToDoItem> ToDoItems
{
get; set;
}
[Required]
public string Name
{
get; set;
}
}
And, I'm saving the information this way:
using (var db = new DatabaseContext())
{
var cat = db.ToDoItemCategories.Single(m => m.ToDoItemCategoryId == SelectedCategory.ToDoItemCategoryId);
ToDoItem model = new ToDoItem() { Description = description, ToDoItemCategory = cat };
db.ToDoItems.Add(model);
db.SaveChanges();
}
But ToDoItems don't get saved to the ToDoItemCategory ToDoItems list. Also, ToDoItemId is being auto-generated with negative ID's, I don't understand why.
I've also tried something similar to this:
using (var db = new DatabaseContext())
{
var cat = db.ToDoItemCategories.Single(m => m.ToDoItemCategoryId == SelectedCategory.ToDoItemCategoryId);
ToDoItem model = new ToDoItem() { Description = description, ToDoItemCategory = cat };
db.ToDoItems.Add(model);
cat.ToDoItems.Add(model);
db.SaveChanges();
}
But no luck. What am I doing wrong here?
After testing and searching a lot I've found a solution to this problem.
Categories = Context.ToDoItemCategories.Where(predicate).Include(m => m.ToDoItems).ToList();
Apparently the navigation properties only get loaded after the Include. Doing that everything works fine.

Categories

Resources