Identity: How to add items to related fields - c#

I want realise edit form, but error update item.
This is small example.
public class AppUser : IdentityUser
{
public string SurName { get; set; }
public virtual ICollection<City> Cities{ get; set; }
public AppUser()
{
Cities = new List<City>();
}
}
And the a model for the City
public class City
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<AppUser> Users { get; set; }
public City()
{
Users = new List<AppUser>();
}
}
And the UserContext class building up the one-to-many relationship between the 2 models.
public class UserContext: IdentityDbContext<AppUser>
{
public DbSet<City> Cities { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<City>().HasMany(x => x.Users)
.WithMany(x => x.Cities)
.Map(x => x.MapLeftKey("CityId")
.MapRightKey("UserId")
.ToTable("UserCity"));
base.OnModelCreating(modelBuilder);
}
}
And if I try update
UserContext db = new UserContext();
....
public ActionResult Edit()
{
var newUser = UserManager.FindById(User.Identity.GetUserId());
City city = db.Cities.FirstOrDefault();
newUser.Cities = new List<City>() { city };
newUser.SurName = "TestName";
var result = UserManager.Update(newUser);
....
}
I see error
System.InvalidOperationException:
"The relationship between the two objects cannot be defined because
they are attached to different ObjectContext objects."
If delete line
newUser.Cities = new List<City>() { city };
Error not show.
Why an error occurs when adding a field with many-to-many relationships?

solution: change from
var newUser = UserManager.FindById(User.Identity.GetUserId());
to
var newUser = db.Users.Include(x=>x.Cities).FirstOrDefault(x=>x.Id== userId);

Full list code change
public ActionResult Edit()
{
//var newUser = UserManager.FindById(User.Identity.GetUserId());
City city = db.Cities.FirstOrDefault();
newUser.Cities = new List<City>() { city };
newUser.SurName = "TestName";
//var result = UserManager.Update(newUser);
db.Entry(newUser).State = EntityState.Modified;
db.SaveChanges();
....
}

Related

C# Bad Performance OData when using extension

I have a Web API for OData services. I have a lot of table with many relations. Here is some of the table:
MSADDRESSCOUNTRY
public partial class MSADDRESSCOUNTRY
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage","CA2214:DoNotCallOverridableMethodsInConstructors")]
public MSADDRESSCOUNTRY()
{
this.MSADDRESSPROVINCEs = new HashSet<MSADDRESSPROVINCE>();
}
public int ID { get; set; }
public string CODE { get; set; }
public string COUNTRYNAME { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage","CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MSADDRESSPROVINCE> MSADDRESSPROVINCEs { get; set; }
}
MSADDRESSPROVINCE
public partial class MSADDRESSPROVINCE
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public MSADDRESSPROVINCE()
{
this.MSADDRESSDISTRICTs = new HashSet<MSADDRESSDISTRICT>();
}
public int ID { get; set; }
public Nullable<int> COUNTRYID { get; set; }
public string PROVINCENAME { get; set; }
public virtual MSADDRESSCOUNTRY MSADDRESSCOUNTRY { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage","CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MSADDRESSDISTRICT> MSADDRESSDISTRICTs { get; set; }
}
MSADDRESSDISTRICT
public partial class MSADDRESSDISTRICT
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public MSADDRESSDISTRICT()
{
this.MSADDRESSSUBDISTRICTs = new HashSet<MSADDRESSSUBDISTRICT>();
}
public int ID { get; set; }
public Nullable<int> PROVINCEID { get; set; }
public string DISTRICTNAME { get; set; }
public virtual MSADDRESSPROVINCE MSADDRESSPROVINCE { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MSADDRESSSUBDISTRICT> MSADDRESSSUBDISTRICTs { get; set; }
}
I create DTO object model for every table with the property is the same with Database object model.
I want the client can use $expand keyword to get child data and/or parent data.
For MSADDRESSCOUNTRY I need to write the code like this.
[EnableQuery(MaxExpansionDepth = 4)]
public IQueryable<MsAddressCountryObject> Get()
{
return db.MSADDRESSCOUNTRies.Select(c => new MsAddressCountryObject
{
ID = c.ID,
CODE = c.CODE,
COUNTRYNAME = c.COUNTRYNAME,
MSADDRESSPROVINCEs = c.MSADDRESSPROVINCEs.Select(data => new MsAddressProvinceObject()
{
ID = data.ID,
COUNTRYID = data.COUNTRYID,
PROVINCENAME = data.PROVINCENAME,
MSADDRESSCOUNTRY = new MsAddressCountryObject()
{
ID = data.MSADDRESSCOUNTRY.ID,
CODE = data.MSADDRESSCOUNTRY.CODE,
COUNTRYNAME = data.MSADDRESSCOUNTRY.COUNTRYNAME,
},
MSADDRESSDISTRICTs = data.MSADDRESSDISTRICTs.Select(dist => new MsAddressDistrictObject()
{
ID = dist.ID,
PROVINCEID = dist.PROVINCEID,
DISTRICTNAME = dist.DISTRICTNAME,
})
})
});
}
For MSADDRESSPROVINCE I need to write the code like this.
[EnableQuery(MaxExpansionDepth = 4)]
public IQueryable<MsAddressProvinceObject> Get()
{
return db.MSADDRESSPROVINCEs.Select(data => new MsAddressProvinceObject()
{
ID = data.ID,
COUNTRYID = data.COUNTRYID,
PROVINCENAME = data.PROVINCENAME,
MSADDRESSCOUNTRY = new MsAddressCountryObject()
{
ID = data.MSADDRESSCOUNTRY.ID,
CODE = data.MSADDRESSCOUNTRY.CODE,
COUNTRYNAME = data.MSADDRESSCOUNTRY.COUNTRYNAME,
},
MSADDRESSDISTRICTs = data.MSADDRESSDISTRICTs.Select(dist => new MsAddressDistrictObject()
{
ID = dist.ID,
PROVINCEID = dist.PROVINCEID,
DISTRICTNAME = dist.DISTRICTNAME
})
});
}
That code works fast. But if I add/change/remove column, I have to modify the controller manually, one by one for all controller. For example, if I want to add geological coordinate in MSADDRESSDISTRICT, I have to change the code in Country Controller, Province Controller and District Controller.
So I decide to create extension method like this.
public static MsAddressCountryObject ToDTO(this MSADDRESSCOUNTRY data)
{
return new MsAddressCountryObject()
{
ID = data.ID,
CODE = data.CODE,
COUNTRYNAME = data.COUNTRYNAME,
};
}
public static IQueryable<MsAddressCountryObject ToDTO(this IEnumerable<MSADDRESSCOUNTRY datas)
{
return datas.Select(country =
{
var obj = country?.ToDTO();
obj.MSADDRESSPROVINCEs = country.MSADDRESSPROVINCEs?.ToDTO();
return obj;
}).AsQueryable();
}
public static MsAddressProvinceObject ToDTO(this MSADDRESSPROVINCE data)
{
return new MsAddressProvinceObject()
{
ID = data.ID,
COUNTRYID = data.COUNTRYID,
PROVINCENAME = data.PROVINCENAME,
MSADDRESSCOUNTRY = data.MSADDRESSCOUNTRY?.ToDTO()
};
}
public static IQueryable<MsAddressProvinceObject ToDTO(this IEnumerable<MSADDRESSPROVINCE datas)
{
return datas.Select(province =
{
var obj = province?.ToDTO();
obj.MSADDRESSDISTRICTs = province.MSADDRESSDISTRICTs.ToDTO();
return obj;
}).AsQueryable();
}
public static MsAddressDistrictObject ToDTO(this MSADDRESSDISTRICT data)
{
return new MsAddressDistrictObject()
{
ID = data.ID,
PROVINCEID = data.PROVINCEID,
DISTRICTNAME = data.DISTRICTNAME,
MSADDRESSPROVINCE = data.MSADDRESSPROVINCE?.ToDTO()
};
}
public static IQueryable<MsAddressDistrictObject ToDTO(this IEnumerable<MSADDRESSDISTRICT datas)
{
return datas.Select(district =
{
var obj = district?.ToDTO();
obj.MSADDRESSSUBDISTRICTs = district.MSADDRESSSUBDISTRICTs?.ToDTO();
return obj;
}).AsQueryable();
}
And the controller just like this.
[EnableQuery(MaxExpansionDepth = 4)]
public IQueryable<MsAddressCountryObject Get()
{
return db.MSADDRESSCOUNTRies.ToDTO()
}
And that makes the performance really bad. I think the extension is making a lot of memory allocation or some thing that make the result not being delivered directly to the client.
My goal is to create the code easy to maintain, and the performance not drop significantly.
I have many relation in other table. I want the $expand works without write all parent/child Select statement manually and one by one.
I have try to not calling ToDTO() from all the extension method. The result is the performance is fast. But I lost all the relation or I need to write the parent/child Select statement for all method.
Any suggestion will help.
Thanks.

"Violation of PRIMARY KEY constraint '...'. Cannot insert duplicate key in object

I just started to make my first project with Codefirst-Approach with C#, Linq and MSSQLSERVER and run into an problem when trying to insert a new DB-entry that contains an reference to an already existing element from another table.
InnerException {"Violation of PRIMARY KEY constraint 'PK_dbo.Manufacturers'. Cannot insert duplicate key in object 'dbo.Manufacturers'. The duplicate key value is (1d262e43-b9b6-4752-9c79-95d955d460ab).\r\nThe statement has been terminated."} System.Exception {System.Data.SqlClient.SqlException}
I broke the problem down to a simple project that I will upload to a share.
My data structure contains a class.Product that links to a Manufacturer object and a List of possible Suppliers.
public class Product
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
public Manufacturer Manuf { get; set; }
public List<Supplier> PossibleSupplier { get { return _possibleSupplier; } set { _possibleSupplier = value; } }
private List<Supplier> _possibleSupplier = new List<Supplier>();
}
public class Supplier
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
}
public class Manufacturer
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
}
I now generate 2 products.
Both products are produced by the same manufacturer.
The List of PossibleSuppliers does also contain same suppliers
private void GenerateProducts()
{
Manufacturer manufactuer1 = new Manufacturer() { Name = "mainManuf 1" };
Supplier supplier1 = new Supplier() { Name = "first Supplier" };
Supplier supplier2 = new Supplier() { Name = "second Supplier" };
Product firstProduct = new Product() { Name = "Product 1", Manuf = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1, supplier2 } };
Product secondProduct = new Product() { Name = "Product 2", Manuf = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1 } };
productList_ = new List<Product>() { firstProduct, secondProduct };
}
The following method is used for storing/updating the DB entries
public static class DbHandler
{
public static bool StoreProduct(Product product)
{
using (ProductDbContext dbObject = new ProductDbContext())
{
try
{
dbObject.Products.AddOrUpdate(product);
dbObject.SaveChanges();
}
catch (Exception ex)
{
//
return false;
}
}
return true;
}
}
public class ProductDbContext : DbContext
{
public ProductDbContext()
{
Database.SetInitializer<ProductDbContext>(new DropCreateDatabaseAlways<ProductDbContext>());
this.Database.Connection.ConnectionString = sqlConnection.ConnectionString;
}
public DbSet<Product> Products { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<Manufacturer> Manufacturers { get; set; }
private static SqlConnectionStringBuilder sqlConnection = new SqlConnectionStringBuilder()
{
DataSource = "localhost\\MSSQLSERVER2019", // update me
UserID = "", // update me
Password = "", // update me
InitialCatalog = "ProductDb",
IntegratedSecurity = true
};
}
The insertion of the first product can be done without problems.
Also inserting additional products that will have unique manufacturers and suppliers will work without problem.
**So I do not have the problem of uniqueness of my primary keys. **
I only receive this error, when I like to add a new entry that has a foreign key to an already existing entry.
Using dbObject.Products.AddOrUpdate(product); instead of dbObject.Products.Add(product); have not solved my problem.
I am also not able to remove the manufacturer entry before adding the second product, because this will violate the foreign key of my first product…
I found a possible solution for manufacturer by adding an additional property for ManufacturerId
public Guid? ManuId { get; set; }
[ForeignKey("ManuId")]
public Manufacturer Manuf { get; set; }
to my data object, but I would not have an idea how to do this with my List PossibleSupplier??
Can someone please push me into the right direction?
!!Many thanks for the fast replays!!
I have updated my DataStructure as following:
public class Product
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
public virtual Manufacturer Manufacturer { get; set; }
public virtual ICollection<Supplier> PossibleSupplier { get; set; }
}
public class Supplier
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
[ForeignKey("Product")]
public Guid ProductId { get; set; }
public virtual Product Product { get; set; }
}
public class Manufacturer
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
[ForeignKey("Product")]
public Guid ProductId { get; set; }
public virtual ICollection<Product> Product { get; set; }
}
But I still get the "Violation of PRIMARY KEY constraint 'PK_dbo.Manufacturers'. Cannot insert duplicate key..." error while trying to insert the second entry.
I have attached how the DB looks in SQL-Server
Okay so I believe I know what your issue is. It lies somewhat with this portion here:
private void GenerateProducts()
{
Manufacturer manufactuer1 = new Manufacturer() { Name = "mainManuf 1" };
Supplier supplier1 = new Supplier() { Name = "first Supplier" };
Supplier supplier2 = new Supplier() { Name = "second Supplier" };
Product firstProduct = new Product() { Name = "Product 1", Manuf = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1, supplier2 } };
Product secondProduct = new Product() { Name = "Product 2", Manuf = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1 } };
productList_ = new List<Product>() { firstProduct, secondProduct };
}
When you assign Manuf = manufacturer1 in both portions below it will work for the first insert because the manufacturer does not exist yet. Now the reason why on the second insert it does not work is because of your code below:
using (ProductDbContext dbObject = new ProductDbContext())
{
try
{
dbObject.Products.AddOrUpdate(product);
dbObject.SaveChanges();
}
catch (Exception ex)
{
//
return false;
}
}
Right now when you go to insert the second product, it will throw the duplicate key exception because you are not referencing the existing entity within your context. You should change it to something like the following:
using (ProductDbContext dbObject = new ProductDbContext())
{
try
{
//Need to check if the manufacturer already exists in the db, if it does
//make sure your project references the EXISTING entity within your context
var check = dbObjec.Manufacturer.Where(x => x.Id == product.Manufacturer.Id).FirstOrDefault();
if (check != null)
product.Manufacturer = check;
dbObject.Products.Add(product);
dbObject.SaveChanges();
}
catch (Exception ex)
{
//
return false;
}
}
If you don't reference the existing manufacturer within the context and then assign it, EF will assume you are trying to add a new one not reference the existing one.
as mentioned in the commands I'd like to share my updated and working project for further use....
MainWindow that generates the test data and executes read/write to DB
public partial class MainWindow : Window
{
private List<Product> productList_;
public MainWindow()
{
GenerateProducts();
InitializeComponent();
}
private void InsertFirst_Click(object sender, RoutedEventArgs e)
{
DbHandler.StoreProduct(productList_[0]);
}
private void InsertSecond_Click(object sender, RoutedEventArgs e)
{
DbHandler.StoreProduct(productList_[1]);
}
private void Read_Click(object sender, RoutedEventArgs e)
{
var productList = DbHandler.GetAllProducts();
}
private void GenerateProducts()
{
Manufacturer manufactuer1 = new Manufacturer() { Name = "mainManuf 1" };
Supplier supplier1 = new Supplier() { Name = "first Supplier" };
Supplier supplier2 = new Supplier() { Name = "second Supplier" };
Supplier supplier3 = new Supplier() { Name = "third Supplier" };
Product firstProduct = new Product() { Name = "Product 1", Manufacturer = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1, supplier2 } };
Product secondProduct = new Product() { Name = "Product 2", Manufacturer = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier2, supplier3 } };
productList_ = new List<Product>() { firstProduct, secondProduct };
}
}
DataStructure: Because of the many to many relation ship between Product and Supplier I have to add
[ForeignKey("Product")]
public ICollection<Guid> ProductId { get; set; }
public virtual ICollection<Product> Product { get; set; }
to the Supplier class. I also decided to add a collection of Products to my manufacturer to make some query-calls more comfortable
public class Product
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
public virtual Manufacturer Manufacturer { get; set; }
public virtual ICollection<Supplier> PossibleSupplier { get; set; }
}
public class Supplier
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
[ForeignKey("Product")]
public ICollection<Guid> ProductId { get; set; }
public virtual ICollection<Product> Product { get; set; }
}
public class Manufacturer
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
//only nice for reverse object from Man --> Product
[ForeignKey("Product")]
public ICollection<Guid> ProductId { get; set; }
public virtual ICollection<Product> Product { get; set; }
}
Before adding a new product to the DB it is important to load possible Manufacturer/Suppliers from the DB and assign them to the current product.
Adding new products is now working fine but as you can see the loading and assigning of the possible suppliers is not really handsome. Therefore I will try to make some modifications on this process in the upcoming days.... I will come back if I have found a "solution".
public static class DbHandler
{
public static List<Product> GetAllProducts()
{
using (ProductDbContext dbObject = new ProductDbContext())
{
//loading with childs and their reverse objects to products
var productList = dbObject.Products.Include("Manufacturer").Include("Manufacturer.Product").Include("PossibleSupplier").Include("PossibleSupplier.Product").Where(i => i.Id != null).ToList();
//loding with childs but without reverse objects
//var productList = dbObject.Products.Include("Manufacturer").Include("PossibleSupplier").Where(i => i.Id != null).ToList();
return productList;
}
}
public static bool StoreProduct(Product product)
{
using (ProductDbContext dbObject = new ProductDbContext())
{
try
{
//this does not solve the loading problem, even when property _id is changed to "private Guid _id = new Guid();
//dbObject.Entry(product).State = product.Id == new Guid() ? EntityState.Added : EntityState.Modified;
//dbObject.Entry(product.Manufacturer).State = product.Manufacturer.Id == new Guid() ? EntityState.Added : EntityState.Modified;
//foreach (var supplier in product.PossibleSupplier)
//{
// dbObject.Entry(supplier).State = supplier.Id == new Guid() ? EntityState.Added : EntityState.Modified;
//}
//Therefore loading must be done manually
Guid manufacturerId = product.Manufacturer.Id;
//Need to check if the manufacturer already exists in the db, if it does
//make sure your project references the EXISTING entity within your context
var checkManuf = dbObject.Manufacturers.Where(x => x.Id == manufacturerId).FirstOrDefault();
if (checkManuf != null)
product.Manufacturer = checkManuf;
List<Supplier> dbSuppliers = new List<Supplier>();
foreach (var posSupplier in product.PossibleSupplier)
{
var checkSupplier = dbObject.Suppliers.FirstOrDefault(x => x.Id == posSupplier.Id);
if (checkSupplier != null)
{
dbSuppliers.Add(checkSupplier);
}
}
foreach (var dbSup in dbSuppliers)
{
product.PossibleSupplier.Remove(product.PossibleSupplier.Single(i => i.Id == dbSup.Id));
product.PossibleSupplier.Add(dbSup);
}
dbObject.Products.Add(product);
dbObject.SaveChanges();
}
catch (Exception ex)
{
//
return false;
}
}
return true;
}
}
public class ProductDbContext : DbContext
{
public ProductDbContext()
{
Database.SetInitializer<ProductDbContext>(new DropCreateDatabaseIfModelChanges<ProductDbContext>());
this.Database.Connection.ConnectionString = sqlConnection.ConnectionString;
}
public DbSet<Product> Products { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<Manufacturer> Manufacturers { get; set; }
private static SqlConnectionStringBuilder sqlConnection = new SqlConnectionStringBuilder()
{
DataSource = "localhost\\MSSQLSERVER2019", // update me
UserID = "", // update me
Password = "", // update me
InitialCatalog = "ProductDb",
IntegratedSecurity = true
};
}
One way seams by using EntityStates but they do not work as expected --> I received duplicated entries in DB.
I have uploaded the current state of the project to the share - filename SqlTestporject_20200414_2027.zip
Br,
-------------------------- Update 2020-04-15 --------------------------
I ended up by writing methods that are handling the decision between update/insert for every single child on its own because I do not have found a way how to update possible offline-changes of already existing dbEntries by simultaneously adding not existing dbEntries.
The main problem was that I received duplicated entries in the DB during adding the second product. The strange thing was, that this duplicates event violates the PK-uniqueness without an error/exception....
So I do have to call down the AddOrUpdate() methods until I reach the last child for my complete data structure.
public static Product AddOrUpdateProduct(Product product)
{
using (ProductDbContext dbObject = new ProductDbContext())
{
try
{
product.Manufacturer = AddOrUpdateManufacturer(dbObject, product.Manufacturer);
List<Supplier> dbSupplierList = new List<Supplier>();
foreach (var supplier in product.PossibleSupplier)
{
dbSupplierList.Add(AddOrUpdateSupplier(dbObject, supplier));
}
product.PossibleSupplier.Clear();
product.PossibleSupplier = dbSupplierList;
if (product.Id == new Guid())
{
//add new product
dbObject.Products.Add(product);
dbObject.SaveChanges();
return product;
}
else
{
//update existing product
var dbProduct = dbObject.Products.Single(x => x.Id == product.Id);
dbProduct.Name = product.Name;
dbObject.SaveChanges();
return dbProduct;
}
}
catch (Exception)
{
throw;
}
}
}
private static Supplier AddOrUpdateSupplier(ProductDbContext dbObject, Supplier supplier)
{
supplier.Address = AddOrUpdateAdress(dbObject, supplier.Address);
if (supplier.Id == new Guid())
{
//add new product
dbObject.Suppliers.Add(supplier);
dbObject.SaveChanges();
return supplier;
}
else
{
//update existing product
var dbSupplier = dbObject.Suppliers.Single(x => x.Id == supplier.Id);
dbSupplier.Name = supplier.Name;
dbObject.SaveChanges();
return dbSupplier;
}
}
private static Manufacturer AddOrUpdateManufacturer(ProductDbContext dbObject, Manufacturer manufacturer)
{
manufacturer.Address = AddOrUpdateAdress(dbObject, manufacturer.Address);
if (manufacturer.Id == new Guid())
{
//add new product
dbObject.Manufacturers.Add(manufacturer);
dbObject.SaveChanges();
return manufacturer;
}
else
{
//update existing product
var dbManufacturer = dbObject.Manufacturers.Single(x => x.Id == manufacturer.Id);
dbManufacturer.Name = manufacturer.Name;
dbObject.SaveChanges();
return dbManufacturer;
}
}
private static Address AddOrUpdateAdress(ProductDbContext dbObject, Address address)
{
if (address.Id == new Guid())
{
//add new product
dbObject.Addresses.Add(address);
dbObject.SaveChanges();
return address;
}
else
{
//update existing product
var dbAddress = dbObject.Addresses.Single(x => x.Id == address.Id);
dbAddress.Street = address.Street;
dbAddress.HouseNumber = address.HouseNumber;
dbAddress.PLZ = address.PLZ;
dbAddress.City = address.City;
dbObject.SaveChanges();
return dbAddress;
}
}
This version can be found here - file SqlTestporject_20200415_1033.zip.
Additionally I'd like to share the following link. Maybe chapter Example 4.18: Creating a Generic Method That Can Apply State Through Any Graph can help others to implement a more comfortable solution.

Behavior of relations in EF Core, concurrency of when objects are created

I've come across a problem with Entity framework Core 2.0.2 and I wonder if someone can enlighten me.
What I have is a person class with a collection of phone numbers.
Person
public class Person : BaseEntity
{
[Required]
public string FirstName { get; set; }
public string MiddleName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Context { get; set; }
public string Email { get; set; }
public ICollection<PhoneNumber> PhoneNumbers { get; set; } = new List<PhoneNumber>();
}
PhoneNumber
public class PhoneNumber : BaseEntity
{
public string Usage { get; set; }
public int PersonId { get; set; }
public Person Person { get; set; }
}
BaseEntity is basically just a property with an id.
Then I have a generic repo
Repository
public class Repository<T> where T : BaseEntity
{
private readonly Dab2_2RdbContext _context;
public Repository(Dab2_2RdbContext context)
{
_context = context;
}
public void Create(T t)
{
_context.Entry<T>(t).State = EntityState.Added;
_context.SaveChanges();
}
public T Read(int id)
{
return _context.Find<T>(id);
}
public void Update(int id, T t)
{
_context.Entry<T>(t).State = EntityState.Modified;
_context.SaveChanges();
}
public void Delete(T t)
{
_context.Entry<T>(t).State = EntityState.Deleted;
_context.SaveChanges();
}
}
All of this code is very simple and works fine.
The situation happens when I test the code like this:
var context = new Dab2_2RdbContext();
var personRepo = new Repository<Person>(context);
var phoneNumberRepo = new Repository<PhoneNumber>(context);
var person = new Person()
{
FirstName = "Kasper",
LastName = "Lastname",
Email = "Something#gmail.com",
Context = "Myself",
};
person.PhoneNumbers = new List<PhoneNumber>()
{
new PhoneNumber() {Usage = "Work"},
new PhoneNumber() {Usage = "School"}
};
// Create
personRepo.Create(person);
This code generates a person with proper values, however, it doesn't include the Phone numbers.
var context = new Dab2_2RdbContext();
var personRepo = new Repository<Person>(context);
var phoneNumberRepo = new Repository<PhoneNumber>(context);
var person = new Person()
{
FirstName = "Kasper",
LastName = "Lastname",
Email = "Something#gmail.com",
Context = "Myself",
};
// Create
personRepo.Create(person);
person.PhoneNumbers = new List<PhoneNumber>()
{
new PhoneNumber() {Usage = "Work"},
new PhoneNumber() {Usage = "School"}
};
personRepo.Update(person.Id, person);
This code makes the proper relationship (the person.The phonenumber relation has been made. Notice the // Create person, shifted above person.Phone number
I wonder if someone can enlighten me, I really can figure out why this is.
The answer is that setting the state of the entry. Will only track the entity, and none of it's properties. While add will track all the navigation properties.

Violation of PRIMARY KEY constraint with Entity Framework 6 , Model one-to-many

I have 2 models with one to many relation, if I save in different instance of DbContext, this throw an exception (Violation of Primary Key) - how to avoid it?
public class Customer
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
public Customer Customer { get; set; }
}
public class DbContext : System.Data.Entity.DbContext
{
public DbContext()
: base(#"Data Source=(localdb)\MSSQLLocalDB; AttachDBFilename='|DataDirectory|\Sample.mdf'; Integrated Security=True")
{
Configuration.ProxyCreationEnabled = false;
Configuration.AutoDetectChangesEnabled = true;
Configuration.ValidateOnSaveEnabled = true;
Configuration.LazyLoadingEnabled = true;
}
public IDbSet<Customer> Customers { get; set; }
public IDbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<Guid>()
.Where(p => p.Name == "Id")
.Configure(p => { p.IsKey(); p.IsRequired(); });
}
}
AppDomain.CurrentDomain.SetData(
"DataDirectory",
System.Environment.CurrentDirectory);
var customer = new Customer();
customer.Id = Guid.NewGuid();
customer.Name = "customername";
using (var db = new DbContext())
{
db.Customers.Add(customer);
db.SaveChanges();
}
var user = new User();
user.Id = Guid.NewGuid();
user.Name = "username";
user.Customer = customer;
using (var db = new DbContext())
{
db.Users.Add(user);
db.SaveChanges(); // <- Throw here
}
of course this is a simplified sample, in what is written it is possible to use only one instance of DbContext, but in reality the customer is passed as a parameter to a method
You are correct - the second instance won't know you just added the customer. Either wrap them in the same using statement or you can tell the second instance the customer already exists:
var user = new User();
user.Id = Guid.NewGuid();
user.Name = "username";
using (var db = new DbContext())
{
user.Customer = new Customer() { Id = customer.Id }; // only need the id
db.Customers.Attach(user.Customer);
db.Users.Add(user);
db.SaveChanges();
}
Entity Framework: adding existing child POCO to new Parent POCO, creates new child in DB

Entity Framework adding item to ICollection error

I have two simple classes, User and Task:
class User
{
public int UserId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
class Task
{
public int TaskId { get; set; }
public string Name { get; set; }
public DateTime CreatedAt { get; set; }
public virtual ICollection<User> Followers { get; set; }
}
The Task class has a property Followers which is an ICollection<User>
Here is the db context class:
class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Task> Tasks { get; set; }
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<User>().HasKey(u => u.UserId);
mb.Entity<Task>().HasKey(t => t.TaskId);
}
}
and here is the code in the Main program:
var db = new MyContext();
var user = new User();
user.Name = "John Doe";
user.Email = "jd#email.com";
db.Users.Add(user);
db.SaveChanges();
var follower = db.Users.Where(u => u.Name == "John Doe").FirstOrDefault();
var task = new Task();
task.Name = "Make the tea";
task.CreatedAt = DateTime.Now;
task.Followers.Add(follower); // ERROR
db.Tasks.Add(task);
db.SaveChanges();
The trouble is I am getting an error when trying to add the follower to the task.
Object reference not set to an instance of an object.
What am I doing wrong?
The problem is that the Followers collection is null. Instead of newing up your classes, let EF create them for you ...
var user = db.Users.Create();
and
var task = db.Tasks.Create();
If you're still getting problems then your proxies are not being created. You can initialise the collections in the class constructors, make each of them a HashSet<T>. It would be better though to identify why the proxies are not getting generated ...
public class Task
{
public Task()
{
Followers = new HashSet<User>();
}
public int TaskId { get; set; }
public string Name { get; set; }
public DateTime CreatedAt { get; set; }
public virtual ICollection<User> Followers { get; set; }
}
try this. just initialize Follower
var db = new MyContext();
var user = new User();
user.Name = "John Doe";
user.Email = "jd#email.com";
db.Users.Add(user);
db.SaveChanges();
var follower = db.Users.Where(u => u.Name == "John Doe").FirstOrDefault();
var task = new Task();
task.Name = "Make the tea";
task.CreatedAt = DateTime.Now;
task.Followers = new Collection<User>()
task.Followers.Add(follower);
db.Tasks.Add(task);
db.SaveChanges();
Try this. You will have to chan ge the constructor as mentioned by qujck
var db = new MyContext();
var user = new User();
user.Name = "John Doe";
user.Email = "jd#email.com";
var task = new Task();
task.Name = "Make the tea";
task.CreatedAt = DateTime.Now;
task.Followers.Add(user);
db.Tasks.Add(task);
db.SaveChanges();
You can initialize the List, because ICollection is an interface then it can't be initialized, but List can be (the following worked for me)
Instead of:
task.Followers.Add(follower);
Write:
task.Followers= new List<User>();
task.Followers.Add(follower);
This should solve your problem :)

Categories

Resources