如何使用 EF Core 6 和值对象实现 Web API 项目字段级权限

发布于 2025-01-19 05:20:12 字数 1525 浏览 2 评论 0原文

试图找出可能的实施,我感到非常沮丧。

根据DDD和CQS概念,我的应用程序的写入side 使用了用EF Core实现的各种聚合物和相关存储库。

读取侧面上,我想使用在某些情况下链接相关数据的查询。这可能只是ID的分辨率(即进行最后一个编辑的用户名称)或出于性能原因列表(即一个人的地址簿)。

因此,返回的对象(getPersondto)的结构在内,包括分辨率或子女是写对象(人)的不同。我将价值对象用作实体中所有属性的类型,以将验证保持在一个位置(始终有效的对象)。

我的问题在读取方面。从get请求返回的资源表示形式是JSON。与请求主题相关的权限决定是否包含在JSON中的字段。

我的想法是,我使用EF Core返回查询对象和一个权限对象,该对象保留该对象的字段权限(用户)。如果主题已对某个字段的阅读许可,则将映射到DTO。 DTO使用可选< t>和一个自定义jsonConverter,如图所示在这里。结果,所有可选< t>未设置的字段将不包括在JSON响应中,但它保留了设置为null的字段。

我使用RAW SQL在EF Core中编写查询,因为我没有使用LINQ编写更多复杂的查询。 EF核心需要无钥匙实体来进行原始SQL查询。我希望EF Core使用为写侧创建的转换器将读取字段转换为值对象。

但是无钥匙实体不能成为关系的主要端,因此他们不能拥有拥有的实体。作为各种github 问题RAW SQL查询。据说

在EF中,每个EntityType映射到一个数据库对象。相同的数据库 对象可以用于多个实体类型,但您无法指定 数据库对象实现对象图。

如果我正确理解,也无法通过视图或存储过程实现此目的。在我看来,也无法定义使用现有DBSET对象的另一个完全连接的GetPerson对象。

如何实现?什么是替代方案?

我能想到

a)使用具有原始类型的原始SQL结果的平面对象,然后使用它映射到DTO。用原始创建对象图的副作用是创建值对象从数据库验证此数据。因此,我要么必须信任数据库,要么需要调用手动价值对象中公开的验证方法。

b)忘记EF核心,使用ado.net。返回对象是ADO.NET记录。考虑到记录场的权限,将映射到DTO。这很简单,开销较少,但需要框架的另一部分。

还有其他选择吗?考虑到字段权限,您如何解决返回组合对象的返回?

I'm quite frustrated trying to figure out a possible implementation.

Pursuant DDD and CQS concepts the write side of my application uses various aggregates and associated repositories that are implemented with EF Core.

On the read side I want to use queries that in some cases link related data. This could be just the resolution of an id (i.e. the name of the user that made the last edit) or for performance reasons a list of child objects (i.e. address book of a person).

Therefore the structure of the returned object (GetPersonDTO) including resolutions or children is different of the write object (Person). I use value objects as types for all properties in the entities to keep validation in one spot (always valid objects).

My problems are on the read side. The returned resource representation from a GET request is JSON. Permissions associated with the requesting subject decide if a field is included in the JSON.

My idea was that I use EF Core to return a query object and a permission object that holds the field permissions of that object for the current subject (user). If the subject has read permission for a certain field it will be mapped to the DTO. The DTO uses Optional<T> and a custom JsonConverter as shown here. As a result all Optional<T> fields that are not set will not be included in the JSON response, but it preserves fields that are set to NULL.

I write the queries in EF Core using raw SQL, because I didn't manage to write more complex queries using LINQ. EF Core requires keyless entities for raw SQL queries. I expected EF Core to convert the read fields back into value objects using the converters that have been created for the write side.

But keyless entities cannot be principal end of relationship hence they cannot have owned entities. As various GitHub issues show it is not yet possible that EF Core recreates the object graph from a raw SQL query. It is stated that

In EF each entityType maps to one database object. Same database
object can be used for multiple entityTypes but you cannot specify a
database object to materialize a graph of object.

If I understand correctly it is also not possible to achieve this with a view or stored procedure. It sounds to me that it is also not possible to define another fully connected GetPerson object that uses existing DbSet objects.

How can this be implemented? What are the alternatives?

I can think of

a) using a flat object with primitive types for the raw SQL result and then use it to map to the DTO. A side effect of creating the object graph with the original is that creating the value objects validate this data from the DB. So I either have to trust the database or I need to call the validation methods that are public in my value objects manually.

b) forget EF Core and use ADO.NET. The return object is then the ADO.NET record. Considering the permissions the fields of the record will then be mapped to the DTO. This is simple with less overhead, but requires another part of the framework.

Are there any other options? How have you solved returning a combined object considering field permissions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

西瑶 2025-01-26 05:20:12

EF6核心不支持持续的值对象,这是EF7中计划的功能: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-new/ef-core-7.0/plan#value-objects

诸如EF之类的ORM的目的是允许程序员通过对象而不是基于文本的语言(例如SQL)操纵RDBM。此对象模型不是业务模型,而是RDBMS概念的符号(class = table,row = object,column =属性,...)。对于微不足道的应用程序,您可能会混淆模型,但是您很快就会发现自己有限,因为业务模型的限制与数据库架构不同。对于较大的应用程序,您编写一个持久性模型,该模型由与数据库结构匹配的DPO组成,并将该模型转换为基础结构层中的其他模型。将您的域模型从持久性模型中脱在一起,可以在应用程序中更灵活,并将域对象重新覆盖为多理型模型,从而限制了独立用例的副作用。

这里的一个示例是将标准化的rdbms带有表格,包括替代键,这些替代键隐藏在存储库完成的投影过程中的域模型。这使您的数据库可以解决关系映射的复杂性,同时在域层中重新整理价值对象。

对于查询,您不应使用GetPerson模型发布您的实体。域层的目的是保护您的应用程序免于违反任何业务规则。查询应用程序状态时,您不会修改应用程序的状态,也不能违反任何规则。域模型仅对状态更改用例有用。因此,处理查询时,您应该直接从DPO绘制DTO。您将保存性能,并允许您的DTO通过诸如AutoMapper之类的库将/过滤器/分页功能直接分类/过滤/分页功能,只要该投影以此库的翻译容量为单位。同样,您的业务规则实施将不会受到大型且复杂的查询模型的影响,这是CQS体系结构的初始目的。

无论您是通过EF等ORM操纵数据库,还是直接在Ado.net级别操纵的RAW SQL查询是基础结构层的实现细节。选择取决于您是否认为自己可以编写比ORM编写“更好”的查询,“更好”作为主观问题,取决于您的项目限制。


更新1 :关于使用可选&lt; t&gt;映射到您的DTO,EF Core的能力有限地将关系数据映射到不简单地表示数据库架构的模型中。这是通过设计,您不应强迫EF尝试直接将数据直接恢复到DTO中。使用单个模型添加了您的API接口和数据库持久性方案之间的依从性。每次更新持久性架构和反之亦然,您都会冒着界面破坏变化的风险。您应该有两个不同的模型,可以使演示文稿与持久性解脱。

无论您是使用EF Core还是ADO.NET读取数据库都不会在概念上发生太大变化。在这两种情况下,您都将数据库信息读取为存储器模型,然后将该模型转换为DTO。不同之处在于,此内存模型是基于OOP(EF + DPO模型)还是键值表(ADO.NET + DATAROW)。

使用EF核心的优点是,由于生成查询并且始终为您逃脱了值,因此它不太容易出现SQL注入。同样,持久性模型可以通过映射库(例如自动应用程序)转换为DTO。自动应用程序使翻译更容易,更便宜。此外,它可以将某些翻译投射到关系模型中。

如果您设法将您的安全性建模到地图配置文件中,则数据库只能选择DTO中数据博览会所需的列。换句话说,如果不允许用户公开dto.name,则数据库将不包括table.name纳入选择语句。但是,这并不总是可以做到的,但是这样做比在SQL中编写“聪明”查询要容易得多。

但是,EF的一个缺点是它比ado.net慢。

如果您确实需要将查询分为两个阶段转换(映射和安全性),则应将安全层更接近数据库,除非翻译逻辑要求该数据相应地映射。

EF6 core does not support persisting value objects, this is a feature planned in EF7: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/plan#value-objects

The purpose of an ORM such as EF, is to allow programmers to manipulate the RDBMS through objects rather than a text based language such as SQL. This object model is not a business model, but symbols of RDBMS concepts (class = table, row = object, column = property, ...). For trivial applications, you can confuse your models, but you will quickly find yourself limited because a business model has different constraints than a database schema. For larger applications, you write a persistence model, consisting of DPO that match your database structure, and translate that model to other models in the infrastructure layer. Decoupling your domain model from your persistence model allows more flexibility in your application, and re-hydrating your domain objects as a polysemic model, limiting side effects of independent use cases.

An example here would be to have a normalized RDBMS with tables including surrogate keys that are hidden from the domain model in the projection process done by the repository. This allows your database to resolve the complexity of relational mapping, while re-hydrating value objects without identities in the domain layer.

For queries, you should not publish your entities with a GetPerson model. The domain layer's purpose is to protect your application from violating any business rules. When querying your application state, you do not modify the state of the application, and cannot violate any rule. A domain model is only useful for state changing use cases. Therefore, when handling a query, you should directly map your DTO from the DPO. You will save performance and allow your DTO to project directly sort/filter/paging features to the database through libraries such as AutoMapper, as long as the projection is in this library's translation capacity. Also your business rules implementation will not impact / be impacted by large and complex query models, which is the initial purpose of a CQS architecture.

Whether you manipulate your database through an ORM such as EF, or as raw SQL queries manipulated directly at ADO.NET level is an implementation detail of your infrastructure layer. Choice depends on whether you think you can write "better" queries than the ORM, "better" being a subjective matter depending on your project constraints.


Update 1: Regarding mapping to your DTO with Optional<T>, EF core has limited ability to map relational data into a model that does not simply represent the database schema. This is by design and you shouldn't force EF to try to restore data into the DTO directly. Using a single model adds adherence between your API interface and database persistence scheme. You would risk interface breaking changes each time you update the persistence schema, and vice-versa. You should have two different models to decouple presentation from persistence.

Whether you use EF core or ADO.NET to read the database does not change much conceptually. In both cases you read database information into a memory model, then translate that model into a DTO. The difference is whether this in-memory model is based on OOP (EF + DPO model) or a key-value table (ADO.NET + datarow).

Pros of using EF core, is that it is less prone to SQL injection as queries are generated and values are always escaped for you. Also, the persistence model can be translated into DTO through a mapping library, such as AutoMapper. AutoMapper makes the translation easier and cheaper to write and maintain. Also, it can project some of the translation into the relational model.

If you manage to model your security into the map profile, your database could only select columns required for data exposition in the DTO. In other words, if the user is not allowed to expose DTO.Name, then the database would not include Table.Name into the select statement. This is not always possible to do, though, but it is much easier done this way than writing "clever" queries in SQL.

One downside of EF, however, is that it is slower than ADO.NET.

If you really need to split your query into a two phase transformation (mapping and security), you should put the security layer closer to the database, unless the translation logic requires that data to map accordingly.

毁梦 2025-01-26 05:20:12

这是一个主观和最佳实践问题,但是我会回答我如何解决类似问题 - 鉴于我实际上正确理解了您的问题。

只要您使用导航属性完全映射了数据库模型,就可以生成非常复杂的查询而不诉诸于原始查询。

    var dto = await context.Persons
        .Where(p => p.Id == id)
        .Select(p => new GetPersonDTO
        {
            Id = p.Id,
            InternallyVerifiedField = !p.UsersWithAccess.Contains(currentUser) ? new Optional<int>(p.InternallyVerifiedField) : new Optional<int>(),
            ExternallyVerifiedField = permissions.Contains(nameof(GetPersonDTO.ExternallyVerifiedField)) ? new Optional<int>(p.ExternallyVerifiedField) : new Optional<int>()
        })
        .SingleOrDefaultAsync();

在此示例中,internallyverified field将取决于某些查询内联,externallifiedfield将取决于某些外部权限对象。 externallified Field的好处是,如果用户没有许可,则可以在到达SQL Server之前从表达式中进行优化。

如果要从完全连接的对象构建DTO对象,它仍然可以在类似于

    var dto = await context.Persons
        .Where(p => p.Id == id)
        .Select(p => new
        {
            permissions = new GetPersonDTOPermissions
            {
                FieldA = context.Permissions.Where(...)
            },
            person = p
        })
        .SingleOrDefaultAsync();

此解决方案的一个查询中完成,您需要从Graph对象Person给定结果<代码>权限,只要您以上下文开始,然后使用 添加 将其添加。

This is a bit of subjective and best-practice question, but I'll answer with how I've solved a similar problem - given that I actually understand your question properly.

As long as you've mapped the database model fully using navigation properties, it is possible to generate very complex queries without resorting to raw queries.

    var dto = await context.Persons
        .Where(p => p.Id == id)
        .Select(p => new GetPersonDTO
        {
            Id = p.Id,
            InternallyVerifiedField = !p.UsersWithAccess.Contains(currentUser) ? new Optional<int>(p.InternallyVerifiedField) : new Optional<int>(),
            ExternallyVerifiedField = permissions.Contains(nameof(GetPersonDTO.ExternallyVerifiedField)) ? new Optional<int>(p.ExternallyVerifiedField) : new Optional<int>()
        })
        .SingleOrDefaultAsync();

In this example the InternallyVerifiedField will depend on some query inline, and ExternallyVerifiedField will depend on some external permission object. The benefit of ExternallyVerifiedField is that it might be optimized out from the expression before even reaching the sql server, if the user does not have permission.

If you want to build the dto object from a fully connected object it can still be done in one query similar to

    var dto = await context.Persons
        .Where(p => p.Id == id)
        .Select(p => new
        {
            permissions = new GetPersonDTOPermissions
            {
                FieldA = context.Permissions.Where(...)
            },
            person = p
        })
        .SingleOrDefaultAsync();

But with this solution you need to manually craft the dto from the graph object person given the resulting permissions, as long as you start with context and add a filter using Where the query will be inlined.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文