Adding Complex Objects in Entity Framework - c#

I'm setting up .NET Core API. My application is divided into 3 layers:
Data Access
Business Logic/Services
API
I'm having troubles added related objects with the API method. Let's say I have following classes.
public class Part
{
[Key]public int Id {get; set;}
public string Name {get; set;}
public string ICollection<PartAttribute> PartAttributes{get; set;}
}
public class PartAttribute
{
[Key]public int Id {get; set;}
public string Name {get; set;}
public string Value {get; set;}
}
And following methods for interaction with DB - context is EF DbContext:
public virtual void Add(T entity)
{
Context.Set<T>()
.Add(entity);
}
I'm having trouble adding Part that have already existing PartAttributes, if I send following JSON via API (assuming that following record in PartAttributes already exist)
{
"Name":"Example1",
"PartAttributes":[
{
"attributeId":1,
"name":"Color",
"value":"black"
}]
}
I'm getting a following exception: "Cannot insert explicit value for identity column in table 'PartAttributes' when IDENTITY_INSERT is set to OFF." - This leads me to conclusion that EF is not recognizing the existing record and tries to insert it as new one. This results in fail because of identity insert setting in SQL Server itself.
What I'd like to achieve is that the existing objects would be recognized and EF would not try to insert the existing attributes as new records in the database. What are the best practices to achieve that behaviour?

You need to take your primary key (which here looks to be attributeId) and load the record using the dbContext. Once loaded update the values and call SaveChanges() or SaveChangesAsync() on the context.
You can even have a method that does insert or update.
for example (SUDO CODE!):
public void InsertOrUpdate(PartAttribute model)
{
var existingRecord = dbContext.Set<PartAttribute>().Find(model.attributeId);
if(existingRecord == null)
{ // Insert (Should not include value for attributeId)
dbContext.Add(model);
}
else
{ // Update
existingRecord = model;
dbContext.SaveChanges();
}
}
Please let me know if you still have issues

Related

HotChocolate merge entity filter

I use for my ASP.NET Web API HotChocolate as a GraphQL Server, which generates filter based on the Entity used on the Query.
The project use an old DB schema, which is way outdated and can't/shouldn't be used anymore, therefore, we want to slowly migrate to a new one.
So to my question:
Is it possible to create a GraphQL filter based on two or multiple Entities from different DB contexts?
Example:
class ExampleOld {
public string Information1 {get; set;}
public ChildOld ChildOld {get; set;}
}
class ChildOld {
public string Text1 {get; set;}
}
class ExampleNew {
public string Information2 {get; set;}
public ChildNew ChildNew {get; set;}
}
class ChildNew {
public string Text2 {get; set;}
}
Query:
query {
example(where: {
and: [
{information1: {eq: '...' }}
{childOld: { text1: {eq: '...'}}}
{information2: {eq: '...' }}
{childNew: { text1: {eq: '...'}}}
}]) {
...
}
}
For now I set the filter manualy on the IQueryable object with the IResolverContext.
Tec Stack:
.NET 6.0
EF Core
HotChocolate
MSSQL DB (for old a new DB)
I'm also open to other solutions.
One solution with thought of, was to migrate all data/table/columns we use now and only work with the new DB.
If we start with a new feature, then we have to migrate the connected tables first.
The problem is on this solution, that we have to migrate all data that we want to filter with.

How to create a EntitySet or Model without creating corresponding table in database - Entity Framework

I have a stored procedure in my sqlserver database which is returning multiple resultset.
I am using following link from msdn to read multiple resultset from SP in entity framework.
https://msdn.microsoft.com/en-us/library/jj691402(v=vs.113).aspx
To read data, I need to have DBSets in xyzDBContext class for each of the resultsets.
Model Class:
public class AreaView
{
public String Area { get; set; }
public String Weight { get; set; }
}
DBContext:
public class EnsembleAPIContext : DbContext
{
public DbSet<AreaView> area {get; set;}
// I want to prevent this table from getting created in db
}
This is how I am reading resultset and mapping it with above created dbset.
reader.NextResult();
var contributionArea = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<ContributionArea>(reader, "area ", MergeOption.AppendOnly);
What I need to do is I want to create entity for these resultsets, but I dont want framework to create tables for these entities in database.
Note: Reason for doing this is, resultset which is returned by sp doesnt have a primary key, so suppose we can have a valid table created using entity without PK.
Is this possible?
Any help is appreciated.
The answer to "If we can skip creating tables in database with Entity framework is": Yes
Use [NotMapped] attribute.
[NotMapped]
public class Employee
{
public int ID { get; set; }
public String name { get; set; }
}
You can use this model for general purpose and table wont be created for this in database.
Another way of doing this is
In OnModelCreating() method
modelBuilder.Ignore<Employee>();
This way DBContext will ignore creating table for this model.
Can we create a DBSet<> without creating corresponding table in database
No. DbSet<T> represents a real database table or view.
To read data, I need to have DBSets in xyzDBContext class for each of the resultsets.
You don't. The ObjectContext.Translate method can be used to map DbReader to any class. The linked example is using entity types, but there is another Translate method overload which works for any type as described in Directly Executing Store Commands MSDN topic - Materializing the Result Type section.
With that being said, remove the DbSet from your context and use something like this:
var areaView = ((IObjectContextAdapter)db).ObjectContext.Translate<AreaView>(reader);

How to manually set entity primary key in Entity Framework code first database?

Well, I have the following model structure: I have one class - DatabaseEntity which is basically
public class DatabaseEntity
{
public int Id { get; set; }
}
so each entity like product, category etc will inherit DatabaseEntity and have Id property. Also I have typical EntityFramework repository class with InsertOrUpdate method:
private readonly DbContext _database;
public void InsertOrUpdate<TObject>(TObject entity) where TObject : DatabaseEntity
{
if(entity.Id == default(int))
{
// New entity
DbSet<TObject>().Add(entity);
}
else
{
// Existing entity
_database.Entry(entity).State = EntityState.Modified;
}
_database.SaveChanges();
}
Then I download from eBay via eBay api list of categoies I have to add to database. Basically category is:
public class EbayCategory : DatabaseEntity
{
// It has Id since it inherits DatabaseEntity
public string Name { get; set; }
// ... some other properties
}
But, the problem is, when I download those categories I download and their Id properties, which, of course, already have values. And when I try to save them to database like:
public void UpdateCategories(IEnumerable<EbayCategory> newCategories)
{
foreach (var newCategory in newCategories)
{
_repository.InsertOrUpdate(newCategory);
}
}
I face some issues... First of all, entity.Id != default(int) because it has value, so repository tries to update this entity, instead of adding, but it is not in the database or context so it throws the following exception:
System.Data.Entity.Infrastructure.DbUpdateConcurencyException
"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries."
... because it thinks that someone else deleted entity which I am trying to update. How can I save this InsertOrUpdate logic, since a lot of projects are based on it, and be able to add items (EbayCategories) with primary key (Id) to database and then update/delete them like other entities without discarding EbayCategory.Id value?
To allow you to manually generate Ids you need a class that has a manually generated ID - so it cannot inherit from DatabaseEntity
public class EbayCategory
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
// ... some other properties
}
Now you will need a different InsertOrUpdate to handle entities that have manually generated keys:
public void InsertOrUpdate(EbayCategory entity)
{
if(Find(entity.ID == null)
{
// New entity
DbSet<EbayCategory>().Add(entity);
}
else
{
// Existing entity
_database.Entry(entity).State = EntityState.Modified;
}
_database.SaveChanges();
}
Colin's answer above quite correctly shows how to achieve this setting using data annotations.
But in the presented problem the entity is a subclass so you can't add the annotation without changing the entity class.
There is an alternative configuration method: Fluent Configuration. Here's my example using an EntityTypeConfiguration class:
public class LookupSchoolsConfiguration : EntityTypeConfiguration<LookupSchools>
{
public LookupSchoolsConfiguration()
{
Property(l => l.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
}
}
You can also add configuration directly to the modelBuilder as per this post: https://stackoverflow.com/a/4999894/486028

Entity Framework: Create and update related objects in WPF

I'm using EF 6.0 and code-first approach.
I have problem with create and update data in db via Entity Framework. I'm not sure if I need to make db.Groups.Attach(student.Group) before storing Student. Without this after saving Student I also have new Group with the same Name but other GroupId.
Moreover I can't update student because I'm getting exception: The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
public class Student {
[Key]
public int StudentId {get; set;}
public string Name {get; set;}
public Group Group {get; set;}
}
public class Group {
[Key]
public int GroupId{ get; set;}
public string Name {get; set;}
public virtual ICollection<Student> Students {get; set;}
}
.
public class StudentDao {
public void createStudent(Student student) {
using (var db = new StorageContext()) {
// without this also creates new Group.
db.Groups.Attach(student.Group);
db.Students.Add(student);
db.SaveChanges();
}
}
public void updateStudent(Student student) {
using (var db = new StorageContext()) {
var original = db.Students.Find(student.StudentId);
if (original != null) {
original.Name = student.Name;
original.Group = student.Group;
db.SaveChanges(); //exception
}
}
}
}
Without this (db.Groups.Attach(student.Group)) after saving Student I also have new Group
That's because Adding an entity to a DbSet marks all adhering entities that are not yet tracked by the context as Added. This is an EF feature, like it or not, so you have to first attach the entities you don't want to re-insert.
Moreover I can't update student because I'm getting exception
For some reason, in the update method the student and its group are still attached to a context. Apparently, there is some other context active in the StudentDao class. You have to make sure this context's lifespan is over when you update the student or else (second best) detach the student and the group from it.
An off-topic advice: if you can, abandon this DAO pattern. EF works much better when you use the DbContext and its DbSets in service-like methods that handle one unit of work. With these DAO's it's impossible to work transactionally and they cause piles of repeated code.
Been a while since I worked on Entity but from what I remember you can't just change the Group, you have to give it a GroupID field as well, change that instead and then reload/update the Student object from the database so that the Group object gets loaded and assigned from within the same context.
This is just one of the reasons I use NHibernate.

Cannot update many-to-many relationships in Entity Framework

I am using Entity Framework 4.3 Code First, and I have problem with updating many-to-many relationships.
I defined the following classes:
public abstract class Entity
{
[Column(Order = 0)]
[Key]
public int Id { get; set; }
[Timestamp]
[Column(Order = 1)]
public byte[] Version { get; set; }
}
public class Video : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public TimeSpan Length { get; set; }
public virtual ICollection<Coworker> Coworkers { get; set; }
}
public class Coworker : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Video> Videos { get; set; }
}
When the database is created, the schema look right:
There is a Videos, Coworkers and VideoCoworkers table too, without
I use repository pattern in an N-Tier application to access database, my Insert and Update method looks like this:
public T Insert(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
session.Context.Set<T>().Add(entity);
}
}
public T Update(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<T>().Attach(entity);
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
When I update an entity, I create the entity object from a DTO, that's why use DbSet.Attach instead of selecting it and updating the properties one-by-one.
When I initialize the database, I add some test data:
Create 3 Coworkers, where I set first and last name. (A, B, C)
Create 3 Videos, where I set title, description and length, and also set some coworkers. First video has A,B, second has B,C and third has A,C.
When I list the Videos from code, I can see that Video.Coworkers collection is filled with good values, and when I query the link table (VideoCoworkers) in SQL Server Management Studio, it also looks good.
My problem is
when I update for example the title of the Video, it works. But when I try to delete from Video2 the existing coworkers (B and C), and try to add coworker A, then the relationship is not updated. It also does not work when I only try to add new coworker, or only try to delete one. I create the entity which is used as the parameter of the Update() method by creating a new Video entity with a new collection of Coworkers (which are selected from the database with Find() method by Id).
What is the correct way to update many-to-many relationships?
But when I try to delete from Video2 the existing coworkers (B and C),
and try to add coworker A, then the relationship is not updated.
Without using a generic repository the correct procedure would be:
using (var session = new DatabaseSession())
{
video2 = session.Context.Set<Video>().Include(v => v.Coworkers)
.Single(v => v.Id == video2Id);
coworkerA = new Coworker { Id = coworkerAId };
session.Context.Set<Coworker>().Attach(coworkerA);
video2.Coworkers.Clear();
video2.Coworkers.Add(coworkerA)
session.Context.SaveChanges();
}
The essential part is that you must load or attach the entity in its original state, change the entity, i.e. remove and add children, and then save the changes. EF's change detection will create the necessary INSERT and DELETE statements for the link table entries. The simple procedure to set the state to Modified you are trying in your generic Update method is suited only for updating scalar properties - like changing the video title - but won't work for updating relationships between entities.
For solve this problem:
attach the entity to context
load the collection(the collection is not loaded, because )
change the state of entity to modified
save changes
So your code for update should be like this:
public Video Update(Video entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<Video>().Attach(entity);
session.Context.Entry(entity).Collection(p => p.Coworkers).Load();
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
Please refer here to see how to save master detail in asp.net mvc with database first. Hopefully it will give you the idea about the code first. You may also have a look at knokout.js example

Categories