Skip to content

Instantly share code, notes, and snippets.

@to11mtm
Last active March 11, 2025 20:28
Show Gist options
  • Save to11mtm/5fc04f7409a0d156c2fd36794617191e to your computer and use it in GitHub Desktop.
Save to11mtm/5fc04f7409a0d156c2fd36794617191e to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Linq2Db.EfCore.Samples.Tests.Microsoft.EntityFrameworkCore.Infrastructure;
using LinqToDB.Data;
using LinqToDB.EntityFrameworkCore;
using LinqToDB.Mapping;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.ValueGeneration;
using Microsoft.Extensions.Logging;
using Xunit;
using Xunit.Abstractions;
namespace Linq2Db.EfCore.Samples.Tests
{
public class XunitLogger : ILogger, IDisposable
{
private ITestOutputHelper _output;
public XunitLogger(ITestOutputHelper output)
{
_output = output;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
_output?.WriteLine(state.ToString());
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public IDisposable BeginScope<TState>(TState state)
{
return this;
}
public void Dispose()
{
}
}
class TestOutputHelperLoggerFactory : ILoggerFactory
{
private readonly ITestOutputHelper _output;
public TestOutputHelperLoggerFactory(ITestOutputHelper output)
{
_output = output;
}
public void Dispose()
{
}
public ILogger CreateLogger(string categoryName)
{
return new XunitLogger(output: _output);
}
public void AddProvider(ILoggerProvider provider)
{
throw new NotImplementedException();
}
}
namespace Microsoft.EntityFrameworkCore.Infrastructure
{
public class AwesomeModelCustomizer : RelationalModelCustomizer
{
public AwesomeModelCustomizer(ModelCustomizerDependencies dependencies)
: base(dependencies) { }
public override void Customize(ModelBuilder modelBuilder, DbContext context)
{
foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes())
{
if (mutableEntityType.IsKeyless)
continue;
var keys = mutableEntityType.GetKeys().ToList();
foreach (var k in keys)
{
foreach (var mutableProperty in k.Properties)
{
if (keys.Count==1 && mutableProperty.ClrType == typeof(decimal) && mutableProperty.IsPrimaryKey())
{
mutableProperty.SetColumnType("INTEGER");
mutableProperty.ValueGenerated = ValueGenerated.OnAdd;
mutableProperty.SetValueGeneratorFactory((p,a)=> new TestDecimalValueGeneratorFactory(p));
}
}
}
}
base.Customize(modelBuilder, context);
}
}
}
public abstract class EFCoreSqliteTestBase<TCtx> where TCtx : DbContext
{
private readonly Func<TCtx> _factory;
private readonly SqliteConnection _connection;
private readonly DbContextOptions<TCtx> _contextOptions;
/// <summary>
/// Use this when logic in your constructor needs to be created.
/// </summary>
protected readonly bool Created;
private readonly SqliteConnectionStringBuilder _cstr;
private readonly TestHarnessIDbContextFactory _contextFact;
private static int counter;
protected EFCoreSqliteTestBase(string testClassName, ITestOutputHelper logOutput = null,
string forceFile = null)
{
DataConnection.TurnTraceSwitchOn();
// Create and open a connection. This creates the SQLite in-memory database, which will persist until the connection is closed
// at the end of the test (see Dispose below).
_cstr = new SqliteConnectionStringBuilder()
{
Mode = string.IsNullOrWhiteSpace(forceFile)? SqliteOpenMode.Memory :SqliteOpenMode.ReadWriteCreate, Cache = SqliteCacheMode.Shared,
DataSource = string.IsNullOrWhiteSpace(forceFile)? $"{testClassName}-{Interlocked.Increment(ref counter)}": forceFile, Pooling = true
};
_connection = new SqliteConnection(_cstr.ConnectionString);
_connection.Open();
// These options will be used by the context instances in this test suite, including the connection opened above.
_contextOptions = new DbContextOptionsBuilder<TCtx>()
.ReplaceService<IModelCustomizer, AwesomeModelCustomizer>()
.UseSqlite(_cstr.ConnectionString)
.UseLoggerFactory(new TestOutputHelperLoggerFactory(logOutput))
.EnableSensitiveDataLogging()
.Options;
LinqToDB.EntityFrameworkCore.LinqToDBForEFTools.Initialize();
// Create the schema and seed some data
using var context = CreateDbContext();
_contextFact = new TestHarnessIDbContextFactory(this);
Created = context.Database.EnsureCreated();
}
protected TCtx CreateDbContext()
{
return ((TCtx)Activator.CreateInstance(typeof(TCtx),_contextOptions));
}
protected IDbContextFactory<TCtx> CreateDbContextFactory()
{
return _contextFact;
}
public class TestHarnessIDbContextFactory : IDbContextFactory<TCtx>
{
private readonly EFCoreSqliteTestBase<TCtx> _parent;
public TestHarnessIDbContextFactory(EFCoreSqliteTestBase<TCtx> options)
{
_parent = options;
}
public TCtx CreateDbContext()
{
return _parent.CreateDbContext();
}
}
}
}
namespace Linq2Db.EfCore.Samples.Tests.Microsoft.EntityFrameworkCore.Infrastructure
{
public class TestDecimalValueGeneratorFactory : ValueGenerator
{
private static long _counter;
public TestDecimalValueGeneratorFactory(IProperty property)
{
}
protected override object NextValue(EntityEntry entry)
{
return (decimal)Interlocked.Increment(ref _counter);
}
public override bool GeneratesTemporaryValues => false;
}
}
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Xunit.Abstractions;
namespace Linq2Db.EfCore.Samples.Tests;
public class UnitTest1
{
public class TestTable
{
public decimal Id { get; set; }
public string What { get; set; }
public Decimal? NullableDec { get; set; }
}
public class TestContext : DbContext
{
public TestContext(DbContextOptions<TestContext> options) : base(options)
{
}
public DbSet<TestTable> TableTest { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TestTable>().Property(x => x.Id).ValueGeneratedOnAdd();
modelBuilder.Entity<TestTable>().HasKey(x => x.Id);
modelBuilder.Entity<TestTable>().Property(x => x.NullableDec).HasMaxLength(10);
modelBuilder.Entity<TestTable>().Property(x => x.What)
.HasColumnType("VARCHAR").HasMaxLength(100);
base.OnModelCreating(modelBuilder);
}
}
public class TestBaseTest : EFCoreSqliteTestBase<TestContext>
{
private readonly ITestOutputHelper outLogger;
public TestBaseTest(ITestOutputHelper logOutput = null, string forceFile = null) : base(nameof(TestBaseTest), logOutput, forceFile)
{
outLogger = logOutput;
using (var ctx = CreateDbContext())
{
using (var l2dbCtx = ctx.CreateLinqToDBConnection())
{
l2dbCtx.DropTable<TestTable>(ctx.TableTest.ToLinqToDBTable(l2dbCtx).TableName);
l2dbCtx.CreateTable<TestTable>(ctx.TableTest.ToLinqToDBTable(l2dbCtx).TableName);
}
}
}
[Fact]
public void ThisWorks()
{
using (var ctx = CreateDbContext())
{
ctx.TableTest.Add(new TestTable{ What="hello"});
ctx.SaveChanges();
Assert.True(ctx.TableTest.Any());
outLogger.WriteLine(JsonSerializer.Serialize(ctx.TableTest.FirstOrDefault()));
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment