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

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

Related

How to save data with EF in the right way (multiple tables)

For Example you have two tables you which are connected (code first) by a ICollection property.
public class EntityEnviroment
{
[Key]
public virtual int env_id { get; set; }
public virtual string env_name { get; set; }
public virtual string env_country { get; set; }
public virtual ICollection<StcEntityFailedReportDetail> failedReportDetails { get; set; }
}
public class EntityFailedReportDetail
{
[Key]
public virtual int failed_reports_details_id { get; set; }
public virtual int report_id { get; set; }
public virtual string report_status { get; set; }
public virtual StcEntityEnviromentStatus StcEntityEnviromentStatus { get; set; }
}
For one table I create a instance of the context and on for my entry and the I add it. At last I save it. For multiple tables I did this:
using (var db = new StatusPlatformContext())
{
var entryDetail = new EntityFailedReportDetail();
foreach (var value in result.failed_report_details)
{
entryDetail.report_id = value.report_id;
entryDetail.report_status = value.report_status;
db.StcEntityFailedReportDetails.Add(entryDetail);
}
var entry = new EntityEnviroment
{
env_name = result.environment_status.env_name,
env_country = "Ger",
failedReportDetails = new List<EntityFailedReportDetail> { entryDetail }
};
entryDetail.EntityEnviroment = entry;
db.EntityEnviromentStat.Add(entry);
db.SaveChanges();
}
If I do it like this only the last entry of the details are saved. If I add a db.SaveChanges() to the foreach I will just get an entry in the foreigen key column for the last of the three detail rows.
How I should structure the code? All examples I found just show one table not multiple. Is there any example with a pattern what I should read?
Thanks
There are two problems
1) You need to create a new EntityFailedReportDetail for each result.
2) Additionally, add each EntityFailedReportDetail to the EntityEnvironment as they are made.
Try this:
var entry = new EntityEnviroment
{
env_name = result.environment_status.env_name,
env_country = "Ger",
failedReportDetails = new List<EntityFailedReportDetail>()
};
foreach (var value in result.failed_report_details)
{
var entryDetail = new EntityFailedReportDetail();
entryDetail.report_id = value.report_id;
entryDetail.report_status = value.report_status;
entry.failedReportDetails.Add(entryDetail);
}
For entities that contain collections of other entities, you should initialize these collections on construction:
public class EntityEnviroment
{
[Key]
public virtual int env_id { get; set; }
public virtual string env_name { get; set; }
public virtual string env_country { get; set; }
public virtual ICollection<StcEntityFailedReportDetail> failedReportDetails { get; set; } = new List<StcEntityFailedReportDetail>();
}
This way you can use these collections on new entities right out of the gate. Anywhere in your code that "sets" a collection should be flagged for investigation. It is not a way to clear a collection from your DB for example so if anywhere other than this (or a constructor) does an = new List<TEntity>() that is a problem.
var entryDetail = new EntityFailedReportDetail();
foreach (var value in result.failed_report_details)
{
entryDetail.report_id = value.report_id;
entryDetail.report_status = value.report_status;
db.StcEntityFailedReportDetails.Add(entryDetail);
}
Your problem with this code is that you are initializing 1 "detail" record, then in a loop, updating it's details and trying to "Add" it to the DbSet. This is one single reference.
Then this code:
failedReportDetails = new List<EntityFailedReportDetail> { entryDetail }
Would simply initialize a collection with that one reference with the latest detail you added.
Adjusting your example:
using (var db = new StatusPlatformContext())
{
var entry = new EntityEnviroment
{
env_name = result.environment_status.env_name,
env_country = "Ger",
};
foreach (var value in result.failed_report_details)
{
var entryDetail = new EntityFailedReportDetail
{
report_id = value.report_id,
report_status = value.report_status,
EntityEnvironment = entry
};
entry.failedReportDetails.Add(entryDetail);
}
db.EntityEnviromentStat.Add(entry);
db.SaveChanges();
}
You don't need to explicitly add each detail to the context's DbSet of details, and if you don't need to query details outside of the entry, your context doesn't even need a DbSet of details. EF will manage the related entities so you only need DbSets for "top level" entities, basically the parent entities that your system references individually. You can always query related entities through their parents.

Entity Framework add local data to list from database

I am pretty new to Entity Framework and I am using this method in order to query through my database:
var _context = new StudioEntities();
var results = _context.tblStudios.Select(u => new
{
u.Standort,
u.Name,
u.Id
}).ToList();
Now my goal is to add local data as well which isn't present in the database. I tried it with this code but it didn't work:
results.Add(new tblStudio { Id = 0, Name = "Gesamt" });
Can someone help me with this? Thanks
Edit:
My table class looks like this:
public partial class tblStudio
{
public int Id { get; set; }
public string Name { get; set; }
public string Standort { get; set; }
public Nullable<int> Plz { get; set; }
}
The result is not a List of tblStudios, it is a List of Anonymous Type. So if you want to add an item to the result you should do like this:
var results = _context.tblStudios.Select(u => new tblStudiosDTO()
{
Standort = u.Standort,
Name = u.Name,
Id = u.Id
}).ToList();
results.Add(new tblStudiosDTO() { Id = "0", Name = "Gesamt" });
But because you cannot project onto a mapped entity then you need to create a DTO class like tblStudiosDTO with needed properties from the tblStudios entity.
public class tblStudiosDTO
{
public string Standort { get; set; }
public string Name { get; set; }
public string Id { get; set; }
}

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.

Can I create nested classes when using Linq-To-Entities?

I'm still learning Entity Framework and Linq-To-Entities, and I was wondering if a statement of this kind is possible:
using (var context = new MyEntities())
{
return (
from a in context.ModelSetA.Include("ModelB")
join c in context.ModelSetC on a.Id equals c.Id
join d in context.ModelSetD on a.Id equals d.Id
select new MyModelA()
{
Id = a.Id,
Name = a.Name,
ModelB = new MyModelB() { Id = a.ModelB.Id, Name = a.ModelB..Name },
ModelC = new MyModelC() { Id = c.Id, Name = c.Name },
ModelD = new MyModelD() { Id = d.Id, Name = d.Name }
}).FirstOrDefault();
}
I have to work with a pre-existing database structure, which is not very optimized, so I am unable to generate EF models without a lot of extra work. I thought it would be easy to simply create my own Models and map the data to them, but I keep getting the following error:
Unable to create a constant value of type 'MyNamespace.MyModelB'. Only
primitive types ('such as Int32, String, and Guid') are supported in
this context.
If I remove the mapping for ModelB, ModelC, and ModelD it runs correctly. Am I unable to create new nested classes with Linq-To-Entities? Or am I just writing this the wrong way?
What you have will work fine with POCOs (e.g., view models). Here's an example. You just can't construct entities this way.
Also, join is generally inappropriate for a L2E query. Use the entity navigation properties instead.
I have created your model (how I understand it) with EF 4.1 in a console application:
If you want to test it, add reference to EntityFramework.dll and paste the following into Program.cs (EF 4.1 creates DB automatically if you have SQL Server Express installed):
using System.Linq;
using System.Data.Entity;
namespace EFNestedProjection
{
// Entities
public class ModelA
{
public int Id { get; set; }
public string Name { get; set; }
public ModelB ModelB { get; set; }
}
public class ModelB
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ModelC
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ModelD
{
public int Id { get; set; }
public string Name { get; set; }
}
// Context
public class MyContext : DbContext
{
public DbSet<ModelA> ModelSetA { get; set; }
public DbSet<ModelB> ModelSetB { get; set; }
public DbSet<ModelC> ModelSetC { get; set; }
public DbSet<ModelD> ModelSetD { get; set; }
}
// ViewModels for projections, not entities
public class MyModelA
{
public int Id { get; set; }
public string Name { get; set; }
public MyModelB ModelB { get; set; }
public MyModelC ModelC { get; set; }
public MyModelD ModelD { get; set; }
}
public class MyModelB
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyModelC
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyModelD
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
// Create some entities in DB
using (var ctx = new MyContext())
{
var modelA = new ModelA { Name = "ModelA" };
var modelB = new ModelB { Name = "ModelB" };
var modelC = new ModelC { Name = "ModelC" };
var modelD = new ModelD { Name = "ModelD" };
modelA.ModelB = modelB;
ctx.ModelSetA.Add(modelA);
ctx.ModelSetB.Add(modelB);
ctx.ModelSetC.Add(modelC);
ctx.ModelSetD.Add(modelD);
ctx.SaveChanges();
}
// Run query
using (var ctx = new MyContext())
{
var result = (
from a in ctx.ModelSetA.Include("ModelB")
join c in ctx.ModelSetC on a.Id equals c.Id
join d in ctx.ModelSetD on a.Id equals d.Id
select new MyModelA()
{
Id = a.Id,
Name = a.Name,
ModelB = new MyModelB() {
Id = a.ModelB.Id, Name = a.ModelB.Name },
ModelC = new MyModelC() {
Id = c.Id, Name = c.Name },
ModelD = new MyModelD() {
Id = d.Id, Name = d.Name }
}).FirstOrDefault();
// No exception here
}
}
}
}
This works without problems. (I have also recreated the model from the database (which EF 4.1 had created) in EF 4.0: It works as well. Not surprising since EF 4.1 doesn't change anything in LINQ to Entities.)
Now the question is why you get an exception? My guess is that there is some important difference in your Models or ViewModels or your query compared to the simple model above which is not visible in your code example in the question.
But the general result is: Projections into nested (non-entity) classes work. (I'm using it in many situations, even with nested collections.) Answer to your question title is: Yes.
What Craig posted does not seem to work for nested entities. Craig, if I am misunderstood what you posted, please correct me.
Here is the workaround I came up with that does work:
using (var context = new MyEntities())
{
var x = (
from a in context.ModelSetA.Include("ModelB")
join c in context.ModelSetC on a.Id equals c.Id
join d in context.ModelSetD on a.Id equals d.Id
select new { a, b, c }).FirstOrDefault();
if (x == null)
return null;
return new MyModelA()
{
Id = x.a.Id,
Name = x.a.Name,
ModelB = new MyModelB() { Id = x.a.ModelB.Id, Name = x.a.ModelB..Name },
ModelC = new MyModelC() { Id = x.c.Id, Name = x.c.Name },
ModelD = new MyModelD() { Id = x.d.Id, Name = x.d.Name }
};
}
Since Entity Framework can't handle creating nested classes from within the query, I simply returned an anonymous object from my query containing the data I wanted, then mapped it to the Model

saveChanges problem

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.

Categories

Resources