如何解决我可以使用无键实体类型在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!
另外(这是可选的),我创建了可以轻松继承的基本类型Change
和Change<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>
。这样,您可以删除ProductChange
,OrderChange
等类。但是,您必须指定列名,以便Entity Framework Core可以将ID
的{{1}}属性映射到Change<Product>
,并将ProductID
的{{1}}属性理解为}映射到ID
等。我选择不执行此操作,因为如果您有复合键,则此方法将不起作用。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。