将两个不同的实体映射到同一个表?

发布于 2024-10-19 00:50:15 字数 351 浏览 1 评论 0原文

我的数据库中有一个包含很多字段的表。 大多数时候我需要所有这些字段。然而,有一种情况,我只需要几个字段,并且正在加载大量行。

我想做的是手动添加实体,然后简单地将其映射到原始表,但删除我不需要的列。我设置了这一切,但我得到了一个相当不言自明的错误:

映射片段时出现问题 ...EntitySets 'FmvHistoryTrimmed' 和 'FMVHistory' 都映射到 表“FMVHistory”。他们的主键 可能会发生碰撞。

我还应该采取其他方法吗?同样,大多数时候所有列都会被使用,所以我不想修剪原始实体并将“额外”字段放入复杂类型中。

I have a table in my database with a lot of fields. Most of the time I need all those fields. There is one scenario, however, where I only need a few of the fields, and I am loading a ton of rows.

What I'd like to do is add in an Entity manually, and then simply map it to the original table, but delete the columns I don't need. I set this all up, but I get the rather self-explanatory error of:

Problem in mapping fragments
...EntitySets 'FmvHistoryTrimmed' and
'FMVHistories' are both mapped to
table 'FMVHistory'. Their primary keys
may collide.

Is there some other way I should go about this? Again, most of the time all of the columns are used, so I don't want to trim down the original entity and put the "extra" fields into a complex type.

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

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

发布评论

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

评论(4

当梦初醒 2024-10-26 00:50:15

您无法将两个常规实体映射到同一个表中。您有多种选择:

  1. 使用表拆分。
  2. 使用自定义查询和投影到非实体类型(如 @Aducci 提议)
  3. 使用 QueryView
  4. 使用数据库视图或直接 DefiningQuery

表拆分

表拆分 允许您将表映射为 1:1 关系的两个实体。第一个实体将仅包含您始终需要的 PK 和字段子集。第二个实体将包含所有其他字段和 PK。两个实体都将包含彼此的导航属性。现在,如果您只需要字段的子集,您将查询第一个实体。如果您需要所有字段,您将查询第一个实体并将导航属性包含到第二个实体。如果需要,您还可以延迟加载第二个实体。

QueryView

QueryView 是直接在您的映射(MSL)并将其映射到新的只读实体类型。您可以使用 QueryView 定义完整实体到子实体的投影。 QueryView 必须在 EDMX 中手动定义(在设计器中不可用)。据我所知,QueryView 在代码优先中不可用,但它实际上与非实体类型的自定义投影相同。

DefiningQuery

DefiningQuery 是直接在您的存储模型 (SSDL)。 DefineingQuery 通常在映射到数据库视图时使用,但您可以将它用于任何自定义 SQL SELECT。您将把查询结果映射到只读实体类型。 DefineingQuery 必须在 EDMX 中手动定义(在设计器中不可用)。它也不能直接在 Code First 中使用,但实际上与在 DbDatabase 上调用 SqlQuery 相同。 DefiningQuery 的问题在于,一旦您在 SSDL 中手动定义它,您就无法使用从数据库更新模型,因为此操作会替换完整的 SSDL 并删除您的查询定义。

You can't map two regular entities into same table. You have several choices:

  1. Use table splitting.
  2. Use custom query with projection to non entity type (as @Aducci proposed)
  3. Use QueryView
  4. Use database view or directly DefiningQuery

Table splitting

Table splitting allows you to map a table into two entities in 1:1 relation. First entity will contain only PK and subset of fields which you need always. Second entity will contain all other fields and PK. Both entities will contain navigation property to each other. Now if you need only subset of fields you will query first entity. If you need all fields you will query first entity and include navifation property to second entity. You can also lazy load second entity if you need it.

QueryView

QueryView is ESQL query defined directly in your mapping (MSL) and it is mapped to new readonly entity type. You can use QueryView to define projection of your full entity into subentity. QueryView must be defined manually in EDMX (it is not available in designer). As I know QueryView is not available in Code first but it is actually the same as custom projection to non entity type.

DefiningQuery

DefiningQuery is custom query defined directly in your storage model (SSDL). DefiningQuery is usually used when mapping to database views but you can use it for any custom SQL SELECT. You will map the result of the query to readonly entity type. DefiningQuery must be defined manually in EDMX (it is not available in designer). It is also not directly avaliable in Code first but it is actually the same as calling SqlQuery on DbDatabase. The problem with DefiningQuery is that once you manually define it in SSDL you can't use Update model from database because this operation replaces complete SSDL and deletes your query definition.

沉鱼一梦 2024-10-26 00:50:15

我将在数据库上创建一个仅包含您需要的数据的视图,并将该视图添加到您的实体数据模型中。

如果您不想修改数据库,则可以创建仅包含您需要的信息的 Linq to 实体或 ESQL 语句投影到 POCO 类。

public IQueryable<SimpleObject> GetView(DBContext context)
{
    return  (from obj in context.ComplexObjects
            select new SimpleObject() { Property1 = obj.Property1,
                                        Property1 = obj.Property2
                                      }); 
}

I would create a View on the database containing only the data you need and add the View to your entity data model.

If you don't want to modify the database, you can create a Linq to entities or ESQL statement projecting to a POCO class with only the information you need.

public IQueryable<SimpleObject> GetView(DBContext context)
{
    return  (from obj in context.ComplexObjects
            select new SimpleObject() { Property1 = obj.Property1,
                                        Property1 = obj.Property2
                                      }); 
}
万劫不复 2024-10-26 00:50:15

我用一个技巧将多个实体映射到一个表。

首先,您的实体必须派生自通用类型。该基本类型本身不会成为您的上下文的一部分。

给上下文一个假表名,可以解析它以获取原始表名。我使用的表名称格式:$$$TableName$$$。它可以有 3 美元或更多。

然后拦截命令并替换命令文本。

层次结构:

    class MyEntityBase
    {
        // Common properties here
    }

    [Table("MyTable")]
    class MyEntityV1 : MyEntityBase
    {
    }

    [Table("$$MyTable$$")]
    class MyEntityV2 : MyEntityBase
    {
    }

拦截器:

class EntityNameReplacerInterceptor: DbCommandInterceptor
{
        private static Regex regex = new Regex("([\\$]{3,})(.+)\\1", RegexOptions.Compiled);

        public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
        {
            ManipulateCommand(command);
            return base.ReaderExecuting(command, eventData, result);
        }

        public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
        {
            ManipulateCommand(command);
            return base.NonQueryExecuting(command, eventData, result);
        }

        public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
        {
            ManipulateCommand(command);
            return base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
        }

        public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
        {
            ManipulateCommand(command);
            return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
        }

        public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result)
        {
            ManipulateCommand(command);
            return base.ScalarExecuting(command, eventData, result);
        }

        public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<object> result, CancellationToken cancellationToken = default)
        {
            ManipulateCommand(command);
            return base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
        }

        private void ManipulateCommand(DbCommand result)
        {
              result.CommandText = regex.Replace(result.CommandText, "$2");
        }
}

配置DbContext

DbSet<MyEntityV1> V1s { get; set; }
DbSet<MyEntityV2> V2s { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.AddInterceptors(new EntityNameReplacerInterceptor());
}

There is a trick which I used to get multiple entities to map to one table.

First your entities must derive from a common type. This base type itself will not be part of your context.

Give the context a fake table name, which can be parsed to get the original table name. I used the table name format: $$$TableName$$$. It can have 3 or more $.

Then intercept the command and replace the command text.

The hierarchy:

    class MyEntityBase
    {
        // Common properties here
    }

    [Table("MyTable")]
    class MyEntityV1 : MyEntityBase
    {
    }

    [Table("$$MyTable$
quot;)]
    class MyEntityV2 : MyEntityBase
    {
    }

The interceptor:

class EntityNameReplacerInterceptor: DbCommandInterceptor
{
        private static Regex regex = new Regex("([\\$]{3,})(.+)\\1", RegexOptions.Compiled);

        public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
        {
            ManipulateCommand(command);
            return base.ReaderExecuting(command, eventData, result);
        }

        public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
        {
            ManipulateCommand(command);
            return base.NonQueryExecuting(command, eventData, result);
        }

        public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
        {
            ManipulateCommand(command);
            return base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
        }

        public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
        {
            ManipulateCommand(command);
            return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
        }

        public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result)
        {
            ManipulateCommand(command);
            return base.ScalarExecuting(command, eventData, result);
        }

        public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<object> result, CancellationToken cancellationToken = default)
        {
            ManipulateCommand(command);
            return base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
        }

        private void ManipulateCommand(DbCommand result)
        {
              result.CommandText = regex.Replace(result.CommandText, "$2");
        }
}

Configuring The DbContext:

DbSet<MyEntityV1> V1s { get; set; }
DbSet<MyEntityV2> V2s { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.AddInterceptors(new EntityNameReplacerInterceptor());
}
澜川若宁 2024-10-26 00:50:15

要使用实体框架中的每个层次结构表 (TPH) 继承映射来解决此问题,请在模型中创建继承层次结构。这包括一个具有所有字段的实体和另一个具有字段子集的实体。此设置在大多数情况下保留原始表结构,同时适应所需字段较少的场景。

TPH的详细实现和示例可以参考微软官方文档实体类型层次结构映射,它提供有关实体框架中的继承映射策略的全面指导。

To address this with Table-per-Hierarchy (TPH) inheritance mapping in Entity Framework, create an inheritance hierarchy in your model. This includes an entity with all fields and another with a subset of fields. This setup maintains the original table structure for most cases while accommodating scenarios with fewer fields needed.

For detailed implementation and examples of TPH, refer to Microsoft's official documentation on Entity Type Hierarchy Mapping, which offers comprehensive guidance on inheritance mapping strategies in Entity Framework.

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