Skip to content

Commit

Permalink
Merge pull request #34 from NorthernLight1/development
Browse files Browse the repository at this point in the history
Add Support For Table-Per-Concrete Inheritance
  • Loading branch information
NorthernLight1 authored Dec 17, 2023
2 parents 8f13991 + 1182733 commit 4ad0e73
Show file tree
Hide file tree
Showing 27 changed files with 758 additions and 732 deletions.
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

0 comments on commit 4ad0e73

Please sign in to comment.