问题
I just learn that with the [Contained] attribute I can define a contained collection. This means the collection is no more accessible from my root oData system. Ok fine, but here is my model:
I have a user that have addresses The user has invoices Each invoice can have one or two addresses from the user.
On which collection should I add the contained attribute?
回答1:
The answer to this completely depends on your domain model. The advice I would give is to use OData containment sparingly. It really only makes sense to use it if the entity you are marking as being a contained entity cannot exist outside of the context of the parent entity. Because of this constraint I think the use cases for OData containment are few and far in between. The advantage over a separate controller is that it can make more sense from an architectural standpoint. However your controllers become more bloated and it is more work to maintain the ODataRouteAttributes
on your methods. Something which is not necessary when using convention based routing.
The example on the guide to set up OData containment explains it somewhat. It falls a bit short on why you would use it. Note that the PaymentInstrument
entity has no foreign key to Account
. This means that there is no separate table where the PaymentInstrument
information is stored. Instead it is stored directly on the Account
record. Yet is still defined as a Collection<T>
so it is probably stored as JSON or across multiple columns. This might not necessarily be the case, but from a code standpoint the database could look like that.
To further explain OData containment let's say we have the domain model below.
public class HttpRequest
{
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
public string CreatedBy { get; set; }
public virtual HttpResponse HttpResponse { get; set; }
}
public class HttpResponse
{
public string Content { get; set; }
public DateTime CreatedOn { get; set; }
}
As you can see the HttpResponse
class has no navigation property to HttpRequest
. Therefore it makes no sense to want to call GET odata/HttpResponses
as we would be getting all HttpResponses
, but not the HttpRequest
they are linked to. In other words the HttpResponse
class is useless without the context i.e. the HttpRequest
for which it was produced.
The HttpResponse
class not having any meaning outside of the HttpRequest
context makes it a perfect candidate for OData containment. Both classes could even be saved on the same record in the database. And because it's not possible to perform a GET/POST/PUT/DELETE without specifying the id of the HttpRequest
to which the HttpResponse
belongs, it makes no sense for the HttpResponse
class to have its own controller.
Now, back to your use case. I can see two likely domain models.
- The entities
User
,UserAddress
,Invoice
andInvoiceAddress
.
In this first option every single entity has their own designated address entity. OData containment would make sense here using such a design as the address entities do not exist outside of their respective parent entity. A UserAddress
is always linked to a User
and an InvoiceAddress
is always linked to an Invoice
. Getting a single UserAddress
entity makes less sense because using this domain model one shouldn't care where the single address is. Instead the focus lays more on what the persisted addresses for this single User
are. It's also not possible to create a UserAddress
without specifying an existing User
. The UserAddress
entity relies on the User
entity entirely.
- The entities
User
,Invoice
,TypedAddress
andAddress
.
In this second option the Address
entity is stand-alone. It exists separately from the other entities. Since an address boils down to a unique location on this planet it is only saved once. Other entities then link to the Address
entity via the TypedAddress
entity where they specify what kind of address it is in relation to the entity linking to it. Getting a single Address
makes perfect sense using this domain model. An addressbook of the entire company could easily be retrieved by requesting GET odata/Addresses
. This is where OData containment does not make sense.
Do note that it is possible to use the ODataConventionModelBuilder to configure containment. Because you do not need to add the ContainedAttribute
to your class, this has the advantage of not polluting your data layer with a reference to the OData library. I would recommend this approach. In your situation I would expect to have the configuration below.
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder
.EntityType<User>()
.ContainsMany(user => user.UserAddresses);
modelBuilder
.EntityType<Invoice>()
.ContainsMany(invoice => invoice.InvoiceAddresses);
来源:https://stackoverflow.com/questions/55163519/odata-containment