OData Containment

那年仲夏 提交于 2021-02-10 05:13:33

问题


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.

  1. The entities User, UserAddress, Invoice and InvoiceAddress.

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.

  1. The entities User, Invoice, TypedAddress and Address.

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!