Entity Framework seeding complex objects? - entity-framework

I am trying to figure out how to seed a complex entity framework code first object. Currently the code I have allows me to insert simple objects but there has to be a way do more complex items, i.e. Address with an AdressType field.
context.AddressTypes.AddOrUpdate(
p => p.Name,
new AddressType { Name = "Original" },
new AddressType { Name = "Shipping" },
new AddressType { Name = "Billing" }
);
context.Addresses.AddOrUpdate(
a => a.Address1,
new Address
{
Address1 = "1234 West Main",
City = "Hannibal",
Region = "MO",
PostalCode = "12345",
Country = "USA",
Type = context.AddressTypes.Where(a=>a.Name=="Original")
});
But while I can "Find" an addresstype by id I can't do a "Where" name equals. Find would work except that I can not guarantee what the id will be with each seed and want to base it on name instead.
Any suggestions on how to do this?
TIA

Solution:
Added a reference System.Linq to the seed class. Then was able to the where clause.

Related

How not to allow an IRI when denormalizing embedded relations?

I have a Customer entity that is linked to a Contact entity, in a nullable OneToOne relationship.
When I create a new Customer, the creation of the linked Contact is optional, but it must not be possible to fill in the IRI of an existing Contact. In other words, it must be a new Contact or nothing.
class Customer
{
#[ORM\OneToOne(targetEntity: Contact::class, cascade: ["persist"])]
#[Groups([
'write:Customer:collection', '...'
])]
private $contact;
}
The 'write:Customer:collection' denormalization group is also present on the Contact properties.
With a good request as follow, I can create my Customer and my Contact, no problem with it.
{
"name": "test company",
"contact": [
"firstname" => 'hello',
"lastname" => 'world'
]
}
Problem:
But, and I don't want it, I also can create the new Customer with an existing Contact, like this:
{
"name": "test company",
"contact": "/api/contacts/{id}"
}
As stated in the serialization documentation:
The following rules apply when denormalizing embedded relations:
If an #id key is present in the embedded resource, then the object corresponding to the given URI will be retrieved through the data provider. Any changes in the embedded relation will also be applied to that object.
If no #id key exists, a new object will be created containing data provided in the embedded JSON document.
However, I would like to disable the rule if an #id key is present, for specific validation group.
I thought of creating a custom constraint that would check that the resource does not exist in the database, but I am surprised that no constraint allows to check this.
Am I missing something? Do you have a solution for me? Thanks in advance.
I finally created a custom constraint that checks if the embed resource sent in request is already managed by Doctrine.
The constraint itself:
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
* #Target({"PROPERTY", "METHOD", "ANNOTATION"})
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class AcceptPersisted extends Constraint
{
public bool $expected = false;
public string $mustBePersistMessage = 'Set a new {{ entity }} is invalid. Must be an existing one.';
public string $mustBeNotPersistMessage = 'Set an existing {{ entity }} is invalid. Must be a new one.';
public function __construct(bool $expected = false, $options = null, array $groups = null, $payload = null)
{
parent::__construct($options, $groups, $payload);
$this->expected = $expected;
}
}
And it validator:
namespace App\Validator\Constraints;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class AcceptPersistedValidator extends ConstraintValidator
{
public function __construct(private EntityManagerInterface $entityManager) {}
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof AcceptPersisted) {
throw new UnexpectedTypeException($constraint, AcceptPersisted::class);
}
if ($value === null) {
return;
}
//if current value is/is not manage by doctrine
if ($this->entityManager->contains($value) !== $constraint->expected) {
$entity = (new \ReflectionClass($value))->getShortName();
$message = $constraint->expected ? $constraint->mustBePersistMessage : $constraint->mustBeNotPersistMessage;
$this->context->buildViolation($message)->setParameter("{{ entity }}", $entity)->addViolation();
}
}
}
So, I just had to add the custom constraint on my property:
use App\Validator\Constraints as CustomAssert;
class Customer
{
#[ORM\OneToOne(targetEntity: Contact::class, cascade: ["persist"])]
#[CustomAssert\AcceptPersisted(expected: false)]
//...
private $contact;
}

"Error getting value from 'MyEntity' on 'System.Data.Entity.DynamicProxies..."

I insert an Entity with this request and it is successful.
Request
{
"Title": "Book 1"
}
Response
{
"id": 1,
"title": "Book 1",
"serialId": "00000000-0000-0000-0000-000000000000",
"borrows": null,
"votes": null
}
I checked the database and there is a Book row with id = 1. But now when I try to fetch a list it returns an error because the DbSet is empty after the insert.
var books = db.Books; // count = 0
The response for /books is the exception:
"exceptionMessage": "The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.",
And the inner exception includes Error getting value from 'Borrows' so I thought it might be to do with how the relationship is defined, also there are no Borrow entities in the database yet.
"exceptionMessage": "Error getting value from 'Borrows' on 'System.Data.Entity.DynamicProxies.Book_6E27A1F717202EA02AE923CCC6405EF9A501FE9A54A71841CEB43E942224D88A'.",
"exceptionType": "Newtonsoft.Json.JsonSerializationException",
The Book entity has a navigation property of List on it with a defined relationship:
modelBuilder.Entity<Book>().HasMany<Borrow>(b => b.Borrows);
Book's entity defined with a list of Borrow entities:
public virtual List<Borrow> Borrows { get; set; }
The migration of Book only has it's Id for the primary key:
CreateTable(
"dbo.Books",
c => new
{
Id = c.Int(nullable: false, identity: true),
Title = c.String(),
SerialId = c.Guid(nullable: false),
})
.PrimaryKey(t => t.Id);
Question: What is causing this exception, is it something to do with how I defined the relationship between Book and Borrow? I've seen examples of relationships having to be defined in both directions, do I need to define the relationship to not require a borrow somehow? I've noticed DbSet Count = 0 occur in the past when there have been results so I think it could also be an issue with NewtonSoft. I'm not sure what is going on here.
Full exception: https://pastebin.com/raw/BvUfBnKU
I got it working by using this answer: https://stackoverflow.com/a/14317598/11419029, returning a List rather than IQueryably.
I changed it to this:
// GET: api/Books
public List<Book> GetBooks()
{
var books = db.Books.ToList();
return books;
}
From this:
// GET: api/Books
public IQueryable<Book> GetBooks()
{
var books = db.Books;
return books;
}
But I'm still not sure what the problem was, another answer said it was to do with a query being executed while iterating results of another query. Triggering lazy loading. Now I'm concerned it is executing nested queries, querying borrows for every instance of a book.

Seeding a Database with Code First Entity Framework - Foreign key syntax

I am trying to find the correct syntax to seed a database with test data. I have a foreign key to my product table. It is the category. I have seeded the database with the values for categories, but stuck on how to add that relationship to the product. I have tried this way to no avail.
context.Categories.AddOrUpdate(x => x.Name,
new Category
{
Name = "Fruit"
});
context.Products.AddOrUpdate(x => x.Name,
new Product
{
Name = "Cherries",
Description = "Bing Cherries",
Measure = "Quart Box",
Price = 1.11M,
Category = context.Categories.FirstOrDefault(x => x.Name == "Fruit")
}
});
Can anyone point me in the right direction?
I found that in order to accomplish the foreign key from Category is to do a save changes to the context. Then I was able to query the context for the categoryId and save it to the CategoryId on the product.
context.Categories.AddOrUpdate(x => x.Name,
new Category
{
Name = "Fruit"
});
context.SaveChanges();
context.Product.AddOrUpdate(x => x.Name,
new Product
{
Name = "Cherries",
Description = "Bing Cherries",
Measure = "Quart Box",
Price = 1.11M,
CategoryId = context.Categories.FirstOrDefault(x => x.Name == "Fruit").Id
});

DBIx::Class - where is this relationship being cached?

I have an Order entity and an Address entity, and in my Schema::Result::Order module I have a simple belongs to relationship:
__PACKAGE__->belongs_to( "address", 'Schema::Result::Address',
{ addressid => 'addressid' });
I run this code with DBIC_TRACE=1:
my $order = $schema->resulset('Order')->find($id);
my $add1 = $order->address;
my $add2 = $order->address;
and I only see one SELECT ... FROM ADDRESS ... query, so apparently the second $order->address method is not hitting the database.
So this might be a simple question, but where is the address object getting cached? (in the $order object?)
Secondly, is this caching configurable (i.e. can I configure DBIC to not cache these relationships)?
I think the way you have your order to address relationship you will only have one address to the order:
__PACKAGE__->belongs_to( "address", 'Schema::Result::Address',
{ addressid => 'addressid' });
If your order will have many addresses you will want:
__PACKAGE__->has_many( "address", 'Schema::Result::Address',
{ addressid => 'addressid' });
Then you can retrieve the addresses a number of ways:
my $address_rs = $order->search_related('address',{});
while(my $row = $address_rs->next) {
#$row has an address record
}
I am not sure how the caching works in this situation
my $order = $schema->resulset('Order')->find($id);
my $add1 = $order->address;
my $add2 = $order->address;
But if you access your address record like this:
my $address_rs = $order->search_related('address',{});
you can control it with your query attributes:
https://metacpan.org/module/DBIx::Class::ResultSet#cache
Found the answer: it's cached in $order->{_relationship_data}->{address}.
Haven't determined if that caching can be disabled.

Entity Framework - How to Set Association Value?

Let's say I have a Person class and an Order class, with foreign keys in the DB. The EF model will mark Person with a List of Orders and Order with a Person instance.
If I want to set the Person for the Order, do I really have to do it with an instance of Person?
Is there not a slimmed down way to do so, say with just a PersonID ?
To assign Person entity to a Order without loading Person entity, you have to do something like this:
var db = new OneToManyEntities();
var Order = new Order { OrderId = 100, OrderName = "Order name" };
Order. PersonReference.EntityKey = new EntityKey("OneToManyEntities.Person ","PersonID",10);
db.AddToOrders(Order);
db.SaveChanges();
Puzzled's answer is correct for EF v1. It's a pain. If you don't mind the extra query, you can set the property succinctly:
int id = 1;
Order.Person = context.Persons.Where(x => x.PersonID == id).FirstOrDefault();
Entity Framework v4 will have "FK Associations", which is a fancy term for directly-settable foreign keys.