Insert entity with one related entity which existed in the database - c#

I am stuck at the operation when using Entity Framework Core 2 to perform an insert of a new entity with one related entity which already existed in the database. For example, I have two objects with one to many relationship:
public Class OrderDetail
{
public int ParentID {get;set;}
public string OrderDetailName {get;set;}
public int ProductID {get;set;}
public virtual Product ProductFK {get;set;}
}
public Class Product
{
public int ProductID {get;set;}
public string ProductName {get;set;}
public virtual Collection<OrderDetail> OrderDetails {get;set;} = new Collection<OrderDetail>();
}
I would like to add a new OrderDetail with an existing Product (with productID = 3) into the database, so I perform like:
private void AddOrderDetail(int productID)
{
OrderDetail newOrderDetail = new OrderDetail();
newOrderDetail.Name = "New OrderDetail";
// Here I have no clue what would be the correct way...Should I do
// Approach A(pick related entity from database then assign as related entity to base entity):
var ProductFromDB = DbContext.Products.SingleOrDefault(p => p.ProductID == productID);
newOrderDetail.ProductFK = ProductFromDB;
// Approach B(directly assign the foreign key value to base entity)
newOrderDetail.ProductID = productID
DbContext.SaveChange();
}
By using approach (A), my newOrderDetail failed to save and I looked into SQL resource, looks like it considered the one that I retrieved from the database (ProductFromDB) as a new object and was trying to insert it again. I feel it's redundant job for picking ProductFromDB first then assign to the inserted entity...
By using approach (B), my newOrderDetail still failed to save, and I am getting an exception like "insert A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.", however, this exception does not happen constantly. I looked into the SQL and found the SQL Script, by running it individually in SQL Server it worked, however when running an application side, it's not working...
So what would be the correct way to deal with above scenario?

If you don't need access to the complete Product object right away, you could try to set just the foreign key column value of your newOrderDetail. Also, you need to ADD your newly created OrderDetail object to the DbContext before saving - something like this:
private void AddOrderDetail(int productID)
{
OrderDetail newOrderDetail = new OrderDetail();
newOrderDetail.Name = "New OrderDetail";
newOrderDetail.ProductID = productID
// OF COURSE, you need to ADD the newly created object to the context!
DbContext.OrderDetails.Add(newOrderDetail);
DbContext.SaveChange();
}
Next time around, when you actually fetch the OrderDetail, it will resolve the linked product and you should be fine.:
using (var DbContext = new YourDbContext())
{
OrderDetail od = DbContext.OrderDetails.FirstOrDefault(x => x.Name = "New OrderDetail");
....
}

Related

SQL query to multiple objects using Entity Framework 6

I'm taking the time to learn EF (specifically version 6).
I created two tables in MSSQL and linked up EF6 to the database creating the EF model framework.
Then I created the classes in code. My desire is to have one row pulled with a list of items for "UserDatas" (yes I know it's misspelled).
Consider this code:
public class user
{
[Key]
public int pkID;
public string ForeignCode;
public string UserName;
public virtual List<UserData> UserDatas { get; set; }
}
public class UserData
{
[Key]
public int pkID;
public int fkUserID;
public string ColumnName;
public string ColumnValue;
}
class Program
{
static TestData db = new TestData();
static void Main(string[] args)
{
var record = db.tblUsers.Select(x => new { x.UserName, x.pkID }).FirstOrDefault();
var record2 = db.tblUsers.Include(x => x.tblUserDatas).ToList();
Console.ReadLine();
}
}
The first query is just a test to pull the primary record in the tblUsers table.
The second query is where I am attempting to pull all fields related to that user which include things like first name, last name, address, etc...
What happens when I set a break point on the Console.Readline(), I see 5 rows listed for record2. The "user" class is duplicated in each of those rows. I was expecting to see that only listed once with a list of items for "UserDatas".
How can I get this query to come through as I expect with one row containing a list of "UserDatas"?
Again, this is only for learning purposes so don't worry about data and if this is the best way to store it.
It should be as simple as the following (if you don't need the projection/anonymous object) and assuming your entities are configured correctly
var user = db.tblUsers
.Include(x => x.UserDatas) // include user data
.FirstOrDefault(); // select the first user
Some notes,
There is no need to prefix tables with tbl
There is no need to prefix fields with pk, fk
If you used Id, you don't need to specify [key]

How do I make Entity Framework 6 (DB First) explicitly insert a Guid/UniqueIdentifier primary key?

I am using Entity Framework 6 DB First with SQL Server tables that each have a uniqueidentifier primary key. The tables have a default on the primary key column that sets it to newid(). I have accordingly updated my .edmx to set the StoreGeneratedPattern for these columns to Identity. So I can create new records, add them to my database context and the IDs are generated automatically. But now I need to save a new record with a specific ID. I've read this article which says you have to execute SET IDENTITY_INSERT dbo.[TableName] ON before saving when using an int identity PK column. Since mine are Guid and not actually an identity column, that's essentially already done. Yet even though in my C# I set the ID to the correct Guid, that value is not even passed as a parameter to the generated SQL insert and a new ID is generated by the SQL Server for the primary key.
I need to be able to both :
insert a new record and let the ID be automatically created for it,
insert a new record with a specified ID.
I have # 1. How can I insert a new record with a specific primary key?
Edit:
Save code excerpt (Note accountMemberSpec.ID is the specific Guid value I want to be the AccountMember's primary key):
IDbContextScopeFactory dbContextFactory = new DbContextScopeFactory();
using (var dbContextScope = dbContextFactory.Create())
{
//Save the Account
dbAccountMember = CRMEntity<AccountMember>.GetOrCreate(accountMemberSpec.ID);
dbAccountMember.fk_AccountID = accountMemberSpec.AccountID;
dbAccountMember.fk_PersonID = accountMemberSpec.PersonID;
dbContextScope.SaveChanges();
}
--
public class CRMEntity<T> where T : CrmEntityBase, IGuid
{
public static T GetOrCreate(Guid id)
{
T entity;
CRMEntityAccess<T> entities = new CRMEntityAccess<T>();
//Get or create the address
entity = (id == Guid.Empty) ? null : entities.GetSingle(id, null);
if (entity == null)
{
entity = Activator.CreateInstance<T>();
entity.ID = id;
entity = new CRMEntityAccess<T>().AddNew(entity);
}
return entity;
}
}
--
public class CRMEntityAccess<T> where T : class, ICrmEntity, IGuid
{
public virtual T AddNew(T newEntity)
{
return DBContext.Set<T>().Add(newEntity);
}
}
And here is the logged, generated SQL for this:
DECLARE #generated_keys table([pk_AccountMemberID] uniqueidentifier)
INSERT[dbo].[AccountMembers]
([fk_PersonID], [fk_AccountID], [fk_FacilityID])
OUTPUT inserted.[pk_AccountMemberID] INTO #generated_keys
VALUES(#0, #1, #2)
SELECT t.[pk_AccountMemberID], t.[CreatedDate], t.[LastModifiedDate]
FROM #generated_keys AS g JOIN [dbo].[AccountMembers] AS t ON g.[pk_AccountMemberID] = t.[pk_AccountMemberID]
WHERE ##ROWCOUNT > 0
-- #0: '731e680c-1fd6-42d7-9fb3-ff5d36ab80d0' (Type = Guid)
-- #1: 'f6626a39-5de0-48e2-a82a-3cc31c59d4b9' (Type = Guid)
-- #2: '127527c0-42a6-40ee-aebd-88355f7ffa05' (Type = Guid)
A solution could be to override DbContext SaveChanges. In this function find all added entries of the DbSets of which you want to specify the Id.
If the Id is not specified yet, specify one, if it is already specified: use the specified one.
Override all SaveChanges:
public override void SaveChanges()
{
GenerateIds();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync()
{
GenerateIds();
return await base.SaveChangesAsync();
}
public override async Task<int> SaveChangesAsync(System.Threading CancellationToken token)
{
GenerateIds();
return await base.SaveChangesAsync(token);
}
GenerateIds should check if you already provided an Id for your added entries or not. If not, provide one.
I'm not sure if all DbSets should have the requested feature, or only some. To check whether the primary key is already filled, I need to know the identifier of the primary key.
I see in your class CRMEntity that you know that every T has an Id, this is because this Id is in CRMEntityBase, or in IGuid, let's assume it is in IGuid. If it is in CRMEntityBase change the following accordingly.
The following is in small steps; if desired you can create one big LINQ.
private void GenerateIds()
{
// fetch all added entries that have IGuid
IEnumerable<IGuid> addedIGuidEntries = this.ChangeTracker.Entries()
.Where(entry => entry.State == EntityState.Added)
.OfType<IGuid>()
// if IGuid.Id is default: generate a new Id, otherwise leave it
foreach (IGuid entry in addedIGuidEntries)
{
if (entry.Id == default(Guid)
// no value provided yet: provide it now
entry.Id = GenerateGuidId() // TODO: implement function
// else: Id already provided; use this Id.
}
}
That is all. Because all your IGuid objects now have a non-default ID (either pre-defined, or generated inside GenerateId) EF will use that Id.
Addition: HasDatabaseGeneratedOption
As xr280xr pointed out in one of the comments, I forgot that you have to tell entity framework that entity framework should not (always) generate an Id.
As an example I do the same with a simple database with Blogs and Posts. A one-to-many relation between Blogs and Posts. To show that the idea does not depend on GUID, the primary key is a long.
// If an entity class is derived from ISelfGeneratedId,
// entity framework should not generate Ids
interface ISelfGeneratedId
{
public long Id {get; set;}
}
class Blog : ISelfGeneratedId
{
public long Id {get; set;} // Primary key
// a Blog has zero or more Posts:
public virtual ICollection><Post> Posts {get; set;}
public string Author {get; set;}
...
}
class Post : ISelfGeneratedId
{
public long Id {get; set;} // Primary Key
// every Post belongs to one Blog:
public long BlogId {get; set;}
public virtual Blog Blog {get; set;}
public string Title {get; set;}
...
}
Now the interesting part: The fluent API that informs Entity Framework that the values for primary keys are already generated.
I prefer fluent API avobe the use of attributes, because the use of fluent API allows me to re-use the entity classes in different database models, simply by rewriting Dbcontext.OnModelCreating.
For example, in some databases I like my DateTime objects a DateTime2, and in some I need them to be simple DateTime. Sometimes I want self generated Ids, sometimes (like in unit tests) I don't need that.
class MyDbContext : Dbcontext
{
public DbSet<Blog> Blogs {get; set;}
public DbSet<Post> Posts {get; set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Entity framework should not generate Id for Blogs:
modelBuilder.Entity<Blog>()
.Property(blog => blog.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
// Entity framework should not generate Id for Posts:
modelBuilder.Entity<Blog>()
.Property(blog => blog.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
... // other fluent API
}
SaveChanges is similar as I wrote above. GenerateIds is slightly different. In this example I have not the problem that sometimes the Id is already filled. Every added element that implements ISelfGeneratedId should generate an Id
private void GenerateIds()
{
// fetch all added entries that implement ISelfGeneratedId
var addedIdEntries = this.ChangeTracker.Entries()
.Where(entry => entry.State == EntityState.Added)
.OfType<ISelfGeneratedId>()
foreach (ISelfGeneratedId entry in addedIdEntries)
{
entry.Id = this.GenerateId() ;// TODO: implement function
// now you see why I need the interface:
// I need to know the primary key
}
}
For those who are looking for a neat Id generator: I often use the same generator as Twitter uses, one that can handle several servers, without the problem that everyone can guess from the primary key how many items are added.
It's in Nuget IdGen package
I see 2 challenges:
Making your Id field an identity with auto generated value will prevent you from specifying your own GUID.
Removing the auto generated option may create duplicate key exceptions if the user forgets to explicitly create a new id.
Simplest solution:
Remove auto generated value
Ensure Id is a PK and is required
Generate a new Guid for your Id in the default constructor of your models.
Example Model
public class Person
{
public Person()
{
this.Id = Guid.NewGuid();
}
public Guid Id { get; set; }
}
Usage
// "Auto id"
var person1 = new Person();
// Manual
var person2 = new Person
{
Id = new Guid("5d7aead1-e8de-4099-a035-4d17abb794b7")
}
This will satisfy both of your needs while keeping the db safe. The only down side of this is you have to do this for all models.
If you go with this approach, I'd rather see a factory method on the model which will give me the object with default values (Id populated) and eliminate the default constructor. IMHO, hiding default value setters in the default constructor is never a good thing. I'd rather have my factory method do that for me and know that the new object is populated with default values (with intention).
public class Person
{
public Guid Id { get; set; }
public static Person Create()
{
return new Person { Id = Guid.NewGuid() };
}
}
Usage
// New person with default values (new Id)
var person1 = Person.Create();
// Empty Guid Id
var person2 = new Person();
// Manually populated Id
var person3 = new Person { Id = Guid.NewGuid() };
I don't think there is a real answer for this one...
As said here How can I force entity framework to insert identity columns? you can enable the mode #2, but it'll break #1.
using (var dataContext = new DataModelContainer())
using (var transaction = dataContext.Database.BeginTransaction())
{
var user = new User()
{
ID = id,
Name = "John"
};
dataContext.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] ON");
dataContext.User.Add(user);
dataContext.SaveChanges();
dataContext.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] OFF");
transaction.Commit();
}
you should change value of StoreGeneratedPattern property of identity column from Identity to None in model designer.
Note, changing of StoreGeneratedPattern to None will fail inserting of object without specified id
As you can see, you're no longer able to insert without setting by yourself an ID.
But, if you look on the bright side : Guid.NewGuid() will allow you to make a new GUID without the DB generation function.
The solution is: write your own insert query. I've put together a quick project to test this, so the example has nothing to do with your domain, but you'll ge the ideea.
using (var ctx = new Model())
{
var ent = new MyEntity
{
Id = Guid.Empty,
Name = "Test"
};
try
{
var result = ctx.Database.ExecuteSqlCommand("INSERT INTO MyEntities (Id, Name) VALUES ( #p0, #p1 )", ent.Id, ent.Name);
}
catch (SqlException e)
{
Console.WriteLine("id already exists");
}
}
The ExecuteSqlCommand returns "rows affected" (in this case 1), or throws exception for duplicate key.

Entity Framework, assign an existing db-entity to another, without create a new one

Each time that I want to assign a db-entity to another, I'm creating a new one.
Working with Entity Framework, code first, creating a restfull webapi service disconnected from Angular UI. For each db entity, I also have a Data obj to send result-data to UI.
Lets consider next example
DB ENTITIES:
Client()
{
int Id {get;set;}
string Name {get;set;}
CountriesDropDown Contry { get; set; }
}
CountriesDropDown()
{
int Id {get;set;}
string Description {get;set;}
}
CountriesDropDown is a fixed list at DB, I dont want to create new rows or edit them, I just want to assign them to a Client.
IS THERE ANY WAY TO SET THIS DROPDOWN TABLE AT ENTITY FRAMEWORK AS ALWAYS EntityState.Unchanged??
THEN:
SAVE()
{
.....
.....
if(Client.Contry != null)
{
Client.Contry.Id = ClientData.Contry.Id;
}
else
{ //INSTEAD OF ASSIGN AN EXISTING COUNTRY IT CREATES A DUPLICATE ONE
Client.Contry = new CountriesDropDown();
Client.Contry.Id = ClientData.Contry.Id;
Client.Contry.Description = ClientData.Contry.Description;
}
}
There are two ways round this.
First in your Client class have a CountryId field and link it to your Country object via a ForeignKeyAttribute
Then just set the Id attribute
Client.CountryId = clientData.CountryId
In querying the data you can still access the navigation property
Secondly Retrieve the existing entity via the db context and attach it
Client.Country = dbContext.Countries.Find(clientData.CountryId)

EF update the child relation of a relation

EF tracking confuses me. Here is a scenario that I am trying to achieve:
public class CentralPoint
{
public Guid ID { get; set; }
public virtual BIDatabase BIDatabase { get; set; }
}
public class BIDatabase
{
public Guid ID { get; set; }
public Guid CentralPointID { get; set; }
public virtual CentralPoint CentralPoint { get; set; }
public Guid ConnectionID { get; set; }
public virtual Connection Connection { get; set; }
}
public class Connection
{
public Guid ID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
These are my entity models. I am having a one-one relationship between CentralPoint and BIDatabase entities, also a one-one relationship between BIDatabase and Connection entities. Obviously there is a separate table for each of these entities.
Inside the controller I am updating the BIDatabase property of the CentralPoint with a new instance
centralPoint.BIDatabase = biDatabase;
Here is the controller part:
public async Task<IActionResult> AddBIDatabaseAsync(Guid cpId, BIDatabase biDatabase)
{
// context is available through Dependency Injection (.net core)
var centralPoint = _context.CentralPoint.Where(cp => cp.ID == cpId)
.Include(cp => cp.BIDatabase)
.ThenInclude(biDb => biDb.Connection)
.FirstOrDefault();
centralPoint.BIDatabase = biDatabase;
await _context.SaveChangesAsync();
// more code ...
}
After this:
CentralPoint Table -> Remains Unchanged: Normal
BIDatabase Table -> Updated with new IDs: Normal
Connection Table -> // A new row is added instead of updating the old one
What I wanted is the connection entity to be updated and not added every time for the same database.
I think I see the issue.
When you replace centralPoint.BIDatabase = biDatabase; your new BIDatabase object probably doesnt have the Id linking it to Connection
Try mapping the fields you need onto the existing object instead of replacing it (or replace it, but map any Ids and existing fields that you want to be persisted through onto the new object first).
What code created this new biDatabase? Was it persisted to a dbContext prior to this method?
Your expectation around the BIDatabase and connection table is a bit suspicious. Given an existing CentralPoint /w BIDatabase /w Connection, if you "change" the CentralPoint's BIDatabase to a new one, the existing record is not updated, it is replaced. This means that the old record and it's associated connection would be removed, and a new BIDatabase /w a new Connection would be inserted. Assuming these entities are configured with DB generated keys:
Example:
CentralPoint(id:1) -> BIDatabase(id:1) -> Connection(id:1)
Create new BIDatabase (id:x) with new Connection (id:x) then:
CentralPoint.BIDatabase = newBIDatabase
BIDatabase(id:1) & Connection(id:1) are marked for deletion.
new BIDatabase & Connection will be persisted with new IDs when saved.
CentralPoint(id:1) -> BIDatabase(id:2) -> Connection(id:2)
If you want to replace the BIDatabase but keep the same connection ID/reference:
Create the new BIDatabase(id:x) /w Connection(id:x)
but before saving it to the context:
var centralPoint = _context.CentralPoint.Where(cp => cp.ID == cpId)
.Include(cp => cp.BIDatabase)
.ThenInclude(biDb => biDb.Connection)
.FirstOrDefault();
biDatabase.Connection = (centralPoint?.BIDatabase?.Connection) ?? biDatabase.Connection;
centralPoint.BIDatabase = biDatabase;
await _context.SaveChangesAsync();
What that extra statement does is look to preserve an existing connection already on the existing CentralPoint.BIDatabase by copying the reference over to the new BIDatabase. If the existing BIDatabase didn't have a connection (or the existing CP didn't have a BIDatabase) then the new connection created would be used. Provided you haven't saved the new BIDatabase to a dbContext prior to this code, the "new" Connection (id:x) will never get inserted where an existing one is substituted.
There were a couple of mistakes I was doing. I am detailing them so it might be helpful to others.
First mistake (pointed out by #Steveland83)
centralPoint.BIDatabase = biDatabase;
Since the CentralPoint entity is being tracked by the context (DbContext) we cannot simply replace a property by another. We need to modify the property by copying values.
So the following should be done instead
_context.Entry(centralPoint.BIDatabase).CurrentValues.SetVal‌​ues(biDatabase);
Second mistake
With the fix above, I was expecting the context to track all property changes in the centralPoint.BIDatabase and update those as well by itself (In my case the Connection property eg. centralPoint.BIDatabase.Connection)
However unfortunately this does not happen for non primitive types. You have to tell the context explicitly which of the non-primitive properties changed.
Here is the second fix
_context.Entry(centralPoint.BIDatabase).CurrentValues.SetVal‌​ues(biDatabase);
_context.Entry(centralPoint.BIDatabase.Connection).CurrentValues.SetVal‌​ues(biDatabase.Connection);
This Updates the BIDatabase and the Connection tables with the changes.

Foreign keys in Entity Framework not updating correctly

I am having trouble with foreign key relationships in the entity framework. I have two tables: Persons and Countries. Persons has a foreign key column to CountryId.
As the Countries table rarely changes, I want to fetch its data only once, dispose the DatabaseContext, and keep the list of Countries cached somewhere. This is where I am running into problems.
The entity framework seems to want you to open a database context, add/edit rows as needed, then close the database context. If you open, fetch data, close; and then later open, save data, close; it has trouble.
So my POCO objects look like this:
public class Country {
public int CountryId {get; set; }
public String Name {get; set; }
}
public Person {
public int PersonId {get; set; }
public virtual Country Country {get; set; }
}
Then, I try to create a new person like this:
Country[] countries;
using (var dt = new DatabaseContext())
{
countries= dt.Countries.ToArray();
}
Person person = new Person();
person.Country = countries[0];
using (var dt = new DatabaseContext()) {
dt.Entities.Add(person);
dt.SaveChanges();
}
On save, the entity framework creates a new row in the Countries table with the same name as countries[0], but a new, incremented ID. This is obviously not the desired outcome - the person should have its Country_CountryId field set to the id of countries[0], and a new row should not be created.
How do I solve this? I think one solution would be to force the entity framework to not create a new row when it is given an object that already has its primary key set. Is there a way to do this?
I wonder if you at least search little bit on Internet before you put quite big effort to describe the issue because this is very common problem asked every few days.
Add method adds all entities in the entity graph. So if you connect country to person and country is not attached to current context, calling Add on the person will mark both person and country as new entities for insertion. If you don't want country to be inserted you must tell EF that country is not a new entity:
Person person = new Person();
person.Country = countries[0];
using (var dt = new DatabaseContext()) {
dt.Entities.Add(person);
dt.Entry(person.Country).State = EntityState.Modified;
dt.SaveChanges();
}
I have solved this problem by adding the following two methods to my DatabaseContext class:
public void Add(object target)
{
this.Set(target.GetType()).Attach(target);
this.Entry(target).State = System.Data.EntityState.Added;
}
public void Modify(object target)
{
this.Set(target.GetType()).Attach(target);
this.Entry(target).State = System.Data.EntityState.Modified;
}

Categories

Resources