How to create and store a (self-tracking) entity object on the server side? - entity-framework

I am trying to achieve the following using Entity framework 4.0 and self-tracking entities:
1) The client application request a book form the server by providing an ISBN number
2) The server performs a query on its database to see if the book is already present
3a) If the book is in the database, it returns it.
3b) If the book is not in the database, it will query Amazon for info, extract the required attributes, create a new book, store it in the database, and return it to the client
Now, 3b) is where the problems are... I can't find any information on how I can create an entity object (a book) on the server side, add it to the context and store it in the database. I have tried all sorts of things:
public class BookBrowserService : IBookBrowserService {
public Book GetBook(string ISBN) {
using (var ctx = new BookBrowserModelContainer()) {
Book book = ctx.Books.Where(b => b.ISBN == ISBN).SingleOrDefault();
if (book == null) {
book = new Book();
book.ISBN = ISBN; // This is the key
book.Title = "This title would be retrieved from Amazon";
Author author = new Author();
author.Name = "The author's name would be retrieved from Amazon";
book.Authors.Add(author);
ctx.Books.AddObject(book);
ctx.SaveChanges(); // This one always throws an exception...
}
return book;
}
}
}
Could anyone tell me what I am doing wrong?
It looks like the problem is related to the EDMX model.
I have a Book entity and an Author entity, with a many-to-many relationship.
The Book entity's Key is ISBN, which is a string of Max length 13.
StoreGeneratedPattern is set to None.
The Author entity's Key is Id, which is a Guid.
StoreGeneratedPattern is Identity.
The exception message is:
"Cannot insert the value NULL into column 'Id', table 'BookBrowser.dbo.Authors'; column does not allow nulls. INSERT fails. The statement has been terminated. "
But since StoreGeneratedPattern is set to Identity, shouldn't an Id value be created automatically?
Thanks,
Peter

It looks that the problem was that I used a Guid as Key in combination with StoreGeneratedPattern = Identity.
When I set StoreGeneratedPattern to None and create my own Guid using Id = Guid.NewGuid(), the problem is gone.
Apparently, the SQL server cannot generate Guids...

you can use StoreGeneratedPattern=Identity, but generated sql script based on your edmx doesn`t contain newid() in describing primary key(GUID). you can do this manually in generated sql script. 'BookId uniqueidentifier NOT NULL
DEFAULT newid()'. So id value will create GUID automatically.

Related

Why can't EF handle two properties with same foreign key, but separate references/instances?

Apparently, EF6 doesn't like objects that have multiple foreign key properties that use the same key value, but do not share the same reference. For example:
var user1 = new AppUser { Id = 1 };
var user2 = new AppUser { Id = 1 };
var address = new Address
{
CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference
};
When I attempt to insert this record, EF throws this exception:
Saving or accepting changes failed because more than one entity of type
'AppUser' have the same primary key value. [blah blah blah]
I've discovered that doing this resolves the issue:
var user1 = new AppUser { Id = 1 };
var user2 = user1; //same reference
I could write some helper code to normalize the references, but I'd rather EF just know they're the same object based on the ID alone.
As for why EF does this, one explanation could be that its trying to avoid doing multipe CRUD operations on the same object since separate instances of the same entity could contain different data. I'd like to be able to tell EF not to worry about that.
Update
So it's as I suspected per my last paragraph above. In absense of a means to tell EF not to do CRUD on either instance, I will just do this for now:
if (address.ModifiedBy.Id == address.CreatedBy.Id)
{
address.ModifiedBy = address.CreatedBy;
}
Works well enough so long as I am not trying to do CRUD on either.
Update2
I've previously resorted to doing this to prevent EF from validating otherwise-required null properties when all I need is the child entity's ID. However, it doesn't keep EF from going into a tizzy over separate instances with the same ID. If it's not going to do CRUD on either AppUser object, why does it care if the instances are different?
foreach (var o in new object[] { address.ModifiedBy, address.CreatedBy })
{
db.Entry(o).State = EntityState.Unchanged;
}
If you get AppUser from context, then you will not need to do anything, because Entity Framework will track entities:
var user1 = context.AppUsers.Find(1);
var user2 = context.AppUsers.Find(1);
var address = new Address
{
CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference
};
Now, they both will point to same objects and will not cause to conflict.
You can add two extra properties to have the Id for the main objects which is the AppUser, then you can use only one AppUser object and reference it for both the created and modified by properties.
CreatedById = user1.Id,
ModifiedById = user1.Id
Otherwise, your code will end up by saving two instances of AppUser with the same primary key.
Another approach is to set both the foreign key properties to only one AppUserobject
The explanation is that EF's change tracker is an identity map. I.e. a record in the database is mapped to one, and only one, CLR object.
This can be demonstrated easily by trying to attach two objects with the same key:
context.AppUsers.Attach(new AppUser { Id = 1 });
context.AppUsers.Attach(new AppUser { Id = 1 });
The second line will throw an exception:
Attaching an entity of type 'AppUser' failed because another entity of the same type already has the same primary key value.
This also happens if you assign
CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference
Somewhere in the process, user1 and user2 must be attached to the context, giving rise to the exception you get.
Apparently, you have a function that receives two Id values that can be different or identical. Admittedly, it would be very convenient if you could simply create two AppUser instances from these Ids, not having to worry about identical keys. Unfortunately, your solution ...
if (address.ModifiedBy.Id == address.CreatedBy.Id)
... is necessary. Solid enough, though.

JPA #SequenceGenerator with Manual ID and Auto ID

I have an entity
#Entity
public class Book {
#Id
#Column(name = "book_id")
#SequenceGenerator(name = "book_book_id_seq", sequenceName = "book_book_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "book_book_id_seq")
private Long id;
// getter, setter & other fields
}
with schema
CREATE TABLE book
(
book_id bigint NOT NULL DEFAULT nextval('book_book_id_seq'::regclass),
CONSTRAINT book_pkey PRIMARY KEY (book_id)
)
What I want to achieve is sometime I would like to use sequence/id generated by database, but sometime the data is created at other place and I would like to create with existing (manual) id.
I can't set the id manually with Spring Data JPA way (using CrudRepository) or JPA way (using EntityManager), but no issue with native query. Is this JPA limitation? Any workaround for my issue?
Book book01 = new Book();
bookRepo.save(book01); // Book with id 1 is created
Book book02 = new Book();
book02.setId(5555L);
bookRepo.save(book02); // Does not create book with id 5555, but 2
Query nativeQuery = entityManager.createNativeQuery("INSERT INTO book VALUES (6666);");
nativeQuery.executeUpdate(); // Book with id 6666 is created
Query nativeQuery02 = entityManager.createNativeQuery("INSERT INTO book DEFAULT VALUES;");
nativeQuery02.executeUpdate(); // Book with id 3 is created
I am using PostgreSQL 9.4, Hibernate 5 and Java 8.
On persist, if a field is annotated with #GeneratedValue, it will generate the ID with whatever strategy is specified. Then it will set value of the id field with the generated value. So if the id is manually set using setId() before persisting, this will just be overriden.
If you want, you can use em.persist for auto-generated IDs. Then use native SQL for manually setting the Id, since native SQLs will bypass whatever mapping you have on your entity.
Yes, by default Hibernate org.hibernate.id.SequenceGenerator always generate new id. What you should do is to override public Serializable generate(SessionImplementor session, Object obj) method, where if your obj (cast to your entity first) has id, then return the id, else get it from database sequence.

Get Record ID in Entity Framework 5 after insert

I realize this must be a relatively simple thing to do, but I'm not getting what I'm looking for with Google.
I need to get the record ID of the record I just saved using the Entity Framework. With SQL queries we used "Select ##IDENTITY as 'Identity';"
If anyone can help it would be greatly appreciated.
The default behavior of Entity Framework is it sets identity fields on entities from the database right after SaveChanges is called.
In the following sample code, before SaveChanges is called, my employee has a default ID of 0. After SaveChanges my employee has a generated ID of 1.
using (TestDbEntities context = new TestDbEntities())
{
Employee e = new Employee ();
e.FirstName = "John";
e.LastName = "Doe";
context.Employee.Add(e);
context.SaveChanges();
Console.WriteLine("Generated ID: {0}", e.ID);
Console.ReadKey();
}

Many-to-Many insert failing - Entity Framework 4.1 DbContext

I am using DB first method, EF 4.1 with DbContext POCO code gen.
My database has a many-to-many relationship as shown below:
Employee
EmployeeId
EmployeeName
Account
AccountId
AccountName
EmployeeAccount
EmployeeId
AccountId
The problem occurs when I am trying to insert a new Employee, and assign them a pre existing account, so I am basically doing this as below:
Employee emp = new Employee();
emp.EmployeeName = "Test";
emp.Accounts.Add(MethodThatLooksUpAccountByName("SomeAccountName"));
context.Employees.Add(emp);
context.SaveChanges();
The SQL this is executing (incorrectly), is attempting to INSERT a new [Account] record, and this is failing on a constraint violation. Of course, it should not INSERT a new [Account] record, it should only insert a new [EmployeeAccount] record, after inserting the [Employee].
Any advice? Thanks.
MethodThatLooksUpAccountByName does this method return an attached or detached object? In any case, you may try to attach the object it returns to the context.
Employee emp = new Employee();
emp.EmployeeName = "Test";
var acc = MethodThatLooksUpAccountByName("SomeAccountName");
context.Attach(acc); //I don't remember if it's attach or attachobject, but intellisense should help you there.
emp.Accounts.Add(acc);
context.Employees.Add(emp);
context.SaveChanges();

ADO.NET Entity : getting data from 3 tables

I have following table structure:
Table: Plant
PlantID: Primary Key
PlantName: String
Table: Party
PartyID: Primary Key
PartyName: String
PlantID: link to Plant table
Table: Customer
PartyID: Primary Key, link to Party
CustomerCode: String
I'd like to have Customer entity object with following fields:
PartyID: Primary Key
CustomerCode: String
PartyName: String
PlantName: String
I am having trouble with PlantName field (which is brought from Plant table
I connected Customer to Party and Party to Plant with associations
However I can not connect Customer to Plant with association ( because it does not have one)
I can not add Plant table to mapping, when I do that - I am getting following error:
Error 3024: Problem in Mapping Fragment starting at line 352: Must specify mapping for all key properties (CustomerSet.PartyID) of the EntitySet CustomerSet
Removing Plant association works.
Any hints or directions very appreciated.
You can get these fields by using the reference path on the Entity Object.
To get the PartyName, use this syntax: Customer.Party.PartyName
To get the PlantName, use this syntax: Customer.Party.Plant.PlantName
You can extend the Customer entity by using the public partial class:
public partial class Customer
{
public string PartyName
{
get { return Party.PartyName; }
set { Party.PartyName = value; }
}
public string PlantName
{
get { return Party.Plant.PlantName; }
set { Party.Plant.PlantName = value; }
}
}
After some research, I came across this thread on MSDN that says you can create a read-only entity, which is enough of a downside to not use it alone, but it gets worse. You will also lose the ability to update all of the models dynamically based on the schema of the database.