微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

我可以使用无键实体类型在Entity Framework Core中查询CHANGETABLE吗?

如何解决我可以使用无键实体类型在Entity Framework Core中查询CHANGETABLE吗?

我正在使用sql Server更改跟踪,并且试图将本文从Microsoft Docs改编为Entity Framework应用程序:Work with Change Tracking

我想使用实体框架运行此SQL查询

SELECT
    P.*,CT.*
FROM
    dbo.Product AS P
RIGHT OUTER JOIN
    CHANGETABLE(CHANGES dbo.Product,@last_synchronization_version) AS CT
ON
    P.ProductID = CT.ProductID

这是到目前为止我得到的:

public class Product
{
    public int ProductID { get; set; }

    // omitted dozens of other properties
}

public class ProductChange
{
    public int ProductID { get; set; }

    public Product? Product { get; set; }

    public long SYS_CHANGE_VERSION { get; set; }

    public long? SYS_CHANGE_CREATION_VERSION { get; set; }

    public char SYS_CHANGE_OPERATION { get; set; }

    public byte[]? SYS_CHANGE_COLUMNS { get; set; }

    public byte[]? SYS_CHANGE_CONTEXT { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<ProductChange>()
        .HasNoKey()
        .ToView(null);

    base.OnModelCreating(modelBuilder);
}
long lastSynchronizationVersion = ...; // obtained as described in "Work with Change Tracking"

const string sql = @"
    SELECT
        P.*,CT.*
    FROM
        dbo.Product AS P
    RIGHT OUTER JOIN
        CHANGETABLE(CHANGES dbo.Product,{0}) AS CT
    ON
        P.ProductID = CT.ProductID";

var changes = await dbContext.Set<ProductChange>.FromsqlRaw(sql,lastSynchronizationVersion);

它不起作用,因为EF无法理解P.*如何映射到public Product? Product { get; set; }。当我删除Product属性并从查询删除P.*时,一切按预期进行。但是,我需要所有属性,而不仅仅是ID。

Product的所有属性复制到ProductChange并使它们全部为可为空的作品,但是我真的不想诉诸于此。

在实践中,我将不仅对产品使用变更跟踪,而且还将对数十种实体类型使用变更跟踪,它们都具有许多属性。仅在两个位置指定每个属性只是为了使Entity Framework与Change Tracking配合使用不是一个好主意。

有没有一种方法可以使无密钥实体类型执行我想要的工作?还是我必须使用ADO.NET的ExecuteReader并手动映射结果?

解决方法

事实证明,您可以将关系与无键实体类型上的导航属性一起使用,就像对实体类型一样。

OnModelCreating中配置关系:

modelBuilder.Entity<ProductChange>()
    .HasNoKey()
    .HasOne(x => x.Entity).WithMany().HasForeignKey(x => x.ProductID) // I added this line.
    .ToView(null);

确保外键属性(在我的示例中为ProductID)可以为空。为了使Entity Framework Core了解该关系是可选的,这是必需的。毕竟,如果更改是删除操作,Entity将为空。

最后,使用Include代替手动联接表:

const string sql = "SELECT * FROM CHANGETABLE(CHANGES dbo.Product,{0}) AS CT";

var changes = await dbContext
    .Set<ProductChange>
    .FromSqlRaw(sql,lastSynchronizationVersion)
    .Include(x => x.Entity)
    .ToArrayAsync();

// it works!

另外(这是可选的),我创建了可以轻松继承的基本类型ChangeChange<TEntity>

public abstract class Change
{
    public long Version { get; set; }
    public long? CreationVersion { get; set; }
    public char Operation { get; set; }
    public byte[]? Columns { get; set; }
    public byte[]? Context { get; set; }
}

public abstract class Change<TEntity> : Change
    where TEntity : class
{
    public TEntity? Entity { get; set; }
}
public ProductChange : Change<Product>
{
    public int? ProductID { get; set; }
}

public OrderChange : Change<Order>
{
    public int? OrderID { get; set; }
}

// etc...

您将必须为OnModelCreating中的每个派生类型配置关系,但是如果添加以下内容,则不必每次都重复HasNoKey()ToView(null)。>

foreach (var changeType in modelBuilder.Model.FindLeastDerivedEntityTypes(typeof(Change)))
{
    var builder = modelBuilder.Entity(changeType.ClrType).HasNoKey().ToView(null);

    builder.Property(nameof(Change.Version)).HasColumnName("SYS_CHANGE_VERSION");
    builder.Property(nameof(Change.CreationVersion)).HasColumnName("SYS_CHANGE_CREATION_VERSION");
    builder.Property(nameof(Change.Operation)).HasColumnName("SYS_CHANGE_OPERATION");
    builder.Property(nameof(Change.Columns)).HasColumnName("SYS_CHANGE_COLUMNS");
    builder.Property(nameof(Change.Context)).HasColumnName("SYS_CHANGE_CONTEXT");
}

如果需要,可以将ID属性移动到Change<TEntity>。这样,您可以删除ProductChangeOrderChange等类。但是,您必须指定列名,以便Entity Framework Core可以将ID的{​​{1}}属性映射到Change<Product>,并将ProductID的{​​{1}}属性理解为}映射到ID等。我选择不执行此操作,因为如果您有复合键,则此方法将不起作用。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。