diff --git a/aspnet-core/Lion.AbpPro.sln b/aspnet-core/Lion.AbpPro.sln
index 9d07d20c..172a82bc 100644
--- a/aspnet-core/Lion.AbpPro.sln
+++ b/aspnet-core/Lion.AbpPro.sln
@@ -246,6 +246,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lion.AbpPro.Cli.Core", "fra
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lion.AbpPro.Cli", "frameworks\src\Lion.AbpPro.Cli\Lion.AbpPro.Cli.csproj", "{B7A68103-D527-421F-8247-5D169A7F8931}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lion.AbpPro.EntityFrameworkCore", "frameworks\src\Lion.AbpPro.EntityFrameworkCore\Lion.AbpPro.EntityFrameworkCore.csproj", "{496667FC-2D9A-4CEF-9CC3-A372E9B9C002}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lion.AbpPro.EntityFrameworkCore.Mysql", "frameworks\src\Lion.AbpPro.EntityFrameworkCore.Mysql\Lion.AbpPro.EntityFrameworkCore.Mysql.csproj", "{2CDD9AF8-AEE1-43EA-B014-DAFE4D3D7DD1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lion.AbpPro.EntityFrameworkCore.Tests", "frameworks\test\Lion.AbpPro.EntityFrameworkCore.Mysql.Tests\Lion.AbpPro.EntityFrameworkCore.Tests.csproj", "{AEFB2D25-29F4-4CE2-820D-A74EFB6A56B2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -600,6 +606,18 @@ Global
{B7A68103-D527-421F-8247-5D169A7F8931}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7A68103-D527-421F-8247-5D169A7F8931}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7A68103-D527-421F-8247-5D169A7F8931}.Release|Any CPU.Build.0 = Release|Any CPU
+ {496667FC-2D9A-4CEF-9CC3-A372E9B9C002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {496667FC-2D9A-4CEF-9CC3-A372E9B9C002}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {496667FC-2D9A-4CEF-9CC3-A372E9B9C002}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {496667FC-2D9A-4CEF-9CC3-A372E9B9C002}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2CDD9AF8-AEE1-43EA-B014-DAFE4D3D7DD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2CDD9AF8-AEE1-43EA-B014-DAFE4D3D7DD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2CDD9AF8-AEE1-43EA-B014-DAFE4D3D7DD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2CDD9AF8-AEE1-43EA-B014-DAFE4D3D7DD1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AEFB2D25-29F4-4CE2-820D-A74EFB6A56B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AEFB2D25-29F4-4CE2-820D-A74EFB6A56B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AEFB2D25-29F4-4CE2-820D-A74EFB6A56B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AEFB2D25-29F4-4CE2-820D-A74EFB6A56B2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -717,6 +735,9 @@ Global
{68C902A2-A604-4F3A-879D-37941C00C7A9} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3}
{8D5BD955-FFDC-4895-927F-624C42B64A92} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3}
{B7A68103-D527-421F-8247-5D169A7F8931} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3}
+ {496667FC-2D9A-4CEF-9CC3-A372E9B9C002} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3}
+ {2CDD9AF8-AEE1-43EA-B014-DAFE4D3D7DD1} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3}
+ {AEFB2D25-29F4-4CE2-820D-A74EFB6A56B2} = {EFC415F8-872F-4C7E-8645-31A51481BCFC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F}
diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/GlobalUsings.cs b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/GlobalUsings.cs
new file mode 100644
index 00000000..80deef1d
--- /dev/null
+++ b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/GlobalUsings.cs
@@ -0,0 +1,12 @@
+// Global using directives
+
+global using Lion.AbpPro.EntityFrameworkCore;
+global using Microsoft.EntityFrameworkCore;
+global using Microsoft.EntityFrameworkCore.Storage;
+global using MySqlConnector;
+global using Volo.Abp.DependencyInjection;
+global using Volo.Abp.Domain.Entities;
+global using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
+global using Volo.Abp.EntityFrameworkCore;
+global using Volo.Abp.EntityFrameworkCore.MySQL;
+global using Volo.Abp.Modularity;
\ No newline at end of file
diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion.AbpPro.EntityFrameworkCore.Mysql.csproj b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion.AbpPro.EntityFrameworkCore.Mysql.csproj
new file mode 100644
index 00000000..89a576e3
--- /dev/null
+++ b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion.AbpPro.EntityFrameworkCore.Mysql.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net7.0
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion/AbpPro/EntityFrameworkCore/Mysql/EfCoreBulkOperationProvider.cs b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion/AbpPro/EntityFrameworkCore/Mysql/EfCoreBulkOperationProvider.cs
new file mode 100644
index 00000000..4f340502
--- /dev/null
+++ b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion/AbpPro/EntityFrameworkCore/Mysql/EfCoreBulkOperationProvider.cs
@@ -0,0 +1,45 @@
+using Volo.Abp.Auditing;
+
+namespace Lion.AbpPro.EntityFrameworkCore.Mysql;
+
+public class EfCoreBulkOperationProvider : IEfCoreBulkOperationProvider, ITransientDependency
+{
+ ///
+ /// 批量新增
+ ///
+ ///
+ ///
+ /// - mysql启用:SET GLOBAL local_infile = true;
+ ///
+ ///
+ /// - 数据库连接字符串需要加上:AllowLoadLocalInfile=true
+ ///
+ /// - abp的审计字段需要手动赋值,比如创建人,创建时间,或者使用AuditPropertySetter
+ ///
+ /// - 只支持单表,比如有一个Blog表和Post表一对多关系,需要调用两次 InsertManyAsync
+ ///
+ ///
+ public virtual async Task InsertManyAsync(IEfCoreRepository repository, IEnumerable entities, bool autoSave, CancellationToken cancellationToken)
+ where TDbContext : IEfCoreDbContext where TEntity : class, IEntity
+ {
+ var dbContext = await repository.GetDbContextAsync();
+ var dbTransaction = dbContext.Database.CurrentTransaction?.GetDbTransaction();
+ await dbContext.BulkInsertAsync(entities, dbTransaction as MySqlTransaction, cancellationToken);
+ if (autoSave)
+ {
+ await dbContext.SaveChangesAsync(cancellationToken);
+ }
+ }
+
+ public virtual async Task UpdateManyAsync(IEfCoreRepository repository, IEnumerable entities, bool autoSave, CancellationToken cancellationToken)
+ where TDbContext : IEfCoreDbContext where TEntity : class, IEntity
+ {
+ await repository.UpdateManyAsync(entities, autoSave, cancellationToken);
+ }
+
+ public virtual async Task DeleteManyAsync(IEfCoreRepository repository, IEnumerable entities, bool autoSave, CancellationToken cancellationToken)
+ where TDbContext : IEfCoreDbContext where TEntity : class, IEntity
+ {
+ await repository.DeleteManyAsync(entities, autoSave, cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion/AbpPro/EntityFrameworkCore/Mysql/LionAbpProEntityFrameworkCoreMysqlModule.cs b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion/AbpPro/EntityFrameworkCore/Mysql/LionAbpProEntityFrameworkCoreMysqlModule.cs
new file mode 100644
index 00000000..46cb2709
--- /dev/null
+++ b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion/AbpPro/EntityFrameworkCore/Mysql/LionAbpProEntityFrameworkCoreMysqlModule.cs
@@ -0,0 +1,6 @@
+namespace Lion.AbpPro.EntityFrameworkCore.Mysql;
+
+[DependsOn(typeof(AbpEntityFrameworkCoreMySQLModule))]
+public class LionAbpProEntityFrameworkCoreMysqlModule : AbpModule
+{
+}
\ No newline at end of file
diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/System/Linq/MySQLBulkInsertExtensions.cs b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/System/Linq/MySQLBulkInsertExtensions.cs
new file mode 100644
index 00000000..89880336
--- /dev/null
+++ b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/System/Linq/MySQLBulkInsertExtensions.cs
@@ -0,0 +1,45 @@
+namespace System.Linq
+{
+ public static class MySQLBulkInsertExtensions
+ {
+ public static async Task BulkInsertAsync(this DbContext dbCtx, IEnumerable items, MySqlTransaction transaction = null, CancellationToken cancellationToken = default) where TEntity : class
+ {
+ var conn = dbCtx.Database.GetDbConnection();
+ await conn.OpenIfNeededAsync(cancellationToken);
+ var dataTable = BulkInsertUtils.BuildDataTable(dbCtx, dbCtx.Set(), items);
+ var bulkCopy = BuildSqlBulkCopy((MySqlConnection)conn, dbCtx, transaction);
+ await bulkCopy.WriteToServerAsync(dataTable, cancellationToken);
+ }
+
+ public static void BulkInsert(this DbContext dbCtx, IEnumerable items, MySqlTransaction transaction = null, CancellationToken cancellationToken = default) where TEntity : class
+ {
+ var conn = dbCtx.Database.GetDbConnection();
+ conn.OpenIfNeeded();
+ var dataTable = BulkInsertUtils.BuildDataTable(dbCtx, dbCtx.Set(), items);
+ var bulkCopy = BuildSqlBulkCopy((MySqlConnection)conn, dbCtx, transaction);
+ bulkCopy.WriteToServer(dataTable);
+ }
+
+ private static MySqlBulkCopy BuildSqlBulkCopy(MySqlConnection conn, DbContext dbCtx, MySqlTransaction transaction = null) where TEntity : class
+ {
+ var dbSet = dbCtx.Set();
+ var entityType = dbSet.EntityType;
+ var dbProps = BulkInsertUtils.ParseDbProps(dbCtx, entityType);
+
+ var bulkCopy = new MySqlBulkCopy(conn, transaction)
+ {
+ DestinationTableName = entityType.GetTableName() //Schema is not supported by MySQL
+ };
+
+ var sourceOrdinal = 0;
+ foreach (var dbProp in dbProps)
+ {
+ var columnName = dbProp.ColumnName;
+ bulkCopy.ColumnMappings.Add(new MySqlBulkCopyColumnMapping(sourceOrdinal, columnName));
+ sourceOrdinal++;
+ }
+
+ return bulkCopy;
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/GlobalUsings.cs b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/GlobalUsings.cs
new file mode 100644
index 00000000..b8093f78
--- /dev/null
+++ b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/GlobalUsings.cs
@@ -0,0 +1,19 @@
+// Global using directives
+
+global using System.Collections;
+global using System.Data;
+global using System.Data.Common;
+global using System.Linq.Expressions;
+global using System.Reflection;
+global using System.Text;
+global using Microsoft.EntityFrameworkCore;
+global using Microsoft.EntityFrameworkCore.Infrastructure;
+global using Microsoft.EntityFrameworkCore.Metadata;
+global using Microsoft.EntityFrameworkCore.Query.Internal;
+global using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+global using Microsoft.EntityFrameworkCore.Storage;
+global using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+global using Volo.Abp.DependencyInjection;
+global using Volo.Abp.Domain.Entities;
+global using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
+global using Volo.Abp.EntityFrameworkCore;
\ No newline at end of file
diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/Lion.AbpPro.EntityFrameworkCore.csproj b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/Lion.AbpPro.EntityFrameworkCore.csproj
new file mode 100644
index 00000000..4c758be2
--- /dev/null
+++ b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/Lion.AbpPro.EntityFrameworkCore.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/Lion/AbpPro/EntityFrameworkCore/BatchUtils.cs b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/Lion/AbpPro/EntityFrameworkCore/BatchUtils.cs
new file mode 100644
index 00000000..63895328
--- /dev/null
+++ b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/Lion/AbpPro/EntityFrameworkCore/BatchUtils.cs
@@ -0,0 +1,190 @@
+namespace Lion.AbpPro.EntityFrameworkCore
+{
+ public static class BatchUtils
+ {
+ public static bool IsNullableType(Type type)
+ {
+ return type.IsGenericType
+ && type.GetGenericTypeDefinition() == typeof(Nullable<>);
+ }
+
+ private static IProperty GetPKProperty(DbSet dbSet) where TEntity : class
+ {
+ var pkProps = dbSet.EntityType.FindPrimaryKey().Properties;
+ if (pkProps.Count != 1)
+ {
+ throw new ArgumentException("Only entity types with one single primary key are supported.");
+ }
+ return pkProps[0];
+
+ }
+
+
+ public static string GetPKColName(DbSet dbSet) where TEntity : class
+ {
+ var pkProp = GetPKProperty(dbSet);
+ string pkColName = pkProp.GetColumnName(StoreObjectIdentifier.SqlQuery(dbSet.EntityType));
+ return pkColName;
+ }
+
+ private static Expression> GetSelectPkExpression(DbContext dbCtx) where TEntity : class
+ {
+
+ var parameter = Expression.Parameter(typeof(TEntity), "e");
+ var pkProp = GetPKProperty(dbCtx.Set());
+ string pkPropName = pkProp.Name;
+
+ var body = Expression.Convert(Expression.MakeMemberAccess(parameter, typeof(TEntity).GetProperty(pkPropName)),typeof(object));
+ return (Expression>)Expression.Lambda(body, parameter);
+ }
+
+ public static string BuildWhereSubQuery(IQueryable queryable, DbContext dbCtx, string aliasSeparator) where TEntity : class
+ {
+ IQueryProvider queryProvider = queryable.Provider;
+ IQueryable whereQuerable = queryable.Select(GetSelectPkExpression(dbCtx));
+
+ /*IRelationalQueryingEnumerable? queryingEnumerable = queryable.Provider.Execute(queryable.Expression) as IRelationalQueryingEnumerable;*/
+ IRelationalQueryingEnumerable? queryingEnumerable = queryProvider.Execute(whereQuerable.Expression) as IRelationalQueryingEnumerable;
+ if (queryingEnumerable==null)
+ {
+ throw new ApplicationException("Can't get IRelationalQueryingEnumerable from Expression");
+ }
+ string subQuerySQL;
+ using (var cmd = queryingEnumerable.CreateDbCommand())
+ {
+ subQuerySQL = cmd.CommandText;
+ }
+ //string tableAlias = BatchUtils.UniqueAlias();
+ string tableAlias = "temp1";
+ var dbSet = dbCtx.Set();
+ string pkName = BatchUtils.GetPKColName(dbSet);
+ ISqlGenerationHelper sqlGenHelpr = dbCtx.GetService();
+ string quotedPkName = sqlGenHelpr.DelimitIdentifier(pkName);//pkId-->"pdId" on NPgsql
+ StringBuilder sbSQL = new StringBuilder();
+ //sbSQL.Append(quotedPkName).Append(" IN(").Append(subQuerySQL).AppendLine(")");
+ //if not put a duplicate subquery, an error will be throw no Mysql: You can't specify target table 'T_Comments' for update in FROM clause
+ if(dbCtx.Database.ProviderName.Contains("mysql",StringComparison.OrdinalIgnoreCase))
+ {
+ sbSQL.Append(quotedPkName).Append(" IN(SELECT ").Append(quotedPkName).Append(" FROM (")
+ .Append(subQuerySQL).AppendLine($") {aliasSeparator} {tableAlias} )");
+ }
+ else
+ {
+ sbSQL.Append(quotedPkName).Append(" IN(").Append(subQuerySQL).AppendLine(")");
+ }
+ return sbSQL.ToString();
+ }
+
+ ///
+ /// exclude the oldSQL from newSQL
+ /// Diff("abc","abc12")=="12"
+ ///
+ ///
+ ///
+ ///
+ public static string Diff(string oldSQL, string newSQL)
+ {
+ if (!newSQL.StartsWith(oldSQL))
+ {
+ throw new ArgumentException("!newSQL.StartsWith(oldSQL)", nameof(newSQL));
+ }
+ return newSQL.Substring(oldSQL.Length);
+ }
+
+ //this method is from source code ef core
+ public static void GenerateList(IReadOnlyList items, IRelationalCommandBuilder Sql, Action generationAction, Action joinAction = null)
+ {
+ if (joinAction == null)
+ {
+ joinAction = delegate (IRelationalCommandBuilder isb)
+ {
+ isb.Append(", ");
+ };
+ }
+ for (int i = 0; i < items.Count; i++)
+ {
+ if (i > 0)
+ {
+ joinAction(Sql);
+ }
+ generationAction(items[i]);
+ }
+ }
+
+ //this method is from source code ef core
+ public static bool IsNonComposedSetOperation(SelectExpression selectExpression)
+ {
+ if (selectExpression.Offset == null && selectExpression.Limit == null && !selectExpression.IsDistinct && selectExpression.Predicate == null && selectExpression.Having == null && selectExpression.Orderings.Count == 0 && selectExpression.GroupBy.Count == 0 && selectExpression.Tables.Count == 1)
+ {
+ TableExpressionBase tableExpressionBase = selectExpression.Tables[0];
+ SetOperationBase setOperation = tableExpressionBase as SetOperationBase;
+ if (setOperation != null && selectExpression.Projection.Count == setOperation.Source1.Projection.Count)
+ {
+ return selectExpression.Projection.Select(delegate (ProjectionExpression pe, int index)
+ {
+ ColumnExpression columnExpression = pe.Expression as ColumnExpression;
+ if (columnExpression != null && string.Equals(columnExpression.Table.Alias, setOperation.Alias, StringComparison.OrdinalIgnoreCase))
+ {
+ return string.Equals(columnExpression.Name, setOperation.Source1.Projection[index].Alias, StringComparison.OrdinalIgnoreCase);
+ }
+ return false;
+ }).All((bool e) => e);
+ }
+ }
+ return false;
+ }
+
+ public static void OpenIfNeeded(this IDbConnection conn )
+ {
+ if (conn.State != ConnectionState.Open)
+ {
+ conn.Open();
+ }
+ }
+
+ public static Task OpenIfNeededAsync(this DbConnection conn,CancellationToken cancellationToken=default)
+ {
+ if (conn.State != ConnectionState.Open)
+ {
+ return conn.OpenAsync(cancellationToken);
+ }
+ else
+ {
+ return Task.CompletedTask;
+ }
+ }
+
+ public static IDictionary ConvertParameterValues(this DbContext ctx, IReadOnlyDictionary modelValues)
+ {
+ var typeMapping = ctx.GetService();
+ Dictionary providerValues = new ();
+ foreach (var kvp in modelValues)
+ {
+ string key = kvp.Key;
+ object value = modelValues[key];
+ providerValues[key] = ConvertToProvider(typeMapping,value);
+ }
+ return providerValues;
+ }
+
+ private static object ConvertToProvider(IRelationalTypeMappingSource typeMappingSource,object modelObject)
+ {
+ if(modelObject==null)
+ {
+ return DBNull.Value;
+ }
+ var mp = typeMappingSource.FindMapping(modelObject.GetType());
+ if (mp == null || mp.Converter == null || mp.Converter.ConvertToProvider == null)
+ {
+ return modelObject;
+
+ }
+ object providerValue = mp.Converter.ConvertToProvider(modelObject);
+ if (providerValue == null)
+ {
+ return DBNull.Value;
+ }
+ return providerValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/Lion/AbpPro/EntityFrameworkCore/BulkInsertUtils.cs b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/Lion/AbpPro/EntityFrameworkCore/BulkInsertUtils.cs
new file mode 100644
index 00000000..fd449c2c
--- /dev/null
+++ b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/Lion/AbpPro/EntityFrameworkCore/BulkInsertUtils.cs
@@ -0,0 +1,178 @@
+namespace Lion.AbpPro.EntityFrameworkCore
+{
+ public static class BulkInsertUtils
+ {
+ public static bool IsOwnedProp(IEntityType entityType, PropertyInfo propInfo)
+ {
+ var propEntityType = entityType.Model.FindEntityType(propInfo.PropertyType);
+ return propEntityType != null && propEntityType.IsOwned();
+ }
+
+ public static bool IsNavigationProp(IEntityType entityType, PropertyInfo propInfo)
+ {
+ return entityType.FindNavigation(propInfo) != null;
+ }
+
+ static IEnumerable BuildDbPropsForOwnedType(DbContext context, IEntityType entityType, PropertyInfo propInfo)
+ {
+ var propEntityType = context.Model.FindEntityType(propInfo.PropertyType, propInfo.Name, entityType);
+ if (propEntityType == null)
+ {
+ propEntityType = context.Model.FindEntityType(propInfo.PropertyType);
+ }
+
+ var tableIdentifier = StoreObjectIdentifier.Table(propEntityType.GetTableName()!, null);
+ foreach (var subProp in propInfo.PropertyType.GetProperties().Where(p => p.CanRead))
+ {
+ var subPropEFProp = propEntityType.FindProperty(subProp.Name);
+ string subPropColName = subPropEFProp!.FindColumn(tableIdentifier)!.Name;
+ DbProp dbProp = new DbProp
+ {
+ ColumnName = subPropColName,
+ GetValueFunc = (obj) =>
+ {
+ object? propValue = propInfo.GetValue(obj);
+ if (propValue == null)
+ {
+ return null;
+ }
+
+ object? subPropValue = subProp.GetValue(propValue);
+ return subPropValue;
+ },
+ PropertyType = subProp.PropertyType,
+ ValueConverter = subPropEFProp.GetValueConverter(),
+ };
+ yield return dbProp;
+ }
+ }
+
+ ///
+ /// Get properties except for navigationProperties or autogenerated ones
+ ///
+ public static DbProp[] ParseDbProps(DbContext dbCtx, IEntityType entityType) where TEntity : class
+ {
+ //skip navigationProperties
+ var props = typeof(TEntity).GetProperties().Where(p => IsOwnedProp(entityType, p) || !IsNavigationProp(entityType, p) && p.CanRead);
+ List propFields = new List();
+
+ foreach (var prop in props)
+ {
+ if (IsOwnedProp(entityType, prop))
+ {
+ foreach (var p in BuildDbPropsForOwnedType(dbCtx, entityType, prop))
+ {
+ propFields.Add(p);
+ }
+ }
+
+ string propName = prop.Name;
+ var efProp = entityType.FindProperty(propName);
+ if (efProp == null) //this property is not mapped
+ {
+ continue;
+ }
+
+ //skip the columns those are autogenerated
+ if (efProp.ValueGenerated == ValueGenerated.OnAdd
+ || efProp.ValueGenerated == ValueGenerated.OnAddOrUpdate)
+ {
+ if (efProp.ClrType != typeof(Guid) && efProp.ClrType != typeof(Guid?))
+ {
+ continue;
+ }
+ }
+
+ string dbColName = efProp.GetColumnName(StoreObjectIdentifier.SqlQuery(entityType));
+ DbProp dbProp = new DbProp
+ {
+ ColumnName = dbColName,
+ //Property = prop,
+ GetValueFunc = (obj) => prop.GetValue(obj),
+ PropertyType = prop.PropertyType,
+ ValueConverter = efProp.GetValueConverter(),
+ };
+ propFields.Add(dbProp);
+ }
+
+ return propFields.ToArray();
+ }
+
+ ///
+ /// Build DataTable for items
+ ///
+ public static DataTable BuildDataTable(DbContext dbCtx, DbSet dbSet,
+ IEnumerable items) where TEntity : class
+ {
+ var entityType = dbSet.EntityType;
+ var dbProps = ParseDbProps(dbCtx, entityType);
+ DataTable dataTable = new DataTable();
+ foreach (var dbProp in dbProps)
+ {
+ string columnName = dbProp.ColumnName;
+ Type propType = dbProp.PropertyType;
+ DataColumn col;
+ bool isNullable;
+ var valueConverter = dbProp.ValueConverter;
+ if (valueConverter != null)
+ {
+ var providerType = valueConverter.ProviderClrType;
+ isNullable = BatchUtils.IsNullableType(providerType);
+ if (isNullable)
+ {
+ col = dataTable.Columns.Add(columnName,
+ providerType.GenericTypeArguments[0]);
+ col.AllowDBNull = true;
+ }
+ else
+ {
+ col = dataTable.Columns.Add(columnName, providerType);
+ }
+ }
+ else
+ {
+ isNullable = BatchUtils.IsNullableType(propType);
+ if (isNullable)
+ {
+ col = dataTable.Columns.Add(columnName,
+ propType.GenericTypeArguments[0]);
+ col.AllowDBNull = true;
+ }
+ else
+ {
+ col = dataTable.Columns.Add(columnName, propType);
+ }
+ }
+ }
+
+ foreach (var item in items)
+ {
+ DataRow row = dataTable.NewRow();
+ foreach (var dbProp in dbProps)
+ {
+ //object? value = dbProp.Property.GetValue(item);
+ object? value = dbProp.GetValueFunc(item);
+
+
+ var valueConverter = dbProp.ValueConverter;
+ if (valueConverter != null)
+ {
+ value = valueConverter.ConvertToProvider(value);
+ }
+
+ //ValueConverter end
+ if (value == null)
+ {
+ value = DBNull.Value;
+ }
+
+ row[dbProp.ColumnName] = value;
+ }
+
+ dataTable.Rows.Add(row);
+ }
+
+ return dataTable;
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/Lion/AbpPro/EntityFrameworkCore/DbProp.cs b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/Lion/AbpPro/EntityFrameworkCore/DbProp.cs
new file mode 100644
index 00000000..8a6ab44d
--- /dev/null
+++ b/aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore/Lion/AbpPro/EntityFrameworkCore/DbProp.cs
@@ -0,0 +1,11 @@
+namespace Lion.AbpPro.EntityFrameworkCore
+{
+ public class DbProp
+ {
+ public ValueConverter? ValueConverter { get; set; }
+ public string ColumnName { get; set; }
+
+ public Func