linq2db.EntityFrameworkCore icon indicating copy to clipboard operation
linq2db.EntityFrameworkCore copied to clipboard

`Merge()` fails with `StackOverflowException` when `OnTargetKey()` is being used

Open deimastep opened this issue 3 years ago • 0 comments

I might be doing something not as it is designed but I've spent more than 1 day to finally find out the reason. What I'm trying to do is to execute a SQL MERGE using EF Core 5 on MS SQL server.

I start merge by using Merge() method.

  • When I use OnTargetKey() method to define merge condition then query fails with StackOverflowException
  • When I use On() and manually define condition by matching source and target primary key then everything goes fine.

Packages in use: Microsoft.EntityFrameworkCore.SqlServer 5.0.14 linq2db.EntityFrameworkCore 5.9.0

P.S. I'm using LinqPad 6.15 to test this MERGE.

Source code

async Task Main()
{
	LinqToDB.Data.DataConnection.TurnTraceSwitchOn();
	LinqToDB.Data.DataConnection.WriteTraceLine = (s1, s2, l) =>
	{
		if (l <= TraceLevel.Info) Util.SqlOutputWriter.WriteLine(s1);
	};

	var dbOptions = new DbContextOptionsBuilder<DataContext>()
		.EnableSensitiveDataLogging()
		.UseSqlServer("Data Source=(LocalDb)\\MSSQLLocalDB;Initial Catalog=xxxxx;Integrated Security=True;")
		.Options;
	using var db = new DataContext(dbOptions);

	var id = 1;
	var systemId = "system";
	var ids = new List<int>() { id };
	var result = new List<object>();

	var resultEnum = db.Set<Account>()
		.Where(x => ids.Contains(x.Id))
		.Join(db.Set<AccountRevision>().Where(x => x.SystemId == systemId), x => x.Id, x => x.Id, (x, y) => y)
		.ToLinqToDB()
		.MergeInto(db.Set<AccountRevision>())
		.OnTargetKey() // Fails with StackOverfloshExcpetion
		//.On(x => new { x.Id, x.SystemId }, x => new { x.Id, x.SystemId }) // MERGE is successful
		.UpdateWhenMatched()
		.InsertWhenNotMatched()
		.MergeWithOutputAsync((s, x, y) => new { action = s, y.Id });

	await foreach (var item in resultEnum)
	{
		result.Add(item);
	}
	result.Dump("Merge result");
}

private class DataContext : DbContext
{
	public DataContext(DbContextOptions<DataContext> options) : base(options)
	{
	}

	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
		base.OnModelCreating(modelBuilder);
		modelBuilder.Entity<AccountRevision>(builder =>
		{
			builder.ToTable("AccountRevisions", "test");
			builder.HasKey(x => new { x.Id, x.SystemId });
			builder.Property(x => x.SystemId)
				.IsRequired(true)
				.HasMaxLength(20)
				.IsUnicode(false);
		});
		modelBuilder.Entity<Account>(builder =>
		{
			builder.ToTable("Accounts", "test");
			builder.HasKey(x => x.Id);
		});
	}
}

public class Account
{
	public int Id { get; set; }
}

public class AccountRevision
{
	public int Id { get; set; }
	
	public string SystemId {get; set;}
	
	public DateTime Timestamp {get; set;}
}

The DB schema is

if not exists(select * from sys.schemas where name = N'test')
	exec(N'create schema test authorization dbo')
go

create table test.Accounts(
	Id int not null,
	constraint [PK_test.Accounts] primary key (Id)
)

create table test.AccountRevisions(
	Id int not null,
	SystemId varchar(20) not null,
	[Timestamp] datetime2 not null
	constraint [PK_test.AccountRevisions] primary key (Id, SystemId)
)

drop table if exists test.AccountRevisions
drop table if exists test.Accounts
drop schema if exists test

deimastep avatar Feb 10 '22 14:02 deimastep