Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support For Table-Per-Concrete Inheritance #34

Merged
merged 5 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
695 changes: 21 additions & 674 deletions LICENSE

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions N.EntityFrameworkCore.Extensions.Test/Data/SqlExpression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.InteropServices.ObjectiveC;
using System.Text;
using System.Threading.Tasks;

namespace N.EntityFrameworkCore.Extensions.Sql
{
internal class SqlExpression
{
SqlExpressionType ExpressionType { get; }
List<object> Items { get; set; }
string Sql => ToSql();
string Alias { get; }
bool IsEmpty => Items.Count == 0;

internal SqlExpression(SqlExpressionType expressionType, object item, string alias = null)
{
ExpressionType = expressionType;
Items = new List<object>();
if (item is IEnumerable<string> values)
{
Items.AddRange(values.ToArray());
}
else
{
Items.Add(item);
}
Alias = alias;
}
internal SqlExpression(SqlExpressionType expressionType, object[] items, string alias = null)
{
ExpressionType = expressionType;
Items = new List<object>();
Items.AddRange(items);
Alias = alias;
}
internal static SqlExpression Columns(IEnumerable<string> columns)
{
return new SqlExpression(SqlExpressionType.Columns, columns);
}

internal static SqlExpression String(string joinOnCondition)
{
return new SqlExpression(SqlExpressionType.String, joinOnCondition);
}

internal static SqlExpression Table(string tableName, string alias = null)
{
return new SqlExpression(SqlExpressionType.Table, tableName, alias);
}

private string ToSql()
{
var values = Items.Select(o => o.ToString()).ToArray();
StringBuilder sbSql = new StringBuilder();
if (ExpressionType == SqlExpressionType.Columns)
{
sbSql.Append(string.Join(",", values.Select(c => c.StartsWith("$") || c.StartsWith("[") ? c : $"[{c}]")));
}
else
{
sbSql.Append(string.Join(",", Items.Select(o => o.ToString())));
}
if (Alias != null)
{
sbSql.Append(" ");
sbSql.Append(SqlKeyword.As.ToString().ToUpper());
sbSql.Append(" ");
sbSql.Append(Alias);
}
//var test = Items.Select(o => o.ToString()).ToArray();
return sbSql.ToString();
}
}
}
13 changes: 12 additions & 1 deletion N.EntityFrameworkCore.Extensions.Test/Data/TestDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using N.EntityFrameworkCore.Extensions.Test.Common;
using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection.Metadata;
using System.Runtime.ConstrainedExecution;

namespace N.EntityFrameworkCore.Extensions.Test.Data
Expand All @@ -11,9 +12,13 @@ public class TestDbContext : DbContext
public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<ProductWithComplexKey> ProductsWithComplexKey { get; set; }
public virtual DbSet<Order> Orders { get; set; }
public virtual DbSet<TpcPerson> TpcPeople { get; set; }
public virtual DbSet<TphPerson> TphPeople { get; set; }
public virtual DbSet<TphCustomer> TphCustomers { get; set; }
public virtual DbSet<TphVendor> TphVendors { get; set; }
public virtual DbSet<TptPerson> TptPeople { get; set; }
public virtual DbSet<TptCustomer> TptCustomers { get; set; }
public virtual DbSet<TptVendor> TptVendors { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
Expand All @@ -22,12 +27,18 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TphPerson>().Property<DateTime>("CreatedDate");
modelBuilder.Entity<ProductWithComplexKey>().HasKey(c => new { c.Key1 });
modelBuilder.Entity<ProductWithComplexKey>().Property<Guid>("Key1").HasDefaultValueSql("newsequentialid()");
modelBuilder.Entity<ProductWithComplexKey>().Property<Guid>("Key2").HasDefaultValueSql("newsequentialid()");
modelBuilder.Entity<ProductWithComplexKey>().Property<Guid>("Key3").HasDefaultValueSql("newsequentialid()");
modelBuilder.Entity<Order>().Property<DateTime>("DbAddedDateTime").HasDefaultValueSql("getdate()");
modelBuilder.Entity<TpcPerson>().UseTpcMappingStrategy();
modelBuilder.Entity<TpcCustomer>().ToTable("TpcCustomer");
modelBuilder.Entity<TpcVendor>().ToTable("TpcVendor");
modelBuilder.Entity<TphPerson>().Property<DateTime>("CreatedDate");
modelBuilder.Entity<TptPerson>().ToTable("TptPeople");
modelBuilder.Entity<TptCustomer>().ToTable("TptCustomer");
modelBuilder.Entity<TptVendor>().ToTable("TptVendor");
}
}
}
11 changes: 11 additions & 0 deletions N.EntityFrameworkCore.Extensions.Test/Data/TpcCustomer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace N.EntityFrameworkCore.Extensions.Test.Data
{
public class TpcCustomer : TpcPerson
{
public string Email { get; set; }
public string Phone { get; set; }
public DateTime AddedDate { get; set; }
}
}
12 changes: 12 additions & 0 deletions N.EntityFrameworkCore.Extensions.Test/Data/TpcPerson.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace N.EntityFrameworkCore.Extensions.Test.Data
{
public abstract class TpcPerson
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
10 changes: 10 additions & 0 deletions N.EntityFrameworkCore.Extensions.Test/Data/TpcVendor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

namespace N.EntityFrameworkCore.Extensions.Test.Data
{
public class TpcVendor : TpcPerson
{
public string Email { get; set; }
public string Phone { get; set; }
public string Url { get; set; }
}
}
11 changes: 11 additions & 0 deletions N.EntityFrameworkCore.Extensions.Test/Data/TptCustomer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace N.EntityFrameworkCore.Extensions.Test.Data
{
public class TptCustomer : TptPerson
{
public string Email { get; set; }
public string Phone { get; set; }
public DateTime AddedDate { get; set; }
}
}
12 changes: 12 additions & 0 deletions N.EntityFrameworkCore.Extensions.Test/Data/TptPerson.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace N.EntityFrameworkCore.Extensions.Test.Data
{
public class TptPerson
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
10 changes: 10 additions & 0 deletions N.EntityFrameworkCore.Extensions.Test/Data/TptVendor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

namespace N.EntityFrameworkCore.Extensions.Test.Data
{
public class TptVendor : TptPerson
{
public string Email { get; set; }
public string Phone { get; set; }
public string Url { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,44 @@ public void With_Default_Options()
Assert.IsTrue(newTotal - oldTotal == rowsInserted, "The new count minus the old count should match the number of rows inserted.");
}
[TestMethod]
public void With_Default_Options_Tph()
public void With_Inheritance_Tpc()
{
var dbContext = SetupDbContext(false);
var customers = new List<TpcCustomer>();
var vendors = new List<TpcVendor>();
for (int i = 0; i < 20000; i++)
{
customers.Add(new TpcCustomer
{
Id = i,
FirstName = string.Format("John_{0}", i),
LastName = string.Format("Smith_{0}", i),
Email = string.Format("john.smith{0}@domain.com", i),
AddedDate = DateTime.UtcNow
});
}
for (int i = 20000; i < 30000; i++)
{
vendors.Add(new TpcVendor
{
Id = i,
FirstName = string.Format("Mike_{0}", i),
LastName = string.Format("Smith_{0}", i),
Email = string.Format("mike.smith{0}@domain.com", i),
Url = string.Format("http://domain.com/mike.smith{0}", i)
});
}
int oldTotal = dbContext.TpcPeople.Count();
int customerRowsInserted = dbContext.BulkInsert(customers, o => o.UsePermanentTable = true);
int vendorRowsInserted = dbContext.BulkInsert(vendors, o => o.UsePermanentTable = true);
int rowsInserted = customerRowsInserted + vendorRowsInserted;
int newTotal = dbContext.TpcPeople.Count();

Assert.IsTrue(rowsInserted == customers.Count + vendors.Count, "The number of rows inserted must match the count of order list");
Assert.IsTrue(newTotal - oldTotal == rowsInserted, "The new count minus the old count should match the number of rows inserted.");
}
[TestMethod]
public void With_Inheritance_Tph()
{
var dbContext = SetupDbContext(false);
var customers = new List<TphCustomer>();
Expand Down Expand Up @@ -100,6 +137,44 @@ public void With_Default_Options_Tph()
Assert.IsTrue(newTotal - oldTotal == rowsInserted, "The new count minus the old count should match the number of rows inserted.");
}
[TestMethod]
public void With_Inheritance_Tpt()
{
var dbContext = SetupDbContext(false);
var customers = new List<TptCustomer>();
var vendors = new List<TptVendor>();
for (int i = 0; i < 20000; i++)
{
customers.Add(new TptCustomer
{
Id = i,
FirstName = string.Format("John_{0}", i),
LastName = string.Format("Smith_{0}", i),
Email = string.Format("john.smith{0}@domain.com", i),
Phone = "777-555-1234",
AddedDate = DateTime.UtcNow
});
}
for (int i = 20000; i < 30000; i++)
{
vendors.Add(new TptVendor
{
Id = i,
FirstName = string.Format("Mike_{0}", i),
LastName = string.Format("Smith_{0}", i),
Email = string.Format("mike.smith{0}@domain.com", i),
Url = string.Format("http://domain.com/mike.smith{0}", i)
});
}
int oldTotal = dbContext.TptPeople.Count();
int customerRowsInserted = dbContext.BulkInsert(customers, o => o.UsePermanentTable = true);
int vendorRowsInserted = dbContext.BulkInsert(vendors);
int rowsInserted = customerRowsInserted + vendorRowsInserted;
int newTotal = dbContext.TptPeople.Count();

Assert.IsTrue(rowsInserted == customers.Count + vendors.Count, "The number of rows inserted must match the count of order list");
Assert.IsTrue(newTotal - oldTotal == rowsInserted, "The new count minus the old count should match the number of rows inserted.");
}
[TestMethod]
public void Without_Identity_Column()
{
var dbContext = SetupDbContext(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,44 @@ public async Task With_Default_Options()
Assert.IsTrue(newTotal - oldTotal == rowsInserted, "The new count minus the old count should match the number of rows inserted.");
}
[TestMethod]
public async Task With_Default_Options_Tph()
public async Task With_Inheritance_Tpc()
{
var dbContext = SetupDbContext(false);
var customers = new List<TpcCustomer>();
var vendors = new List<TpcVendor>();
for (int i = 0; i < 20000; i++)
{
customers.Add(new TpcCustomer
{
Id = i,
FirstName = string.Format("John_{0}", i),
LastName = string.Format("Smith_{0}", i),
Email = string.Format("john.smith{0}@domain.com", i),
AddedDate = DateTime.UtcNow
});
}
for (int i = 20000; i < 30000; i++)
{
vendors.Add(new TpcVendor
{
Id = i,
FirstName = string.Format("Mike_{0}", i),
LastName = string.Format("Smith_{0}", i),
Email = string.Format("mike.smith{0}@domain.com", i),
Url = string.Format("http://domain.com/mike.smith{0}", i)
});
}
int oldTotal = dbContext.TpcPeople.Count();
int customerRowsInserted = await dbContext.BulkInsertAsync(customers);
int vendorRowsInserted = await dbContext.BulkInsertAsync(vendors);
int rowsInserted = customerRowsInserted + vendorRowsInserted;
int newTotal = dbContext.TpcPeople.Count();

Assert.IsTrue(rowsInserted == customers.Count + vendors.Count, "The number of rows inserted must match the count of order list");
Assert.IsTrue(newTotal - oldTotal == rowsInserted, "The new count minus the old count should match the number of rows inserted.");
}
[TestMethod]
public async Task With_Inheritance_Tph()
{
var dbContext = SetupDbContext(false);
var customers = new List<TphCustomer>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,37 @@ public void With_Default_Options()
Assert.IsTrue(areUpdatedOrdersMerged, "The orders that were updated did not merge correctly");
}
[TestMethod]
public void With_Default_Options_Tph()
public void With_Inheritance_Tpc()
{
var dbContext = SetupDbContext(true);
var customers = dbContext.TpcPeople.Where(o => o.Id <= 1000).OfType<TpcCustomer>().ToList();
int customersToAdd = 5000;
int customersToUpdate = customers.Count;
foreach (var customer in customers)
{
customer.FirstName = "BulkMerge_Tpc_Update";
}
for (int i = 0; i < customersToAdd; i++)
{
customers.Add(new TpcCustomer
{
Id = 10000 + i,
FirstName = "BulkMerge_Tpc_Add",
AddedDate = DateTime.UtcNow
});
}
var result = dbContext.BulkMerge(customers, options => { options.MergeOnCondition = (s, t) => s.Id == t.Id; });
int customersAdded = dbContext.TpcPeople.Where(o => o.FirstName == "BulkMerge_Tpc_Add").OfType<TpcCustomer>().Count();
int customersUpdated = dbContext.TpcPeople.Where(o => o.FirstName == "BulkMerge_Tpc_Update").OfType<TpcCustomer>().Count();

Assert.IsTrue(result.RowsAffected == customers.Count, "The number of rows inserted must match the count of customer list");
Assert.IsTrue(result.RowsUpdated == customersToUpdate, "The number of rows updated must match");
Assert.IsTrue(result.RowsInserted == customersToAdd, "The number of rows added must match");
Assert.IsTrue(customersToAdd == customersAdded, "The custmoers that were added did not merge correctly");
Assert.IsTrue(customersToUpdate == customersUpdated, "The customers that were updated did not merge correctly");
}
[TestMethod]
public void With_Inheritance_Tph()
{
var dbContext = SetupDbContext(true);
var customers = dbContext.TphPeople.Where(o => o.Id <= 1000).OfType<TphCustomer>().ToList();
Expand Down
Loading
Loading