From 37fc750bb44fd3984b38018c2ec338d0e01acb74 Mon Sep 17 00:00:00 2001 From: MaceWindu Date: Sun, 26 Feb 2023 14:45:00 +0100 Subject: [PATCH] backport linq2db 5 migration from v7 (#300) --- Build/linq2db.Default.props | 4 +- Directory.Packages.props | 8 +- NuGet/linq2db.EntityFrameworkCore.nuspec | 6 +- README.md | 84 +-- .../EFCoreMetadataReader.cs | 516 +++++++++--------- .../ILinqToDBForEFTools.cs | 9 +- .../Internal/EFCoreExpressionAttribute.cs | 2 +- .../Internal/LinqToDBOptionsExtension.cs | 19 +- .../LinqToDBContextOptionsBuilder.cs | 48 +- .../LinqToDBForEFTools.ContextExtensions.cs | 47 +- ...EFTools.ContextOptionsBuilderExtensions.cs | 20 +- .../LinqToDBForEFTools.Extensions.cs | 2 +- .../LinqToDBForEFTools.cs | 214 +++++--- .../LinqToDBForEFToolsDataConnection.cs | 130 ++--- .../LinqToDBForEFToolsDataContext.cs | 2 +- .../LinqToDBForEFToolsImplDefault.cs | 149 +++-- .../ForMappingTestsBase.cs | 56 +- .../TestEfCoreAndLinq2DbComboInterceptor.cs | 72 --- .../TestEfCoreAndLinqToDBComboInterceptor.cs | 72 +++ .../ForMappingTests.cs | 13 +- .../PomeloMySqlTests.cs | 11 +- .../ForMappingTests.cs | 11 +- .../NpgSqlTests.cs | 5 +- .../SampleTests/IdTests.cs | 39 +- .../SampleTests/QueryableExtensions.cs | 2 +- .../ForMappingTests.cs | 8 +- .../InterceptorTests.cs | 63 +-- .../ForMappingTests.cs | 20 +- .../IssueTests.cs | 8 +- .../JsonConverTests.cs | 21 +- .../QueryableExtensions.cs | 111 ---- .../Settings.cs | 11 + .../ToolsTests.cs | 66 +-- .../ValueConversion/ConvertorTests.cs | 6 +- azure-pipelines.yml | 15 +- 35 files changed, 930 insertions(+), 940 deletions(-) delete mode 100644 Tests/LinqToDB.EntityFrameworkCore.BaseTests/Interceptors/TestEfCoreAndLinq2DbComboInterceptor.cs create mode 100644 Tests/LinqToDB.EntityFrameworkCore.BaseTests/Interceptors/TestEfCoreAndLinqToDBComboInterceptor.cs delete mode 100644 Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/QueryableExtensions.cs create mode 100644 Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/Settings.cs diff --git a/Build/linq2db.Default.props b/Build/linq2db.Default.props index 0cb639e..66e1c12 100644 --- a/Build/linq2db.Default.props +++ b/Build/linq2db.Default.props @@ -1,11 +1,11 @@  - 2.9.0 + 2.10.0 Svyatoslav Danyliv, Igor Tkachev, Dmitry Lukashenko, Ilya Chudin Linq to DB linq2db.net - 2002-2022 linq2db.net + 2002-2023 linq2db.net https://github.com/linq2db/linq2db.EntityFrameworkCore git diff --git a/Directory.Packages.props b/Directory.Packages.props index 736ae48..a4ada7f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,12 +1,12 @@  - + - + - - + + diff --git a/NuGet/linq2db.EntityFrameworkCore.nuspec b/NuGet/linq2db.EntityFrameworkCore.nuspec index 137ac7f..7ef4d05 100644 --- a/NuGet/linq2db.EntityFrameworkCore.nuspec +++ b/NuGet/linq2db.EntityFrameworkCore.nuspec @@ -5,7 +5,7 @@ Linq to DB (linq2db) extensions for Entity Framework Core 2.x Igor Tkachev, Ilya Chudin, Svyatoslav Danyliv, Dmitry Lukashenko Igor Tkachev, Ilya Chudin, Svyatoslav Danyliv, Dmitry Lukashenko - Copyright © 2020-2022 Igor Tkachev, Ilya Chudin, Svyatoslav Danyliv, Dmitry Lukashenko + Copyright © 2020-2023 Igor Tkachev, Ilya Chudin, Svyatoslav Danyliv, Dmitry Lukashenko Allows to execute Linq to DB (linq2db) queries in Entity Framework Core DbContext. linq linq2db LinqToDB ORM database entity-framework-core EntityFrameworkCore EFCore DB SQL SqlServer SqlCe SqlServerCe MySql Firebird SQLite Oracle ODP PostgreSQL DB2 @@ -14,8 +14,8 @@ https://github.com/linq2db/linq2db.EntityFrameworkCore MIT-LICENSE.txt - - + + diff --git a/README.md b/README.md index 82a5dfe..63afa6b 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,17 @@ You can also register additional options (like interceptors) for LinqToDB during ```cs var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite(); -optionsBuilder.UseLinqToDb(builder => +optionsBuilder.UseLinqToDB(builder => { + // add custom command interceptor builder.AddInterceptor(new MyCommandInterceptor()); + // add additional mappings + builder.AddMappingSchema(myCustomMappings); + // configure SQL Server dialect explicitly + //builder.AddCustomOptions(o => o.UseSqlServer(SqlServerVersion.v2022)); + // due to bug in linq2db 5.0.0, use overload with connection string + // will be fixed in next linq2db release + builder.AddCustomOptions(o => o.UseSqlServer("unused", SqlServerVersion.v2022)); }); ``` @@ -58,10 +66,10 @@ ctx.BulkCopy(new BulkCopyOptions {...}, items); // query for retrieving products that do not have duplicates by Name var query = - from p in ctx.Products - from op in ctx.Products.LeftJoin(op => op.ProductID != p.ProductID && op.Name == p.Name) - where Sql.ToNullable(op.ProductID) == null - select p; + from p in ctx.Products + from op in ctx.Products.LeftJoin(op => op.ProductID != p.ProductID && op.Name == p.Name) + where Sql.ToNullable(op.ProductID) == null + select p; // insert these records into the same or another table query.Insert(ctx.Products.ToLinqToDBTable(), s => new Product { Name = s.Name ... }); @@ -90,13 +98,13 @@ It is not required to work directly with `LINQ To DB` `DataConnection` class but ```cs // uing DbContext -using (var dc = ctx.CreateLinqToDbConnection()) +using (var dc = ctx.CreateLinqToDBConnection()) { // linq queries using linq2db extensions } // using DbContextOptions -using (var dc = options.CreateLinqToDbConnection()) +using (var dc = options.CreateLinqToDBConnection()) { // linq queries using linq2db extensions } @@ -110,35 +118,35 @@ Async methods have the same name but with `LinqToDB` suffix. E.g. `ToListAsyncLi ```cs using (var ctx = CreateAdventureWorksContext()) { - var productsWithModelCount = - from p in ctx.Products - select new - { - // Window Function - Count = Sql.Ext.Count().Over().PartitionBy(p.ProductModelID).ToValue(), - Product = p - }; - - var neededRecords = - from p in productsWithModelCount - where p.Count.Between(2, 4) // LINQ To DB extension - select new - { - p.Product.Name, - p.Product.Color, - p.Product.Size, - // retrieving value from column dynamically - PhotoFileName = Sql.Property(p.Product, "ThumbnailPhotoFileName") - }; - - // ensure we have replaced EF context - var items1 = neededRecords.ToLinqToDB().ToArray(); - - // async version - var items2 = await neededRecords.ToLinqToDB().ToArrayAsync(); - - // and simple bonus - how to generate SQL - var sql = neededRecords.ToLinqToDB().ToString(); + var productsWithModelCount = + from p in ctx.Products + select new + { + // Window Function + Count = Sql.Ext.Count().Over().PartitionBy(p.ProductModelID).ToValue(), + Product = p + }; + + var neededRecords = + from p in productsWithModelCount + where p.Count.Between(2, 4) // LINQ To DB extension + select new + { + p.Product.Name, + p.Product.Color, + p.Product.Size, + // retrieving value from column dynamically + PhotoFileName = Sql.Property(p.Product, "ThumbnailPhotoFileName") + }; + + // ensure we have replaced EF context + var items1 = neededRecords.ToLinqToDB().ToArray(); + + // async version + var items2 = await neededRecords.ToLinqToDB().ToArrayAsync(); + + // and simple bonus - how to generate SQL + var sql = neededRecords.ToLinqToDB().ToString(); } ``` @@ -165,9 +173,11 @@ Below is a list of providers, that should work right now: - Oracle - SQL Server CE -# Know limitations +# Known limitations - No Lazy loading - No way to work with in-memory database +- No TPT (table per type) support +- No many-to-many support # Help! It doesn't work! diff --git a/Source/LinqToDB.EntityFrameworkCore/EFCoreMetadataReader.cs b/Source/LinqToDB.EntityFrameworkCore/EFCoreMetadataReader.cs index b43791d..b3cc49b 100644 --- a/Source/LinqToDB.EntityFrameworkCore/EFCoreMetadataReader.cs +++ b/Source/LinqToDB.EntityFrameworkCore/EFCoreMetadataReader.cs @@ -8,7 +8,6 @@ using System.Runtime.CompilerServices; using LinqToDB.Expressions; using LinqToDB.Reflection; -using LinqToDB.SqlQuery; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -20,11 +19,11 @@ namespace LinqToDB.EntityFrameworkCore { - using Mapping; - using Metadata; - using Extensions; using Common; + using Extensions; using Internal; + using Mapping; + using Metadata; using SqlQuery; /// @@ -32,103 +31,105 @@ namespace LinqToDB.EntityFrameworkCore /// internal sealed class EFCoreMetadataReader : IMetadataReader { - readonly IModel? _model; - private readonly SqlTranslatingExpressionVisitorDependencies? _dependencies; - private readonly IRelationalTypeMappingSource? _mappingSource; - private readonly IMigrationsAnnotationProvider? _annotationProvider; + private readonly string _objectId; + private readonly IModel? _model; + private readonly SqlTranslatingExpressionVisitorDependencies? _dependencies; + private readonly IRelationalTypeMappingSource? _mappingSource; + private readonly IMigrationsAnnotationProvider? _annotationProvider; private readonly ConcurrentDictionary _calculatedExtensions = new(); - public EFCoreMetadataReader( - IModel? model, IInfrastructure? accessor) + public EFCoreMetadataReader(IModel? model, IInfrastructure? accessor) { _model = model; + if (accessor != null) { - _dependencies = accessor.GetService(); - _mappingSource = accessor.GetService(); + _dependencies = accessor.GetService(); + _mappingSource = accessor.GetService(); _annotationProvider = accessor.GetService(); } + + _objectId = $".{_model?.GetHashCode() ?? 0}.{_dependencies?.GetHashCode() ?? 0}.{_mappingSource?.GetHashCode() ?? 0}.{_annotationProvider?.GetHashCode() ?? 0}."; } - public T[] GetAttributes(Type type, bool inherit = true) where T : Attribute + public MappingAttribute[] GetAttributes(Type type) { + List? result = null; + var et = _model?.FindEntityType(type); if (et != null) { - if (typeof(T) == typeof(TableAttribute)) - { - var relational = et.Relational(); - return new[] { (T)(Attribute)new TableAttribute(relational.TableName) { Schema = relational.Schema } }; - } - if (typeof(T) == typeof(QueryFilterAttribute)) - { - var filter = et.QueryFilter; + result = new(); + + // TableAttribute + var relational = et.Relational(); + result.Add(new TableAttribute(relational.TableName) { Schema = relational.Schema }); - if (filter != null) + // QueryFilterAttribute + var filter = et.QueryFilter; + if (filter != null) + { + var queryParam = Expression.Parameter(typeof(IQueryable<>).MakeGenericType(type), "q"); + var dcParam = Expression.Parameter(typeof(IDataContext), "dc"); + var contextProp = Expression.Property(Expression.Convert(dcParam, typeof(LinqToDBForEFToolsDataConnection)), "Context"); + var filterBody = filter.Body.Transform(contextProp, static (contextProp, e) => { - var queryParam = Expression.Parameter(typeof(IQueryable<>).MakeGenericType(type), "q"); - var dcParam = Expression.Parameter(typeof(IDataContext), "dc"); - var contextProp = Expression.Property(Expression.Convert(dcParam, typeof(LinqToDBForEFToolsDataConnection)), "Context"); - var filterBody = filter.Body.Transform(e => + if (typeof(DbContext).IsSameOrParentOf(e.Type)) { - if (typeof(DbContext).IsSameOrParentOf(e.Type)) - { - Expression newExpr = contextProp; - if (newExpr.Type != e.Type) - newExpr = Expression.Convert(newExpr, e.Type); - return newExpr; - } + Expression newExpr = contextProp; + if (newExpr.Type != e.Type) + newExpr = Expression.Convert(newExpr, e.Type); + return newExpr; + } - return e; - }); + return e; + }); - filterBody = LinqToDBForEFTools.TransformExpression(filterBody, null, null, _model); + filterBody = LinqToDBForEFTools.TransformExpression(filterBody, null, null, _model); - // we have found dependency, check for compatibility + // we have found dependency, check for compatibility - var filterLambda = Expression.Lambda(filterBody, filter.Parameters[0]); - Expression body = Expression.Call(Methods.Queryable.Where.MakeGenericMethod(type), queryParam, filterLambda); + var filterLambda = Expression.Lambda(filterBody, filter.Parameters[0]); + Expression body = Expression.Call(Methods.Queryable.Where.MakeGenericMethod(type), queryParam, filterLambda); - var checkType = filter.Body != filterBody; - if (checkType) - { - body = Expression.Condition( - Expression.TypeIs(dcParam, typeof(LinqToDBForEFToolsDataConnection)), body, queryParam); - } + var checkType = filter.Body != filterBody; + if (checkType) + { + body = Expression.Condition( + Expression.TypeIs(dcParam, typeof(LinqToDBForEFToolsDataConnection)), body, queryParam); + } - var lambda = Expression.Lambda(body, queryParam, dcParam); + var lambda = Expression.Lambda(body, queryParam, dcParam); - return new[] { (T) (Attribute) new QueryFilterAttribute { FilterFunc = lambda.Compile() } }; - } + result.Add(new QueryFilterAttribute() { FilterFunc = lambda.Compile() }); } - } - if (typeof(T) == typeof(TableAttribute)) - { - var tableAttribute = type.GetCustomAttribute(inherit); - if (tableAttribute != null) - return new[] { (T)(Attribute)new TableAttribute(tableAttribute.Name) { Schema = tableAttribute.Schema } }; - } - else if (_model != null && typeof(T) == typeof(InheritanceMappingAttribute)) - { - if (et != null) + // InheritanceMappingAttribute + if (_model != null) { var derivedEntities = _model.GetEntityTypes().Where(e => e.BaseType == et && e.Relational().DiscriminatorValue != null).ToList(); - return - derivedEntities.Select(e => - (T)(Attribute)new InheritanceMappingAttribute - { - Type = e.ClrType, - Code = e.Relational().DiscriminatorValue - } - ) - .ToArray(); + foreach (var e in derivedEntities) + { + result.Add( + new InheritanceMappingAttribute() + { + Type = e.ClrType, + Code = e.Relational().DiscriminatorValue + } + ); + } } - + } + else + { + // TableAttribute + var tableAttribute = type.GetAttribute(); + if (tableAttribute != null) + (result ??= new()).Add(new TableAttribute(tableAttribute.Name) { Schema = tableAttribute.Schema }); } - return Array.Empty(); + return result == null ? Array.Empty() : result.ToArray(); } static bool CompareProperty(MemberInfo? property, MemberInfo memberInfo) @@ -138,11 +139,11 @@ static bool CompareProperty(MemberInfo? property, MemberInfo memberInfo) if (property == null) return false; - + if (memberInfo.DeclaringType?.IsAssignableFrom(property.DeclaringType) == true - && memberInfo.Name == property.Name - && memberInfo.MemberType == property.MemberType - && memberInfo.GetMemberType() == property.GetMemberType()) + && memberInfo.Name == property.Name + && memberInfo.MemberType == property.MemberType + && memberInfo.GetMemberType() == property.GetMemberType()) { return true; } @@ -189,130 +190,139 @@ static DataType DbTypeToDataType(DbType dbType) _ => DataType.Undefined }; } - - public T[] GetAttributes(Type type, MemberInfo memberInfo, bool inherit = true) where T : Attribute + + public MappingAttribute[] GetAttributes(Type type, MemberInfo memberInfo) { - if (typeof(Expression).IsSameOrParentOf(type)) - return Array.Empty(); + if (typeof(Expression).IsSameOrParentOf(type)) + return Array.Empty(); + + List? result = null; + var hasColumn = false; - if (typeof(T) == typeof(ColumnAttribute)) + var et = _model?.FindEntityType(type); + if (et != null) { - var et = _model?.FindEntityType(type); - if (et != null) + var props = et.GetProperties(); + var prop = props.FirstOrDefault(p => CompareProperty(p, memberInfo)); + + // ColumnAttribute + if (prop != null) { - var props = et.GetProperties(); - var prop = props.FirstOrDefault(p => CompareProperty(p, memberInfo)); - - if (prop != null) - { - var discriminator = et.Relational().DiscriminatorProperty; + hasColumn = true; + var discriminator = et.Relational().DiscriminatorProperty; - var isPrimaryKey = prop.IsPrimaryKey(); - var primaryKeyOrder = 0; - if (isPrimaryKey) + var isPrimaryKey = prop.IsPrimaryKey(); + var primaryKeyOrder = 0; + if (isPrimaryKey) + { + var pk = prop.GetContainingPrimaryKey()!; + var idx = 0; + foreach (var p in pk.Properties) { - var pk = prop.GetContainingPrimaryKey()!; - primaryKeyOrder = pk.Properties.Select((p, i) => new { p, index = i }) - .FirstOrDefault(v => CompareProperty(v.p, memberInfo))?.index ?? 0; - } + if (CompareProperty(p, memberInfo)) + { + primaryKeyOrder = idx; + break; + } - var annotations = prop.GetAnnotations(); - if (_annotationProvider != null) - { - annotations = annotations.Concat(_annotationProvider.For(prop)); + idx++; } + } - var isIdentity = annotations - .Any(a => + var annotations = prop.GetAnnotations(); + if (_annotationProvider != null) + { + annotations = annotations.Concat(_annotationProvider.For(prop)); + } + + var isIdentity = annotations + .Any(static a => + { + if (a.Name.EndsWith(":ValueGenerationStrategy")) { - if (a.Name.EndsWith(":ValueGenerationStrategy")) - { - var value = a.Value?.ToString(); + var value = a.Value?.ToString(); - if (value != null && (value.Contains("Identity") || value.Contains("Serial"))) - return true; - }; + if (value != null && (value.Contains("Identity") || value.Contains("Serial"))) + return true; + }; - if (a.Name.EndsWith(":Autoincrement")) - return a.Value is bool b && b; + if (a.Name.EndsWith(":Autoincrement")) + return a.Value is bool b && b; - // for postgres - if (a.Name == "Relational:DefaultValueSql") + // for postgres + if (a.Name == "Relational:DefaultValueSql") + { + if (a.Value is string str) { - if (a.Value is string str) - { - return str.ToLowerInvariant().Contains("nextval"); - } + return str.ToLowerInvariant().Contains("nextval"); } + } - return false; - }); + return false; + }); - var dataType = DataType.Undefined; + var dataType = DataType.Undefined; - var relational = prop.Relational(); + var relational = prop.Relational(); - if (annotations.FirstOrDefault(a => a.Value is RelationalTypeMapping)?.Value is RelationalTypeMapping typeMapping) + if (annotations.FirstOrDefault(a => a.Value is RelationalTypeMapping)?.Value is RelationalTypeMapping typeMapping) + { + if (typeMapping.DbType != null) { - if (typeMapping.DbType != null) - { - dataType = DbTypeToDataType(typeMapping.DbType.Value); - } - else - { - var ms = _model != null ? LinqToDBForEFTools.GetMappingSchema(_model, null) : MappingSchema.Default; - dataType = ms.GetDataType(typeMapping.ClrType).Type.DataType; - } + dataType = DbTypeToDataType(typeMapping.DbType.Value); } + else + { + var ms = _model != null ? LinqToDBForEFTools.GetMappingSchema(_model, null, null) : MappingSchema.Default; + dataType = ms.GetDataType(typeMapping.ClrType).Type.DataType; + } + } + + var behaviour = prop.BeforeSaveBehavior; + var skipOnInsert = prop.ValueGenerated.HasFlag(ValueGenerated.OnAdd); - var behaviour = prop.BeforeSaveBehavior; - var skipOnInsert = prop.ValueGenerated.HasFlag(ValueGenerated.OnAdd); + if (skipOnInsert) + { + skipOnInsert = isIdentity || behaviour != PropertySaveBehavior.Save; + } - if (skipOnInsert) + var skipOnUpdate = behaviour != PropertySaveBehavior.Save || + prop.ValueGenerated.HasFlag(ValueGenerated.OnUpdate); + + (result ??= new()).Add( + new ColumnAttribute() { - skipOnInsert = isIdentity || behaviour != PropertySaveBehavior.Save; + Name = relational.ColumnName, + Length = prop.GetMaxLength() ?? 0, + CanBeNull = prop.IsNullable, + DbType = relational.ColumnType, + DataType = dataType, + IsPrimaryKey = isPrimaryKey, + PrimaryKeyOrder = primaryKeyOrder, + IsIdentity = isIdentity, + IsDiscriminator = discriminator == prop, + SkipOnInsert = skipOnInsert, + SkipOnUpdate = skipOnUpdate } + ); - var skipOnUpdate = behaviour != PropertySaveBehavior.Save || - prop.ValueGenerated.HasFlag(ValueGenerated.OnUpdate); - - return new T[] + // ValueConverterAttribute + var converter = prop.GetValueConverter(); + if (converter != null) + { + var valueConverterAttribute = new ValueConverterAttribute() { - (T)(Attribute)new ColumnAttribute - { - Name = relational.ColumnName, - Length = prop.GetMaxLength() ?? 0, - CanBeNull = prop.IsNullable, - DbType = relational.ColumnType, - DataType = dataType, - IsPrimaryKey = isPrimaryKey, - PrimaryKeyOrder = primaryKeyOrder, - IsIdentity = isIdentity, - IsDiscriminator = discriminator == prop, - SkipOnInsert = skipOnInsert, - SkipOnUpdate = skipOnUpdate - } + ValueConverter = new ValueConverter(converter.ConvertToProviderExpression, converter.ConvertFromProviderExpression, false) }; + + result.Add(valueConverterAttribute); } } - var columnAttributes = memberInfo.GetCustomAttributes(inherit); - - return columnAttributes.Select(c => (T)(Attribute)new ColumnAttribute - { - Name = c.Name, - DbType = c.TypeName, - }).ToArray(); - } - - if (typeof(T) == typeof(AssociationAttribute)) - { - var et = _model?.FindEntityType(type); - var navigations = et?.GetNavigations().Where(n => CompareProperty(n.PropertyInfo, memberInfo)).ToArray(); - if (navigations?.Length > 0) + // AssociationAttribute + foreach (var navigation in et.GetNavigations()) { - var associations = new List(); - foreach (var navigation in navigations) + if (CompareProperty(navigation.PropertyInfo, memberInfo)) { var fk = navigation.ForeignKey; if (!navigation.IsDependentToPrincipal()) @@ -320,100 +330,87 @@ public T[] GetAttributes(Type type, MemberInfo memberInfo, bool inherit = tru // Could not track when EF decides to do INNER JOIN var canBeNull = true; - var thisKey = string.Join(",", fk.PrincipalKey.Properties.Select(p => p.Name)); - var otherKey = string.Join(",", fk.Properties.Select(p => p.Name)); - associations.Add(new AssociationAttribute + var thisKey = string.Join(",", fk.PrincipalKey.Properties.Select(static p => p.Name)); + var otherKey = string.Join(",", fk.Properties.Select(static p => p.Name)); + (result ??= new()).Add(new AssociationAttribute() { - ThisKey = thisKey, - OtherKey = otherKey, - CanBeNull = canBeNull, + ThisKey = thisKey, + OtherKey = otherKey, + CanBeNull = canBeNull }); } else { - var thisKey = string.Join(",", fk.Properties.Select(p => p.Name)); - var otherKey = string.Join(",", fk.PrincipalKey.Properties.Select(p => p.Name)); - associations.Add(new AssociationAttribute + var thisKey = string.Join(",", fk.Properties.Select(static p => p.Name)); + var otherKey = string.Join(",", fk.PrincipalKey.Properties.Select(static p => p.Name)); + (result ??= new()).Add(new AssociationAttribute() { - ThisKey = thisKey, - OtherKey = otherKey, - CanBeNull = !fk.IsRequired, + ThisKey = thisKey, + OtherKey = otherKey, + CanBeNull = !fk.IsRequired }); } } - - return associations.Select(a => (T)(Attribute)a).ToArray(); } - } - else if (typeof(T) == typeof(Sql.ExpressionAttribute)) + } + + if (!hasColumn) { - // Search for translator first - if (_dependencies != null) - { - if (memberInfo.IsMethodEx()) - { - var methodInfo = (MethodInfo) memberInfo; - var func = GetDbFunctionFromMethodCall(type, methodInfo); - if (func != null) - return new T[] { (T) (Attribute) func }; - } - else if (memberInfo.IsPropertyEx()) + // ColumnAttribute + var columnAttribute = memberInfo.GetAttribute(); + + if (columnAttribute != null) + (result ??= new()).Add(new ColumnAttribute() { - var propertyInfo = (PropertyInfo) memberInfo; - var func = GetDbFunctionFromProperty(type, propertyInfo); - if (func != null) - return new T[] { (T) (Attribute) func }; - } - } + Name = columnAttribute.Name, + DbType = columnAttribute.TypeName, + }); + } + // Search for translator first + // Sql.ExpressionAttribute + if (_dependencies != null) + { if (memberInfo.IsMethodEx()) { - var method = (MethodInfo) memberInfo; - - //TODO: - /* - var func = _model?.GetDbFunctions().FirstOrDefault(f => f.MethodInfo == method); + var methodInfo = (MethodInfo) memberInfo; + var func = GetDbFunctionFromMethodCall(type, methodInfo); if (func != null) - return new T[] - { - (T) (Attribute) new Sql.FunctionAttribute - { - Name = func.Name, - ServerSideOnly = true - } - }; - - var functionAttributes = memberInfo.GetCustomAttributes(inherit); - return functionAttributes.Select(f => (T) (Attribute) new Sql.FunctionAttribute - { - Name = f.Name, - ServerSideOnly = true, - }).ToArray(); - */ + (result ??= new()).Add(func); } - } - else if (typeof(T) == typeof(ValueConverterAttribute)) - { - var et = _model?.FindEntityType(type); - if (et != null) + else if (memberInfo.IsPropertyEx()) { - var props = et.GetProperties(); - var prop = props.FirstOrDefault(p => CompareProperty(p, memberInfo)); - - var converter = prop?.GetValueConverter(); - if (converter != null) - { - var valueConverterAttribute = new ValueConverterAttribute - { - ValueConverter = new ValueConverter(converter.ConvertToProviderExpression, - converter.ConvertFromProviderExpression, false) - }; - return new T[] { (T) (Attribute) valueConverterAttribute }; - } + var propertyInfo = (PropertyInfo) memberInfo; + var func = GetDbFunctionFromProperty(type, propertyInfo); + if (func != null) + (result ??= new()).Add(func); } } - return Array.Empty(); + // Sql.FunctionAttribute + // TODO + //if (memberInfo.IsMethodEx()) + //{ + // var method = (MethodInfo) memberInfo; + + // var func = _model?.GetDbFunctions().FirstOrDefault(f => f.MethodInfo == method); + // if (func != null) + // (result ??= new()).Add(new Sql.FunctionAttribute() + // { + // Name = func.Name, + // ServerSideOnly = true + // }); + + // var functionAttribute = memberInfo.GetAttribute(); + // if (functionAttribute != null) + // (result ??= new()).Add(new Sql.FunctionAttribute() + // { + // Name = functionAttribute.Name, + // ServerSideOnly = true, + // }); + //} + + return result == null ? Array.Empty() : result.ToArray(); } sealed class ValueConverter : IValueConverter @@ -423,14 +420,13 @@ public ValueConverter( LambdaExpression convertFromProviderExpression, bool handlesNulls) { FromProviderExpression = convertFromProviderExpression; - ToProviderExpression = convertToProviderExpression; - HandlesNulls = handlesNulls; + ToProviderExpression = convertToProviderExpression; + HandlesNulls = handlesNulls; } - public bool HandlesNulls { get; } + public bool HandlesNulls { get; } public LambdaExpression FromProviderExpression { get; } - public LambdaExpression ToProviderExpression { get; } - + public LambdaExpression ToProviderExpression { get; } } sealed class SqlTransparentExpression : Expression @@ -447,12 +443,12 @@ private bool Equals(SqlTransparentExpression other) return ReferenceEquals(this, other); } - public override bool Equals(object obj) + public override bool Equals(object? obj) { - if (ReferenceEquals(null, obj)) return false; + if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; - return Equals((SqlTransparentExpression) obj); + return Equals((SqlTransparentExpression)obj); } public override Type Type => Expression.Type; @@ -469,7 +465,7 @@ public override int GetHashCode() if (_dependencies == null || _model == null) return null; - methodInfo = (MethodInfo?) type.GetMemberEx(methodInfo) ?? methodInfo; + methodInfo = (MethodInfo?)type.GetMemberEx(methodInfo) ?? methodInfo; var found = _calculatedExtensions.GetOrAdd(methodInfo, mi => { @@ -512,14 +508,14 @@ public override int GetHashCode() { EFCoreExpressionAttribute? result = null; - if ((propInfo.GetMethod?.IsStatic != true) - && !(mi is DynamicColumnInfo) - && !mi.GetCustomAttributes().Any()) + if ((propInfo.GetMethod?.IsStatic != true) + && !(mi is DynamicColumnInfo) + && !mi.HasAttribute()) { var objExpr = new SqlTransparentExpression(Expression.Constant(DefaultValue.GetValue(type), type)); var mcExpr = Expression.MakeMemberAccess(objExpr, propInfo); - var newExpression = _dependencies.MemberTranslator.Translate(mcExpr); + var newExpression = _dependencies!.MemberTranslator.Translate(mcExpr); if (newExpression != null) { var parametersArray = new Expression[] { objExpr }; @@ -547,8 +543,8 @@ string PrepareExpressionText(Expression? expr) { if (param is SqlTransparentExpression transparent) { - if (transparent.Expression is ConstantExpression constantExpr && - expr is ConstantExpression sqlConstantExpr) + if (transparent.Expression is ConstantExpression && + expr is ConstantExpression) { //found = sqlConstantExpr.Value.Equals(constantExpr.Value); found = true; @@ -578,7 +574,7 @@ string PrepareExpressionText(Expression? expr) if (!sqlFunction.IsNiladic) { text = text + "("; - for (int i = 0; i < sqlFunction.Arguments.Count; i++) + for (var i = 0; i < sqlFunction.Arguments.Count; i++) { var paramText = PrepareExpressionText(sqlFunction.Arguments[i]); if (i > 0) @@ -593,8 +589,8 @@ string PrepareExpressionText(Expression? expr) } if (newExpression.GetType().GetProperty("Left") != null && - newExpression.GetType().GetProperty("Right") != null && - newExpression.GetType().GetProperty("Operator") != null) + newExpression.GetType().GetProperty("Right") != null && + newExpression.GetType().GetProperty("Operator") != null) { // Handling NpgSql's CustomBinaryExpression @@ -614,9 +610,9 @@ string PrepareExpressionText(Expression? expr) var converted = UnwrapConverted(newExpression); var expressionText = PrepareExpressionText(converted); var result = new EFCoreExpressionAttribute(expressionText) - { ServerSideOnly = true, IsPredicate = memberInfo.GetMemberType() == typeof(bool) }; + { ServerSideOnly = true, IsPredicate = memberInfo.GetMemberType() == typeof(bool) }; - if (converted is SqlFunctionExpression || converted is SqlFragmentExpression) + if (converted is SqlFunctionExpression or SqlFragmentExpression) result.Precedence = Precedence.Primary; return result; @@ -627,7 +623,7 @@ private static Expression UnwrapConverted(Expression expr) if (expr is SqlFunctionExpression func) { if (string.Equals(func.FunctionName, "COALESCE", StringComparison.InvariantCultureIgnoreCase) && - func.Arguments.Count == 2 && func.Arguments[1].NodeType == ExpressionType.Extension) + func.Arguments?.Count == 2 && func.Arguments[1].NodeType == ExpressionType.Extension) return UnwrapConverted(func.Arguments[0]); } @@ -638,5 +634,7 @@ public MemberInfo[] GetDynamicColumns(Type type) { return Array.Empty(); } + + string IMetadataReader.GetObjectID() => _objectId; } } diff --git a/Source/LinqToDB.EntityFrameworkCore/ILinqToDBForEFTools.cs b/Source/LinqToDB.EntityFrameworkCore/ILinqToDBForEFTools.cs index 0f051df..bc78c35 100644 --- a/Source/LinqToDB.EntityFrameworkCore/ILinqToDBForEFTools.cs +++ b/Source/LinqToDB.EntityFrameworkCore/ILinqToDBForEFTools.cs @@ -28,10 +28,11 @@ public interface ILinqToDBForEFTools /// /// Returns LINQ To DB provider, based on provider data from EF Core. /// + /// Linq To DB context options. /// Provider information, extracted from EF Core. /// Database connection information. /// LINQ TO DB provider instance. - IDataProvider? GetDataProvider(EFProviderInfo providerInfo, EFConnectionInfo connectionInfo); + IDataProvider? GetDataProvider(DataOptions options, EFProviderInfo providerInfo, EFConnectionInfo connectionInfo); /// /// Creates metadata provider for specified EF Core data model. Default implementation uses @@ -50,8 +51,9 @@ public interface ILinqToDBForEFTools /// EF Core data model. /// Additional optional LINQ To DB database metadata provider. /// EF Core registry for type conversion. + /// Linq To DB context options. /// Mapping schema for provided EF Core model. - MappingSchema CreateMappingSchema(IModel model, IMetadataReader metadataReader, IValueConverterSelector convertorSelector); + MappingSchema CreateMappingSchema(IModel model, IMetadataReader metadataReader, IValueConverterSelector convertorSelector, DataOptions dataOptions); /// /// Returns mapping schema using provided EF Core data model and metadata provider. @@ -59,8 +61,9 @@ public interface ILinqToDBForEFTools /// EF Core data model. /// Additional optional LINQ To DB database metadata provider. /// EF Core registry for type conversion. + /// Linq To DB context options. /// Mapping schema for provided EF Core model. - MappingSchema GetMappingSchema(IModel model, IMetadataReader? metadataReader, IValueConverterSelector? convertorSelector); + MappingSchema GetMappingSchema(IModel model, IMetadataReader? metadataReader, IValueConverterSelector? convertorSelector, DataOptions? dataOptions); /// /// Returns EF Core for specific instance. diff --git a/Source/LinqToDB.EntityFrameworkCore/Internal/EFCoreExpressionAttribute.cs b/Source/LinqToDB.EntityFrameworkCore/Internal/EFCoreExpressionAttribute.cs index ba3ecc6..2155ce4 100644 --- a/Source/LinqToDB.EntityFrameworkCore/Internal/EFCoreExpressionAttribute.cs +++ b/Source/LinqToDB.EntityFrameworkCore/Internal/EFCoreExpressionAttribute.cs @@ -8,7 +8,7 @@ namespace LinqToDB.EntityFrameworkCore.Internal { /// - /// Maps linq2db expression. + /// Maps Linq To DB expression. /// public class EFCoreExpressionAttribute : Sql.ExpressionAttribute { diff --git a/Source/LinqToDB.EntityFrameworkCore/Internal/LinqToDBOptionsExtension.cs b/Source/LinqToDB.EntityFrameworkCore/Internal/LinqToDBOptionsExtension.cs index 0eb3ee4..e3a1182 100644 --- a/Source/LinqToDB.EntityFrameworkCore/Internal/LinqToDBOptionsExtension.cs +++ b/Source/LinqToDB.EntityFrameworkCore/Internal/LinqToDBOptionsExtension.cs @@ -1,25 +1,21 @@ -using System.Collections.Generic; +using System.Linq; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace LinqToDB.EntityFrameworkCore.Internal { - using Interceptors; - /// - /// Model containing LinqToDB related context options + /// Model containing LinqToDB related context options. /// public class LinqToDBOptionsExtension : IDbContextOptionsExtension { private string? _logFragment; - private IList? _interceptors; - /// /// List of registered LinqToDB interceptors /// - public virtual IList Interceptors => _interceptors ??= new List(); + public virtual DataOptions Options { get; set; } /// public string LogFragment @@ -30,9 +26,9 @@ public string LogFragment { string logFragment = string.Empty; - if (_interceptors?.Count > 0) + if (Options.DataContextOptions.Interceptors?.Count > 0) { - _logFragment = $"Interceptors count: {_interceptors.Count}"; + _logFragment = $"Interceptors count: {Options.DataContextOptions.Interceptors.Count}"; } else { @@ -49,6 +45,7 @@ public string LogFragment /// public LinqToDBOptionsExtension() { + Options = new(); } /// @@ -57,7 +54,7 @@ public LinqToDBOptionsExtension() /// protected LinqToDBOptionsExtension(LinqToDBOptionsExtension copyFrom) { - _interceptors = copyFrom._interceptors; + Options = copyFrom.Options; } /// @@ -71,7 +68,7 @@ protected LinqToDBOptionsExtension(LinqToDBOptionsExtension copyFrom) /// public void Validate(IDbContextOptions options) { - } + } /// public long GetServiceProviderHashCode() => 0; diff --git a/Source/LinqToDB.EntityFrameworkCore/LinqToDBContextOptionsBuilder.cs b/Source/LinqToDB.EntityFrameworkCore/LinqToDBContextOptionsBuilder.cs index 07a22ef..fee5147 100644 --- a/Source/LinqToDB.EntityFrameworkCore/LinqToDBContextOptionsBuilder.cs +++ b/Source/LinqToDB.EntityFrameworkCore/LinqToDBContextOptionsBuilder.cs @@ -1,19 +1,21 @@ -using Microsoft.EntityFrameworkCore; +using System; +using Microsoft.EntityFrameworkCore; namespace LinqToDB.EntityFrameworkCore { - using Internal; using Interceptors; + using Internal; + using Mapping; /// - /// LinqToDB context options builder + /// Linq To DB context options builder /// - public class LinqToDBContextOptionsBuilder + public class LinqToDBContextOptionsBuilder { - private readonly LinqToDBOptionsExtension _extension; + private readonly LinqToDBOptionsExtension? _extension; /// - /// Db context options + /// Db context options. /// public DbContextOptions DbContextOptions { get; private set; } @@ -28,13 +30,41 @@ public LinqToDBContextOptionsBuilder(DbContextOptionsBuilder optionsBuilder) } /// - /// Registers LinqToDb interceptor + /// Registers Linq To DB interceptor. /// - /// The interceptor instance to register + /// The interceptor instance to register. /// public LinqToDBContextOptionsBuilder AddInterceptor(IInterceptor interceptor) { - _extension.Interceptors.Add(interceptor); + if (_extension != null) + _extension.Options = _extension.Options.UseInterceptor(interceptor); + + return this; + } + + /// + /// Registers custom Linq To DB MappingSchema. + /// + /// The interceptor instance to register. + /// + public LinqToDBContextOptionsBuilder AddMappingSchema(MappingSchema mappingSchema) + { + if (_extension != null) + _extension.Options = _extension.Options.UseMappingSchema(mappingSchema); + + return this; + } + + /// + /// Registers custom Linq To DB options. + /// + /// Function to setup custom Linq To DB options. + /// + public LinqToDBContextOptionsBuilder AddCustomOptions(Func optionsSetter) + { + if (_extension != null) + _extension.Options = optionsSetter(_extension.Options); + return this; } } diff --git a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextExtensions.cs b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextExtensions.cs index 9cee945..145feb7 100644 --- a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextExtensions.cs +++ b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextExtensions.cs @@ -2,18 +2,16 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using JetBrains.Annotations; - namespace LinqToDB.EntityFrameworkCore { extern alias interactive_async; using Data; - using Linq; using Internal; - using Interceptors; + using Linq; public static partial class LinqToDBForEFTools { @@ -31,7 +29,7 @@ public static BulkCopyRowsCopied BulkCopy(this DbContext context, BulkCopyOpt { if (context == null) throw new ArgumentNullException(nameof(context)); - using (var dc = context.CreateLinqToDbConnection()) + using (var dc = context.CreateLinqToDBConnection()) { return dc.BulkCopy(options, source); } @@ -49,7 +47,7 @@ public static BulkCopyRowsCopied BulkCopy(this DbContext context, int maxBatc { if (context == null) throw new ArgumentNullException(nameof(context)); - using (var dc = context.CreateLinqToDbConnection()) + using (var dc = context.CreateLinqToDBConnection()) { return dc.BulkCopy( new BulkCopyOptions { MaxBatchSize = maxBatchSize }, @@ -68,7 +66,7 @@ public static BulkCopyRowsCopied BulkCopy(this DbContext context, IEnumerable { if (context == null) throw new ArgumentNullException(nameof(context)); - using (var dc = context.CreateLinqToDbConnection()) + using (var dc = context.CreateLinqToDBConnection()) { return dc.BulkCopy( new BulkCopyOptions(), @@ -97,7 +95,7 @@ public static async Task BulkCopyAsync( if (context == null) throw new ArgumentNullException(nameof(context)); if (source == null) throw new ArgumentNullException(nameof(source)); - using (var dc = context.CreateLinqToDbConnection()) + using (var dc = context.CreateLinqToDBConnection()) { return await dc.BulkCopyAsync(options, source, cancellationToken).ConfigureAwait(Common.Configuration.ContinueOnCapturedContext); } @@ -126,7 +124,7 @@ public static async Task BulkCopyAsync( if (context == null) throw new ArgumentNullException(nameof(context)); if (source == null) throw new ArgumentNullException(nameof(source)); - using (var dc = context.CreateLinqToDbConnection()) + using (var dc = context.CreateLinqToDBConnection()) { return await dc.BulkCopyAsync(maxBatchSize, source, cancellationToken).ConfigureAwait(Common.Configuration.ContinueOnCapturedContext); } @@ -147,7 +145,7 @@ public static async Task BulkCopyAsync( if (context == null) throw new ArgumentNullException(nameof(context)); if (source == null) throw new ArgumentNullException(nameof(source)); - using (var dc = context.CreateLinqToDbConnection()) + using (var dc = context.CreateLinqToDBConnection()) { return await dc.BulkCopyAsync(source, cancellationToken).ConfigureAwait(Common.Configuration.ContinueOnCapturedContext); } @@ -172,7 +170,7 @@ public static IValueInsertable Into(this DbContext context, ITable targ if (context == null) throw new ArgumentNullException(nameof(context)); if (target == null) throw new ArgumentNullException(nameof(target)); - return context.CreateLinqToDbConnection().Into(target); + return context.CreateLinqToDBConnection().Into(target); } #endregion @@ -189,18 +187,18 @@ public static ITable GetTable(this DbContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); - return context.CreateLinqToDbContext().GetTable(); + return context.CreateLinqToDBContext().GetTable(); } #endregion - #region Interceptors + #region Options /// - /// Returns list of registered Linq2Db interceptors from EF Context + /// Returns Linq To DB context options from EF Context. /// - /// Db context object - public static IList? GetLinq2DbInterceptors(this DbContext context) + /// Db context object. + public static DataOptions? GetLinqToDBOptions(this DbContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); @@ -208,25 +206,18 @@ public static ITable GetTable(this DbContext context) var contextOptions = ((IInfrastructure)context.Database)? .Instance?.GetService(typeof(IDbContextOptions)) as IDbContextOptions; - return contextOptions?.GetLinq2DbInterceptors(); + return contextOptions?.GetLinqToDBOptions(); } /// - /// Returns list of registered Linq2Db interceptors from EF Context options + /// Returns Linq To DB context options from EF Context options. /// - /// Db context options - public static IList GetLinq2DbInterceptors(this IDbContextOptions contextOptions) + /// Db context options. + public static DataOptions? GetLinqToDBOptions(this IDbContextOptions contextOptions) { if (contextOptions == null) throw new ArgumentNullException(nameof(contextOptions)); - var linq2DbExtension = contextOptions?.FindExtension(); - List interceptors = new List(); - if (linq2DbExtension?.Interceptors != null) - { - interceptors.AddRange(linq2DbExtension.Interceptors); - } - - return interceptors; + return contextOptions?.FindExtension()?.Options; } #endregion diff --git a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextOptionsBuilderExtensions.cs b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextOptionsBuilderExtensions.cs index 63ad05a..f5461c0 100644 --- a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextOptionsBuilderExtensions.cs +++ b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextOptionsBuilderExtensions.cs @@ -9,19 +9,31 @@ namespace LinqToDB.EntityFrameworkCore public static partial class LinqToDBForEFTools { /// - /// Registers custom options related to LinqToDB provider + /// Registers custom options related to LinqToDB provider. /// /// - /// Custom options action + /// Custom options action. /// + [Obsolete($"Use {nameof(UseLinqToDB)} overload.")] public static DbContextOptionsBuilder UseLinqToDb( this DbContextOptionsBuilder optionsBuilder, - Action? linq2DbOptionsAction = null) + Action? linq2dbOptionsAction = null) + => UseLinqToDB(optionsBuilder, linq2dbOptionsAction); + + /// + /// Registers custom options related to LinqToDB provider. + /// + /// + /// Custom options action. + /// + public static DbContextOptionsBuilder UseLinqToDB( + this DbContextOptionsBuilder optionsBuilder, + Action? linq2dbOptionsAction = null) { ((IDbContextOptionsBuilderInfrastructure)optionsBuilder) .AddOrUpdateExtension(GetOrCreateExtension(optionsBuilder)); - linq2DbOptionsAction?.Invoke(new LinqToDBContextOptionsBuilder(optionsBuilder)); + linq2dbOptionsAction?.Invoke(new LinqToDBContextOptionsBuilder(optionsBuilder)); return optionsBuilder; } diff --git a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.Extensions.cs b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.Extensions.cs index dae9ec5..481c984 100644 --- a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.Extensions.cs +++ b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.Extensions.cs @@ -17,7 +17,7 @@ public static ITable ToLinqToDBTable(this DbSet dbSet) if (context == null) throw new LinqToDBForEFToolsException($"Can not load current context from {nameof(dbSet)}"); - var dc = CreateLinqToDbContext(context); + var dc = CreateLinqToDBContext(context); return dc.GetTable(); } diff --git a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.cs b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.cs index 82c2518..4049fad 100644 --- a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.cs +++ b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.cs @@ -57,11 +57,10 @@ static bool InitializeInternal() if (queryable.Provider is IQueryProviderAsync) return queryable; - var context = Implementation.GetCurrentContext(queryable); - if (context == null) - throw new LinqToDBForEFToolsException("Can not evaluate current context from query"); + var context = Implementation.GetCurrentContext(queryable) + ?? throw new LinqToDBForEFToolsException("Can not evaluate current context from query"); - var dc = CreateLinqToDbContext(context); + var dc = CreateLinqToDBContext(context); var newExpression = queryable.Expression; var result = (IQueryable)instantiator.MakeGenericMethod(queryable.ElementType) @@ -195,15 +194,14 @@ public static EFProviderInfo GetEFProviderInfo(DbContextOptions options) /// /// Returns LINQ To DB provider, based on provider data from EF Core. /// + /// Linq To DB context options. /// EF Core provider information. /// Database connection information. /// LINQ TO DB provider instance. - public static IDataProvider GetDataProvider(EFProviderInfo info, EFConnectionInfo connectionInfo) + public static IDataProvider GetDataProvider(DataOptions options, EFProviderInfo info, EFConnectionInfo connectionInfo) { - var provider = Implementation.GetDataProvider(info, connectionInfo); - - if (provider == null) - throw new LinqToDBForEFToolsException("Can not detect provider from Entity Framework or provider not supported"); + var provider = Implementation.GetDataProvider(options, info, connectionInfo) + ?? throw new LinqToDBForEFToolsException("Can not detect provider from Entity Framework or provider not supported"); return provider; } @@ -213,14 +211,16 @@ public static IDataProvider GetDataProvider(EFProviderInfo info, EFConnectionInf /// /// EF Core data model. /// EF Core service provider. + /// Linq To DB context options. /// Mapping schema for provided EF Core model. public static MappingSchema GetMappingSchema( IModel model, - IInfrastructure? accessor) + IInfrastructure? accessor, + DataOptions? dataOptions) { var converterSelector = accessor?.GetService(); - return Implementation.GetMappingSchema(model, GetMetadataReader(model, accessor), converterSelector); + return Implementation.GetMappingSchema(model, GetMetadataReader(model, accessor), converterSelector, dataOptions); } /// @@ -244,34 +244,51 @@ public static Expression TransformExpression(Expression expression, IDataContext /// Optional transaction instance, to which created connection should be attached. /// If not specified, will use current transaction if it available. /// LINQ To DB instance. + [Obsolete($"Use {nameof(CreateLinqToDBConnection)} overload.")] public static DataConnection CreateLinqToDbConnection(this DbContext context, IDbContextTransaction? transaction = null) + => CreateLinqToDBConnection(context, transaction); + + /// + /// Creates LINQ To DB instance, attached to provided + /// EF Core instance connection and transaction. + /// + /// EF Core instance. + /// Optional transaction instance, to which created connection should be attached. + /// If not specified, will use current transaction if it available. + /// LINQ To DB instance. + public static DataConnection CreateLinqToDBConnection(this DbContext context, + IDbContextTransaction? transaction = null) { if (context == null) throw new ArgumentNullException(nameof(context)); - var info = GetEFProviderInfo(context); + var info = GetEFProviderInfo(context); + var options = context.GetLinqToDBOptions() ?? new DataOptions(); + options = AddMappingSchema(options, GetMappingSchema(context.Model, context, options)); DataConnection? dc = null; transaction = transaction ?? context.Database.CurrentTransaction; var connectionInfo = GetConnectionInfo(info); - var provider = GetDataProvider(info, connectionInfo); + var provider = GetDataProvider(options, info, connectionInfo); if (transaction != null) { - var dbTrasaction = transaction.GetDbTransaction(); + var dbTransaction = transaction.GetDbTransaction(); // TODO: we need API for testing current connection - //if (provider.IsCompatibleConnection(dbTrasaction.Connection)) - dc = new LinqToDBForEFToolsDataConnection(context, provider, dbTrasaction, context.Model, TransformExpression); + //if (provider.IsCompatibleConnection(dbTransaction.Connection)) + options = options.UseTransaction(provider, dbTransaction); + dc = new LinqToDBForEFToolsDataConnection(context, options, context.Model, TransformExpression); } if (dc == null) { var dbConnection = context.Database.GetDbConnection(); // TODO: we need API for testing current connection + options = options.UseConnection(provider, dbConnection); if (true /*provider.IsCompatibleConnection(dbConnection)*/) - dc = new LinqToDBForEFToolsDataConnection(context, provider, dbConnection, context.Model, TransformExpression); + dc = new LinqToDBForEFToolsDataConnection(context, options, context.Model, TransformExpression); else { //dc = new LinqToDBForEFToolsDataConnection(context, provider, connectionInfo.ConnectionString, context.Model, TransformExpression); @@ -280,15 +297,7 @@ public static DataConnection CreateLinqToDbConnection(this DbContext context, var logger = CreateLogger(info.Options); if (logger != null) - { EnableTracing(dc, logger); - } - - var mappingSchema = GetMappingSchema(context.Model, context); - if (mappingSchema != null) - dc.AddMappingSchema(mappingSchema); - - AddInterceptorsToDataContext(context, dc); return dc; } @@ -313,26 +322,38 @@ static void EnableTracing(DataConnection dc, ILogger logger) } /// - /// Creates linq2db data context for EF Core database context. + /// Creates Linq To DB data context for EF Core database context. /// /// EF Core database context. /// Transaction instance. - /// linq2db data context. + /// Linq To DB data context. + [Obsolete($"Use {nameof(CreateLinqToDBContext)} overload.")] public static IDataContext CreateLinqToDbContext(this DbContext context, IDbContextTransaction? transaction = null) + => CreateLinqToDBContext(context, transaction); + + /// + /// Creates Linq To DB data context for EF Core database context. + /// + /// EF Core database context. + /// Transaction instance. + /// Linq To DB data context. + public static IDataContext CreateLinqToDBContext(this DbContext context, + IDbContextTransaction? transaction = null) { if (context == null) throw new ArgumentNullException(nameof(context)); - var info = GetEFProviderInfo(context); + var info = GetEFProviderInfo(context); + var options = context.GetLinqToDBOptions() ?? new DataOptions(); + options = AddMappingSchema(options, GetMappingSchema(context.Model, context, options)); DataConnection? dc = null; transaction = transaction ?? context.Database.CurrentTransaction; - var connectionInfo = GetConnectionInfo(info); - var provider = GetDataProvider(info, connectionInfo); - var mappingSchema = GetMappingSchema(context.Model, context); - var logger = CreateLogger(info.Options); + var connectionInfo = GetConnectionInfo(info); + var provider = GetDataProvider(options, info, connectionInfo); + var logger = CreateLogger(info.Options); if (transaction != null) { @@ -340,15 +361,17 @@ public static IDataContext CreateLinqToDbContext(this DbContext context, // TODO: we need API for testing current connection // if (provider.IsCompatibleConnection(dbTransaction.Connection)) - dc = new LinqToDBForEFToolsDataConnection(context, provider, dbTransaction, context.Model, TransformExpression); + options = options.UseTransaction(provider, dbTransaction); + dc = new LinqToDBForEFToolsDataConnection(context, options, context.Model, TransformExpression); } if (dc == null) { var dbConnection = context.Database.GetDbConnection(); // TODO: we need API for testing current connection + options = options.UseConnection(provider, dbConnection); if (true /*provider.IsCompatibleConnection(dbConnection)*/) - dc = new LinqToDBForEFToolsDataConnection(context, provider, context.Database.GetDbConnection(), context.Model, TransformExpression); + dc = new LinqToDBForEFToolsDataConnection(context, options, context.Model, TransformExpression); else { /* @@ -366,15 +389,8 @@ public static IDataContext CreateLinqToDbContext(this DbContext context, } } - if (mappingSchema != null) - dc.AddMappingSchema(mappingSchema); - if (logger != null) - { EnableTracing(dc, logger); - } - - AddInterceptorsToDataContext(context, dc); return dc; } @@ -385,25 +401,34 @@ public static IDataContext CreateLinqToDbContext(this DbContext context, /// /// EF Core instance. /// LINQ To DB instance. + [Obsolete($"Use {nameof(CreateLinqToDBConnectionDetached)} overload.")] public static DataConnection CreateLinq2DbConnectionDetached(this DbContext context) + => CreateLinqToDBConnectionDetached(context); + + /// + /// Creates LINQ To DB instance that creates new database connection using connection + /// information from EF Core instance. + /// + /// EF Core instance. + /// LINQ To DB instance. + public static DataConnection CreateLinqToDBConnectionDetached(this DbContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); var info = GetEFProviderInfo(context); var connectionInfo = GetConnectionInfo(info); - var dataProvider = GetDataProvider(info, connectionInfo); + var options = context.GetLinqToDBOptions() ?? new DataOptions(); + var dataProvider = GetDataProvider(options, info, connectionInfo); + + options = AddMappingSchema(options, GetMappingSchema(context.Model, context, options)) + .UseDataProvider(dataProvider) + .UseConnectionString(connectionInfo.ConnectionString!); - var dc = new LinqToDBForEFToolsDataConnection(context, dataProvider, connectionInfo.ConnectionString!, context.Model, TransformExpression); + var dc = new LinqToDBForEFToolsDataConnection(context, options, context.Model, TransformExpression); var logger = CreateLogger(info.Options); if (logger != null) - { EnableTracing(dc, logger); - } - - var mappingSchema = GetMappingSchema(context.Model, context); - if (mappingSchema != null) - dc.AddMappingSchema(mappingSchema); return dc; } @@ -470,38 +495,49 @@ public static EFConnectionInfo GetConnectionInfo(EFProviderInfo info) /// /// EF Core instance. /// New LINQ To DB instance. + [Obsolete($"Use {nameof(CreateLinqToDBConnection)} overload.")] public static DataConnection CreateLinqToDbConnection(this DbContextOptions options) + => CreateLinqToDBConnection(options); + + /// + /// Creates new LINQ To DB instance using connectivity information from + /// EF Core instance. + /// + /// EF Core instance. + /// New LINQ To DB instance. + public static DataConnection CreateLinqToDBConnection(this DbContextOptions options) { var info = GetEFProviderInfo(options); DataConnection? dc = null; var connectionInfo = GetConnectionInfo(info); - var dataProvider = GetDataProvider(info, connectionInfo); + var dataOptions = options.GetLinqToDBOptions() ?? new DataOptions(); + var dataProvider = GetDataProvider(dataOptions, info, connectionInfo); var model = GetModel(options); + if (model != null) + dataOptions = AddMappingSchema(dataOptions, GetMappingSchema(model, null, dataOptions)); + + dataOptions = dataOptions.UseDataProvider(dataProvider); + if (connectionInfo.Connection != null) - dc = new LinqToDBForEFToolsDataConnection(null, dataProvider, connectionInfo.Connection, model, TransformExpression); + { + dataOptions = dataOptions.UseConnection(connectionInfo.Connection); + dc = new LinqToDBForEFToolsDataConnection(null, dataOptions, model, TransformExpression); + } else if (connectionInfo.ConnectionString != null) - dc = new LinqToDBForEFToolsDataConnection(null, dataProvider, connectionInfo.ConnectionString, model, TransformExpression); + { + dataOptions = dataOptions.UseConnectionString(connectionInfo.ConnectionString); + dc = new LinqToDBForEFToolsDataConnection(null, dataOptions, model, TransformExpression); + } if (dc == null) throw new LinqToDBForEFToolsException($"Can not extract connection information from {nameof(DbContextOptions)}"); var logger = CreateLogger(info.Options); if (logger != null) - { EnableTracing(dc, logger); - } - - if (model != null) - { - var mappingSchema = GetMappingSchema(model, null); - if (mappingSchema != null) - dc.AddMappingSchema(mappingSchema); - } - - AddInterceptorsToDataContext(options, dc); return dc; } @@ -515,14 +551,27 @@ public static DataConnection CreateLinqToDbConnection(this DbContextOptions opti /// LINQ To DB query, attached to provided . public static IQueryable ToLinqToDB(this IQueryable query, IDataContext dc) { - var context = Implementation.GetCurrentContext(query); - if (context == null) - throw new LinqToDBForEFToolsException("Can not evaluate current context from query"); + if (query == null) throw new ArgumentNullException(nameof(query)); + if (dc == null) throw new ArgumentNullException(nameof(dc)); + + var context = Implementation.GetCurrentContext(query) + ?? throw new LinqToDBForEFToolsException("Can not evaluate current context from query"); AddInterceptorsToDataContext(context, dc); return new LinqToDBForEFQueryProvider(dc, query.Expression); } + private static void AddInterceptorsToDataContext(DbContext context, IDataContext dc) + { + var options = context.GetLinqToDBOptions(); + + if (options?.DataContextOptions.Interceptors?.Any() == true) + { + foreach (var interceptor in options.DataContextOptions.Interceptors) + dc.AddInterceptor(interceptor); + } + } + /// /// Converts EF Core's query to LINQ To DB query and attach it to current EF Core connection. /// @@ -536,11 +585,10 @@ public static IQueryable ToLinqToDB(this IQueryable query) return query; } - var context = Implementation.GetCurrentContext(query); - if (context == null) - throw new LinqToDBForEFToolsException("Can not evaluate current context from query"); + var context = Implementation.GetCurrentContext(query) + ?? throw new LinqToDBForEFToolsException("Can not evaluate current context from query"); - var dc = CreateLinqToDbContext(context); + var dc = CreateLinqToDBContext(context); return new LinqToDBForEFQueryProvider(dc, query.Expression); } @@ -559,34 +607,18 @@ public static IQueryable ToLinqToDB(this IQueryable query) /// Enables attaching entities to change tracker. /// Entities will be attached only if AsNoTracking() is not used in query and DbContext is configured to track entities. /// - public static bool EnableChangeTracker + public static bool EnableChangeTracker { get => Implementation.EnableChangeTracker; set => Implementation.EnableChangeTracker = value; } - private static void AddInterceptorsToDataContext(DbContext efContext, IDataContext dc) + private static DataOptions AddMappingSchema(DataOptions dataOptions, MappingSchema mappingSchema) { - var contextOptions = ((IInfrastructure)efContext.Database)? - .Instance?.GetService(typeof(IDbContextOptions)) as IDbContextOptions; - - AddInterceptorsToDataContext(contextOptions, dc); - } - - private static void AddInterceptorsToDataContext(IDbContextOptions? contextOptions, - IDataContext dc) - { - var registeredInterceptors = contextOptions?.GetLinq2DbInterceptors(); + if (dataOptions.ConnectionOptions.MappingSchema != null) + return dataOptions.UseMappingSchema(MappingSchema.CombineSchemas(dataOptions.ConnectionOptions.MappingSchema, mappingSchema)); - if (registeredInterceptors != null - - && dc != null ) - { - foreach (var interceptor in registeredInterceptors) - { - dc.AddInterceptor(interceptor); - } - } + return dataOptions.UseMappingSchema(mappingSchema); } } } diff --git a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsDataConnection.cs b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsDataConnection.cs index d91abb5..e03413d 100644 --- a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsDataConnection.cs +++ b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsDataConnection.cs @@ -5,25 +5,21 @@ using System.Reflection; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Options; namespace LinqToDB.EntityFrameworkCore { - using System.Diagnostics.CodeAnalysis; using Data; - using DataProvider; - using Linq; using Expressions; - using LinqToDB.Interceptors; - using System.Data.Common; + using Interceptors; + using Linq; /// - /// linq2db EF.Core data connection. + /// Linq To DB EF.Core data connection. /// public class LinqToDBForEFToolsDataConnection : DataConnection, IExpressionPreprocessor, IEntityServiceInterceptor { @@ -34,11 +30,14 @@ public class LinqToDBForEFToolsDataConnection : DataConnection, IExpressionPrepr private Type? _lastType; private IStateManager? _stateManager; - private static IMemoryCache _entityKeyGetterCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); + private static IMemoryCache _entityKeyGetterCache = new MemoryCache(Microsoft.Extensions.Options.Options.Create(new MemoryCacheOptions())); private static MethodInfo TryGetEntryMethodInfo = MemberHelper.MethodOf(sm => sm.TryGetEntry(null!, Array.Empty())); + private static ConstructorInfo TupleConstructor = + MemberHelper.ConstructorOf(() => new Tuple(null, false)); + /// /// Change tracker enable flag. /// @@ -53,63 +52,14 @@ public class LinqToDBForEFToolsDataConnection : DataConnection, IExpressionPrepr /// Creates new instance of data connection. /// /// EF.Core database context. - /// linq2db database provider. - /// Connection string. - /// EF.Core data model. - /// Expression converter. - public LinqToDBForEFToolsDataConnection( - DbContext? context, - [NotNull] IDataProvider dataProvider, - [NotNull] string connectionString, - IModel? model, - Func? transformFunc) : base(dataProvider, connectionString) - { - Context = context; - _model = model; - _transformFunc = transformFunc; - CopyDatabaseProperties(); - if (LinqToDBForEFTools.EnableChangeTracker) - AddInterceptor(this); - } - - /// - /// Creates new instance of data connection. - /// - /// EF.Core database context. - /// linq2db database provider. - /// Database transaction. - /// EF.Core data model. - /// Expression converter. - public LinqToDBForEFToolsDataConnection( - DbContext? context, - [NotNull] IDataProvider dataProvider, - [NotNull] DbTransaction transaction, - IModel? model, - Func? transformFunc - ) : base(dataProvider, transaction) - { - Context = context; - _model = model; - _transformFunc = transformFunc; - CopyDatabaseProperties(); - if (LinqToDBForEFTools.EnableChangeTracker) - AddInterceptor(this); - } - - /// - /// Creates new instance of data connection. - /// - /// EF.Core database context. - /// linq2db database provider. - /// Database connection instance. + /// Linq To DB context options. /// EF.Core data model. /// Expression converter. public LinqToDBForEFToolsDataConnection( - DbContext? context, - [NotNull] IDataProvider dataProvider, - [NotNull] DbConnection connection, - IModel? model, - Func? transformFunc) : base(dataProvider, connection) + DbContext? context, + DataOptions options, + IModel? model, + Func? transformFunc) : base(options) { Context = context; _model = model; @@ -140,7 +90,7 @@ public TypeKey(IEntityType entityType, IModel? model) } public IEntityType EntityType { get; } - public IModel? Model { get; } + public IModel? Model { get; } private bool Equals(TypeKey other) { @@ -187,7 +137,7 @@ object IEntityServiceInterceptor.EntityCreated(EntityCreatedEventData eventData, return entity; if (!LinqToDBForEFTools.EnableChangeTracker - || !Tracking + || !Tracking || Context!.ChangeTracker.QueryTrackingBehavior == QueryTrackingBehavior.NoTracking) return entity; @@ -205,9 +155,7 @@ object IEntityServiceInterceptor.EntityCreated(EntityCreatedEventData eventData, if (eventData.TableName != _lastEntityType.Relational().TableName) return entity; - if (_stateManager == null) - _stateManager = Context.GetService(); - + _stateManager ??= Context.GetService(); // It is a real pain to register entity in change tracker // @@ -224,17 +172,17 @@ object IEntityServiceInterceptor.EntityCreated(EntityCreatedEventData eventData, if (retrievalFunc == null) return entity; - entry = retrievalFunc(_stateManager, entity); + var (retrieved, valid) = retrievalFunc(_stateManager, entity); - if (entry == null) - { - entry = _stateManager.StartTrackingFromQuery(_lastEntityType, entity, ValueBuffer.Empty, null); - } + if (!valid) + return entity; + + entry = retrieved ?? _stateManager.StartTrackingFromQuery(_lastEntityType, entity, ValueBuffer.Empty, null); return entry.Entity; } - private Func? CreateEntityRetrievalFunc(IEntityType entityType) + private Func>? CreateEntityRetrievalFunc(IEntityType entityType) { var stateManagerParam = Expression.Parameter(typeof(IStateManager), "sm"); var objParam = Expression.Parameter(typeof(object), "o"); @@ -243,9 +191,13 @@ object IEntityServiceInterceptor.EntityCreated(EntityCreatedEventData eventData, var assignExpr = Expression.Assign(variable, Expression.Convert(objParam, entityType.ClrType)); var key = entityType.GetKeys().FirstOrDefault(); + if (key == null) + return null; + + var properties = key.Properties.Where(p => p.PropertyInfo != null || p.FieldInfo != null).ToList(); - var arrayExpr = key.Properties.Where(p => p.PropertyInfo != null || p.FieldInfo != null).Select(p => - Expression.Convert(Expression.MakeMemberAccess(variable, p.PropertyInfo ?? (MemberInfo)p.FieldInfo), + var arrayExpr = properties.Select(p => + Expression.Convert(Expression.MakeMemberAccess(variable, p.PropertyInfo ?? (MemberInfo)p.FieldInfo!), typeof(object))) .ToArray(); @@ -254,13 +206,29 @@ object IEntityServiceInterceptor.EntityCreated(EntityCreatedEventData eventData, var newArrayExpression = Expression.NewArrayInit(typeof(object), arrayExpr); var body = - Expression.Block(new[] { variable }, - assignExpr, - Expression.Call(stateManagerParam, TryGetEntryMethodInfo, Expression.Constant(key), - newArrayExpression)); + (Expression)Expression.New(TupleConstructor, Expression.Call(stateManagerParam, TryGetEntryMethodInfo, + Expression.Constant(key), + newArrayExpression), Expression.Constant(true)); + + if (properties.Any(p => !p.ClrType.IsValueType)) + { + var checkExpression = properties + .Where(p => !p.ClrType.IsValueType) + .Select(p => + Expression.Equal( + Expression.MakeMemberAccess(variable, p.PropertyInfo ?? (MemberInfo)p.FieldInfo!), + Expression.Default(p.ClrType))) + .Aggregate(Expression.OrElse); + + var invalidResult = Expression.New(TupleConstructor, Expression.Default(typeof(InternalEntityEntry)), Expression.Constant(false)); + + body = Expression.Condition(checkExpression, invalidResult, body); + } + + body = Expression.Block(new[] { variable }, assignExpr, body); var lambda = - Expression.Lambda>(body, stateManagerParam, objParam); + Expression.Lambda>>(body, stateManagerParam, objParam); return lambda.Compile(); } diff --git a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsDataContext.cs b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsDataContext.cs index 2ceea07..122e024 100644 --- a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsDataContext.cs +++ b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsDataContext.cs @@ -11,7 +11,7 @@ namespace LinqToDB.EntityFrameworkCore using Linq; /// - /// linq2db EF.Core data context. + /// Linq To DB EF.Core data context. /// public class LinqToDBForEFToolsDataContext : DataContext, IExpressionPreprocessor { diff --git a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsImplDefault.cs b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsImplDefault.cs index 7d6a64c..88e714e 100644 --- a/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsImplDefault.cs +++ b/Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsImplDefault.cs @@ -9,14 +9,12 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.Extensions.Logging; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Caching.Memory; namespace LinqToDB.EntityFrameworkCore @@ -107,55 +105,63 @@ public virtual void ClearCaches() /// Returns LINQ To DB provider, based on provider data from EF Core. /// Could be overriden if you have issues with default detection mechanisms. /// + /// Linq To DB context options. /// Provider information, extracted from EF Core. /// /// LINQ TO DB provider instance. - public virtual IDataProvider GetDataProvider(EFProviderInfo providerInfo, EFConnectionInfo connectionInfo) + public virtual IDataProvider GetDataProvider(DataOptions options, EFProviderInfo providerInfo, EFConnectionInfo connectionInfo) { - var info = GetLinqToDbProviderInfo(providerInfo); + if (options.ConnectionOptions.DataProvider != null) + return options.ConnectionOptions.DataProvider; + + LinqToDBProviderInfo info; + if (options.ConnectionOptions.ProviderName != null) + info = new LinqToDBProviderInfo() { ProviderName = options.ConnectionOptions.ProviderName }; + else + info = GetLinqToDBProviderInfo(providerInfo); return _knownProviders.GetOrAdd(new ProviderKey(info.ProviderName, connectionInfo.ConnectionString), k => { - return CreateLinqToDbDataProvider(providerInfo, info, connectionInfo); + return CreateLinqToDBDataProvider(providerInfo, info, connectionInfo); }); } /// - /// Converts EF Core provider settings to linq2db provider settings. + /// Converts EF Core provider settings to Linq To DB provider settings. /// /// EF Core provider settings. - /// linq2db provider settings. - protected virtual LinqToDBProviderInfo GetLinqToDbProviderInfo(EFProviderInfo providerInfo) + /// Linq To DB provider settings. + protected virtual LinqToDBProviderInfo GetLinqToDBProviderInfo(EFProviderInfo providerInfo) { var provInfo = new LinqToDBProviderInfo(); var relational = providerInfo.Options?.Extensions.OfType().FirstOrDefault(); if (relational != null) { - provInfo.Merge(GetLinqToDbProviderInfo(relational)); + provInfo.Merge(GetLinqToDBProviderInfo(relational)); } if (providerInfo.Connection != null) { - provInfo.Merge(GetLinqToDbProviderInfo(providerInfo.Connection)); + provInfo.Merge(GetLinqToDBProviderInfo(providerInfo.Connection)); } if (providerInfo.Context != null) { - provInfo.Merge(GetLinqToDbProviderInfo(providerInfo.Context.Database)); + provInfo.Merge(GetLinqToDBProviderInfo(providerInfo.Context.Database)); } return provInfo; } /// - /// Creates instance of linq2db database provider. + /// Creates instance of Linq To DB database provider. /// /// EF Core provider settings. - /// linq2db provider settings. + /// Linq To DB provider settings. /// EF Core connection settings. - /// linq2db database provider. - protected virtual IDataProvider CreateLinqToDbDataProvider(EFProviderInfo providerInfo, LinqToDBProviderInfo provInfo, + /// Linq To DB database provider. + protected virtual IDataProvider CreateLinqToDBDataProvider(EFProviderInfo providerInfo, LinqToDBProviderInfo provInfo, EFConnectionInfo connectionInfo) { if (provInfo.ProviderName == null) @@ -194,7 +200,7 @@ protected virtual IDataProvider CreateLinqToDbDataProvider(EFProviderInfo provid case ProviderName.Oracle11Devart: return OracleTools.GetDataProvider(OracleVersion.v11, OracleProvider.Devart); case ProviderName.OracleDevart: - return OracleTools.GetDataProvider(OracleVersion.v12, OracleProvider.Devart); + return OracleTools.GetDataProvider( OracleVersion.v12, OracleProvider.Devart); case ProviderName.SqlCe: return SqlCeTools.GetDataProvider(); @@ -207,11 +213,11 @@ protected virtual IDataProvider CreateLinqToDbDataProvider(EFProviderInfo provid } /// - /// Creates linq2db provider settings object from instance. + /// Creates Linq To DB provider settings object from instance. /// /// EF Core database information object. - /// linq2db provider settings. - protected virtual LinqToDBProviderInfo? GetLinqToDbProviderInfo(DatabaseFacade database) + /// Linq To DB provider settings. + protected virtual LinqToDBProviderInfo? GetLinqToDBProviderInfo(DatabaseFacade database) { switch (database.ProviderName) { @@ -263,11 +269,11 @@ protected virtual IDataProvider CreateLinqToDbDataProvider(EFProviderInfo provid } /// - /// Creates linq2db provider settings object from instance. + /// Creates Linq To DB provider settings object from instance. /// /// Database connection. - /// linq2db provider settings. - protected virtual LinqToDBProviderInfo? GetLinqToDbProviderInfo(DbConnection connection) + /// Linq To DB provider settings. + protected virtual LinqToDBProviderInfo? GetLinqToDBProviderInfo(DbConnection connection) { switch (connection.GetType().Name) { @@ -295,11 +301,11 @@ protected virtual IDataProvider CreateLinqToDbDataProvider(EFProviderInfo provid } /// - /// Creates linq2db provider settings object from instance. + /// Creates Linq To DB provider settings object from instance. /// /// EF Core provider options. - /// linq2db provider settings. - protected virtual LinqToDBProviderInfo? GetLinqToDbProviderInfo(RelationalOptionsExtension extensions) + /// Linq To DB provider settings. + protected virtual LinqToDBProviderInfo? GetLinqToDBProviderInfo(RelationalOptionsExtension extensions) { switch (extensions.GetType().Name) { @@ -331,30 +337,24 @@ protected virtual IDataProvider CreateLinqToDbDataProvider(EFProviderInfo provid } /// - /// Creates linq2db SQL Server database provider instance. + /// Creates Linq To DB SQL Server database provider instance. /// /// SQL Server dialect. /// Connection string. - /// linq2db SQL Server provider instance. + /// Linq To DB SQL Server provider instance. protected virtual IDataProvider CreateSqlServerProvider(SqlServerVersion version, string? connectionString) { - if (!string.IsNullOrEmpty(connectionString)) - return DataConnection.GetDataProvider("System.Data.SqlClient", connectionString!)!; - - return DataProvider.SqlServer.SqlServerTools.GetDataProvider(version, SqlServerProvider.MicrosoftDataSqlClient); + return DataProvider.SqlServer.SqlServerTools.GetDataProvider(version, SqlServerProvider.SystemDataSqlClient); } /// - /// Creates linq2db PostgreSQL database provider instance. + /// Creates Linq To DB PostgreSQL database provider instance. /// /// PostgreSQL dialect. /// Connection string. - /// linq2db PostgreSQL provider instance. + /// Linq To DB PostgreSQL provider instance. protected virtual IDataProvider CreatePostgreSqlProvider(PostgreSQLVersion version, string? connectionString) { - if (!string.IsNullOrEmpty(connectionString)) - return DataConnection.GetDataProvider(ProviderName.PostgreSQL, connectionString!)!; - return PostgreSQLTools.GetDataProvider(version); } @@ -376,31 +376,35 @@ public virtual IMetadataReader CreateMetadataReader(IModel? model, IInfrastructu /// EF Core data model. /// Additional optional LINQ To DB database metadata provider. /// + /// Linq To DB context options. /// Mapping schema for provided EF.Core model. public virtual MappingSchema CreateMappingSchema( IModel model, IMetadataReader? metadataReader, - IValueConverterSelector? convertorSelector) + IValueConverterSelector? convertorSelector, + DataOptions dataOptions) { var schema = new MappingSchema(); if (metadataReader != null) schema.AddMetadataReader(metadataReader); - DefineConvertors(schema, model, convertorSelector); + DefineConvertors(schema, model, convertorSelector, dataOptions); return schema; } /// - /// Import type conversions from EF Core model into linq2db mapping schema. + /// Import type conversions from EF Core model into Linq To DB mapping schema. /// - /// linq2db mapping schema. + /// Linq To DB mapping schema. /// EF Core data mode. /// Type filter. + /// Linq To DB context options. public virtual void DefineConvertors( MappingSchema mappingSchema, IModel model, - IValueConverterSelector? convertorSelector) + IValueConverterSelector? convertorSelector, + DataOptions dataOptions) { if (mappingSchema == null) throw new ArgumentNullException(nameof(mappingSchema)); if (model == null) throw new ArgumentNullException(nameof(model)); @@ -462,7 +466,7 @@ void MapEFCoreType(Type modelType) modelType), toParam)); mappingSchema.SetValueToSqlConverter(modelType, (sb, dt, v) - => sqlConverter.Convert(sb, dt, converter.ConvertToProvider(v))); + => sqlConverter.Convert(sb, mappingSchema, dt, dataOptions, converter.ConvertToProvider(v))); } } @@ -484,21 +488,26 @@ private static Expression WithConvertToObject(Expression valueExpression) => valueExpression.Type != typeof(object) ? Expression.Convert(valueExpression, typeof(object)) : valueExpression; - + /// /// Returns mapping schema using provided EF Core data model and metadata provider. /// /// EF Core data model. /// Additional optional LINQ To DB database metadata provider. /// + /// Linq To DB context options. /// Mapping schema for provided EF.Core model. public virtual MappingSchema GetMappingSchema( IModel model, IMetadataReader? metadataReader, - IValueConverterSelector? convertorSelector) + IValueConverterSelector? convertorSelector, + DataOptions? dataOptions) { + dataOptions ??= new(); + var result = _schemaCache.GetOrCreate( - Tuple.Create( + ( + dataOptions, model, metadataReader, convertorSelector, @@ -507,8 +516,8 @@ public virtual MappingSchema GetMappingSchema( e => { e.SlidingExpiration = TimeSpan.FromHours(1); - return CreateMappingSchema(model, metadataReader, convertorSelector); - }); + return CreateMappingSchema(model, metadataReader, convertorSelector, dataOptions); + })!; return result; } @@ -538,45 +547,36 @@ public virtual MappingSchema GetMappingSchema( static readonly MethodInfo IncludeMethodInfoString = MemberHelper.MethodOfGeneric>(q => q.Include(string.Empty)); static readonly MethodInfo ThenIncludeMethodInfo = - MemberHelper.MethodOfGeneric>(q => q.ThenInclude(null)); + MemberHelper.MethodOfGeneric>(q => q.ThenInclude(null!)); static readonly MethodInfo TagWithMethodInfo = MemberHelper.MethodOfGeneric>(q => q.TagWith(string.Empty)); static readonly MethodInfo ThenIncludeEnumerableMethodInfo = - MemberHelper.MethodOfGeneric>>(q => q.ThenInclude(null)); + MemberHelper.MethodOfGeneric>>(q => q.ThenInclude(null!)); static readonly MethodInfo AsNoTrackingMethodInfo = MemberHelper.MethodOfGeneric>(q => q.AsNoTracking()); static readonly MethodInfo EFProperty = MemberHelper.MethodOfGeneric(() => EF.Property(1, "")); - static readonly MethodInfo - L2DBProperty = typeof(Sql).GetMethod(nameof(Sql.Property)).GetGenericMethodDefinition(); - static readonly MethodInfo L2DBFromSqlRawSqlStringMethodInfo = MemberHelper.MethodOfGeneric(dc => dc.FromSql(new Common.RawSqlString())); static readonly MethodInfo L2DBFromSqlFormattableStringMethodInfo = MemberHelper.MethodOfGeneric(dc => dc.FromSql($"some {1}")); - static readonly MethodInfo L2DBRemoveOrderByMethodInfo = - MemberHelper.MethodOfGeneric>(q => q.RemoveOrderBy()); - static readonly ConstructorInfo RawSqlStringConstructor = MemberHelper.ConstructorOf(() => new Common.RawSqlString("")); static readonly ConstructorInfo DataParameterConstructor = MemberHelper.ConstructorOf(() => new DataParameter("", "", DataType.Undefined, "")); static readonly MethodInfo ToSql = MemberHelper.MethodOfGeneric(() => Sql.ToSql(1)); - static readonly MethodInfo TagQueryMethodInfo = - MemberHelper.MethodOfGeneric>(q => q.TagQuery(string.Empty)); - /// /// Removes conversions from expression. /// /// Expression. /// Unwrapped expression. - [return: NotNullIfNotNull("ex")] + [return: NotNullIfNotNull(nameof(ex))] public static Expression? Unwrap(Expression? ex) { if (ex == null) @@ -868,7 +868,7 @@ TransformInfo LocalTransform(Expression e) return new TransformInfo(Expression.Call(method, methodCall.Arguments.Select(a => a.Transform(l => LocalTransform(l))) .ToArray()), false, true); } - else if (generic == L2DBRemoveOrderByMethodInfo) + else if (generic == Methods.LinqToDB.RemoveOrderBy) { // This is workaround. EagerLoading runs query again with RemoveOrderBy method. // it is only one possible way now how to detect nested query. @@ -876,8 +876,7 @@ TransformInfo LocalTransform(Expression e) } else if (generic == TagWithMethodInfo) { - var method = - TagQueryMethodInfo.MakeGenericMethod(methodCall.Method.GetGenericArguments()); + var method = Methods.LinqToDB.TagQuery.MakeGenericMethod(methodCall.Method.GetGenericArguments()); return new TransformInfo(Expression.Call(method, methodCall.Arguments.Select(a => a.Transform(l => LocalTransform(l))) .ToArray()), false, true); @@ -906,7 +905,7 @@ TransformInfo LocalTransform(Expression e) return new TransformInfo(Expression.Call(null, L2DBFromSqlRawSqlStringMethodInfo.MakeGenericMethod( methodCall.Method.GetGenericArguments()[0]), - Expression.Constant(dc), + Expression.Constant(dc), Expression.New(RawSqlStringConstructor, Expression.Property(methodCall.Arguments[1], "Format")), methodCall.Arguments[2]), false, true); } @@ -940,7 +939,7 @@ TransformInfo LocalTransform(Expression e) if (generic == EFProperty) { - var prop = Expression.Call(null, L2DBProperty.MakeGenericMethod(methodCall.Method.GetGenericArguments()[0]), + var prop = Expression.Call(null, Methods.LinqToDB.SqlExt.Property.MakeGenericMethod(methodCall.Method.GetGenericArguments()[0]), methodCall.Arguments[0], methodCall.Arguments[1]); return new TransformInfo(prop, false, true); } @@ -961,21 +960,15 @@ TransformInfo LocalTransform(Expression e) if (canWrap) { - var parameterInfo = parameters[i]; - var notParametrized = parameterInfo.GetCustomAttributes() - .FirstOrDefault(); - if (notParametrized != null) + if (parameters[i].HasAttribute()) { - if (newArguments == null) - { - newArguments = new List(methodCall.Arguments.Take(i)); - } + newArguments ??= new List(methodCall.Arguments.Take(i)); newArguments.Add(Expression.Call(ToSql.MakeGenericMethod(arg.Type), arg)); continue; } - } - + } + newArguments?.Add(methodCall.Arguments[i]); } @@ -989,7 +982,7 @@ TransformInfo LocalTransform(Expression e) return new TransformInfo(e); } - var newExpression = expression.Transform(e => LocalTransform(e)); + var newExpression = expression.Transform(LocalTransform); if (!ignoreTracking && dc is LinqToDBForEFToolsDataConnection dataConnection) { @@ -1022,7 +1015,7 @@ static Type GetEnumerableElementType(Type type, MappingSchema mappingSchema) if (!IsEnumerableType(type, mappingSchema)) return type; if (type.IsArray) - return type.GetElementType(); + return type.GetElementType()!; if (typeof(IGrouping<,>).IsSameOrParentOf(type)) return type.GetGenericArguments()[1]; return type.GetGenericArguments()[0]; @@ -1045,8 +1038,8 @@ static bool IsEnumerableType(Type type, MappingSchema mappingSchema) /// Current instance. public virtual DbContext? GetCurrentContext(IQueryable query) { - var compilerField = typeof (EntityQueryProvider).GetField("_queryCompiler", BindingFlags.NonPublic | BindingFlags.Instance); - var compiler = (QueryCompiler) compilerField.GetValue(query.Provider); + var compilerField = typeof (EntityQueryProvider).GetField("_queryCompiler", BindingFlags.NonPublic | BindingFlags.Instance)!; + var compiler = (QueryCompiler)compilerField.GetValue(query.Provider)!; var queryContextFactoryField = compiler.GetType().GetField("_queryContextFactory", BindingFlags.NonPublic | BindingFlags.Instance); diff --git a/Tests/LinqToDB.EntityFrameworkCore.BaseTests/ForMappingTestsBase.cs b/Tests/LinqToDB.EntityFrameworkCore.BaseTests/ForMappingTestsBase.cs index b66fca8..a8f0436 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.BaseTests/ForMappingTestsBase.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.BaseTests/ForMappingTestsBase.cs @@ -5,19 +5,20 @@ using FluentAssertions; using LinqToDB.Data; using LinqToDB.EntityFrameworkCore.BaseTests.Models.ForMapping; +using LinqToDB.Mapping; using NUnit.Framework; namespace LinqToDB.EntityFrameworkCore.BaseTests { public abstract class ForMappingTestsBase : TestsBase { - public abstract ForMappingContextBase CreateContext(); + public abstract ForMappingContextBase CreateContext(Func? optionsSetter = null); [Test] public virtual void TestIdentityMapping() { using var context = CreateContext(); - using var connection = context.CreateLinqToDbConnection(); + using var connection = context.CreateLinqToDBConnection(); var ed = connection.MappingSchema.GetEntityDescriptor(typeof(WithIdentity)); var pk = ed.Columns.Single(c => c.IsPrimaryKey); @@ -29,7 +30,7 @@ public virtual void TestIdentityMapping() public virtual void TestNoIdentityMapping() { using var context = CreateContext(); - using var connection = context.CreateLinqToDbConnection(); + using var connection = context.CreateLinqToDBConnection(); var ed = connection.MappingSchema.GetEntityDescriptor(typeof(NoIdentity)); var pk = ed.Columns.Single(c => c.IsPrimaryKey); @@ -41,7 +42,7 @@ public virtual void TestNoIdentityMapping() public virtual void TestTableCreation() { using var context = CreateContext(); - using var connection = context.CreateLinqToDbConnection(); + using var connection = context.CreateLinqToDBConnection(); using var t1 = connection.CreateTempTable(); using var t2 = connection.CreateTempTable(); @@ -52,7 +53,7 @@ public virtual void TestTableCreation() public virtual void TestBulkCopyNoIdentity() { using var context = CreateContext(); - using var connection = context.CreateLinqToDbConnection(); + using var connection = context.CreateLinqToDBConnection(); using var t = connection.CreateTempTable(); @@ -72,7 +73,7 @@ public virtual void TestBulkCopyNoIdentity() public virtual void TestBulkCopyWithIdentity() { using var context = CreateContext(); - using var connection = context.CreateLinqToDbConnection(); + using var connection = context.CreateLinqToDBConnection(); using var t = connection.CreateTempTable(); @@ -117,5 +118,48 @@ await FluentActions.Awaiting(() => context.WithDuplicateProperties.Where(x => x. .ToArrayAsyncLinqToDB()).Should().NotThrowAsync(); } + [Test] + public virtual void TestMappingSchemaCached() + { + using var context1 = CreateContext(); + using var context2 = CreateContext(); + using var connection1 = context1.CreateLinqToDBConnection(); + using var connection2 = context2.CreateLinqToDBConnection(); + + Assert.AreEqual(connection1.MappingSchema, connection2.MappingSchema); + } + + sealed class TestEntity + { + public int Field { get; set; } + } + + [Test] + public virtual void TestMappingSchemaCachedWithCustomSchema() + { + var ms = new MappingSchema("Test"); + new FluentMappingBuilder(ms) + .Entity() + .HasPrimaryKey(e => e.Field) + .Build(); + + using var context1 = CreateContext(o => o.UseMappingSchema(ms)); + using var context2 = CreateContext(o => o.UseMappingSchema(ms)); + using var connection1 = context1.CreateLinqToDBConnection(); + using var connection2 = context2.CreateLinqToDBConnection(); + + Assert.AreEqual(connection1.MappingSchema, connection2.MappingSchema); + + // check EF mapping is in place + var ed = connection1.MappingSchema.GetEntityDescriptor(typeof(WithIdentity)); + var pk = ed.Columns.Single(c => c.IsPrimaryKey); + pk.IsIdentity.Should().BeTrue(); + + // check additional mapping also used + ed = connection1.MappingSchema.GetEntityDescriptor(typeof(TestEntity)); + pk = ed.Columns.Single(c => c.IsPrimaryKey); + pk.IsIdentity.Should().BeFalse(); + Assert.AreEqual("Field", pk.ColumnName); + } } } diff --git a/Tests/LinqToDB.EntityFrameworkCore.BaseTests/Interceptors/TestEfCoreAndLinq2DbComboInterceptor.cs b/Tests/LinqToDB.EntityFrameworkCore.BaseTests/Interceptors/TestEfCoreAndLinq2DbComboInterceptor.cs deleted file mode 100644 index f52a6cd..0000000 --- a/Tests/LinqToDB.EntityFrameworkCore.BaseTests/Interceptors/TestEfCoreAndLinq2DbComboInterceptor.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Data; -using System.Data.Common; -using System.Threading; -using System.Threading.Tasks; -using LinqToDB.Common; -using LinqToDB.Interceptors; - -namespace LinqToDB.EntityFrameworkCore.BaseTests.Interceptors -{ - public class TestEfCoreAndLinq2DbComboInterceptor : TestInterceptor, ICommandInterceptor - { - #region LinqToDbInterceptor - public void AfterExecuteReader(LinqToDB.Interceptors.CommandEventData eventData, DbCommand command, CommandBehavior commandBehavior, DbDataReader dataReader) - { - HasInterceptorBeenInvoked = true; - } - - public void BeforeReaderDispose(LinqToDB.Interceptors.CommandEventData eventData, DbCommand? command, DbDataReader dataReader) - { - HasInterceptorBeenInvoked = true; - } - - public Task BeforeReaderDisposeAsync(LinqToDB.Interceptors.CommandEventData eventData, DbCommand? command, DbDataReader dataReader) - { - HasInterceptorBeenInvoked = true; - return Task.CompletedTask; - } - - public DbCommand CommandInitialized(LinqToDB.Interceptors.CommandEventData eventData, DbCommand command) - { - HasInterceptorBeenInvoked = true; - return command; - } - - public Option ExecuteNonQuery(LinqToDB.Interceptors.CommandEventData eventData, DbCommand command, Option result) - { - HasInterceptorBeenInvoked = true; - return result; - } - - public Task> ExecuteNonQueryAsync(LinqToDB.Interceptors.CommandEventData eventData, DbCommand command, Option result, CancellationToken cancellationToken) - { - HasInterceptorBeenInvoked = true; - return Task.FromResult(result); - } - - public Option ExecuteReader(LinqToDB.Interceptors.CommandEventData eventData, DbCommand command, CommandBehavior commandBehavior, Option result) - { - HasInterceptorBeenInvoked = true; - return result; - } - - public Task> ExecuteReaderAsync(LinqToDB.Interceptors.CommandEventData eventData, DbCommand command, CommandBehavior commandBehavior, Option result, CancellationToken cancellationToken) - { - HasInterceptorBeenInvoked = true; - return Task.FromResult(result); - } - - public Option ExecuteScalar(LinqToDB.Interceptors.CommandEventData eventData, DbCommand command, Option result) - { - HasInterceptorBeenInvoked = true; - return result; - } - - public Task> ExecuteScalarAsync(LinqToDB.Interceptors.CommandEventData eventData, DbCommand command, Option result, CancellationToken cancellationToken) - { - HasInterceptorBeenInvoked = true; - return Task.FromResult(result); - } - #endregion - } -} diff --git a/Tests/LinqToDB.EntityFrameworkCore.BaseTests/Interceptors/TestEfCoreAndLinqToDBComboInterceptor.cs b/Tests/LinqToDB.EntityFrameworkCore.BaseTests/Interceptors/TestEfCoreAndLinqToDBComboInterceptor.cs new file mode 100644 index 0000000..dce5529 --- /dev/null +++ b/Tests/LinqToDB.EntityFrameworkCore.BaseTests/Interceptors/TestEfCoreAndLinqToDBComboInterceptor.cs @@ -0,0 +1,72 @@ +using System.Data; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using LinqToDB.Common; +using LinqToDB.Interceptors; + +namespace LinqToDB.EntityFrameworkCore.BaseTests.Interceptors +{ + public class TestEfCoreAndLinqToDBComboInterceptor : TestInterceptor, ICommandInterceptor + { + #region LinqToDBInterceptor + public void AfterExecuteReader(CommandEventData eventData, DbCommand command, CommandBehavior commandBehavior, DbDataReader dataReader) + { + HasInterceptorBeenInvoked = true; + } + + public void BeforeReaderDispose(CommandEventData eventData, DbCommand? command, DbDataReader dataReader) + { + HasInterceptorBeenInvoked = true; + } + + public Task BeforeReaderDisposeAsync(CommandEventData eventData, DbCommand? command, DbDataReader dataReader) + { + HasInterceptorBeenInvoked = true; + return Task.CompletedTask; + } + + public DbCommand CommandInitialized(CommandEventData eventData, DbCommand command) + { + HasInterceptorBeenInvoked = true; + return command; + } + + public Option ExecuteNonQuery(CommandEventData eventData, DbCommand command, Option result) + { + HasInterceptorBeenInvoked = true; + return result; + } + + public Task> ExecuteNonQueryAsync(CommandEventData eventData, DbCommand command, Option result, CancellationToken cancellationToken) + { + HasInterceptorBeenInvoked = true; + return Task.FromResult(result); + } + + public Option ExecuteReader(CommandEventData eventData, DbCommand command, CommandBehavior commandBehavior, Option result) + { + HasInterceptorBeenInvoked = true; + return result; + } + + public Task> ExecuteReaderAsync(CommandEventData eventData, DbCommand command, CommandBehavior commandBehavior, Option result, CancellationToken cancellationToken) + { + HasInterceptorBeenInvoked = true; + return Task.FromResult(result); + } + + public Option ExecuteScalar(CommandEventData eventData, DbCommand command, Option result) + { + HasInterceptorBeenInvoked = true; + return result; + } + + public Task> ExecuteScalarAsync(CommandEventData eventData, DbCommand command, Option result, CancellationToken cancellationToken) + { + HasInterceptorBeenInvoked = true; + return Task.FromResult(result); + } + #endregion + } +} diff --git a/Tests/LinqToDB.EntityFrameworkCore.PomeloMySql.Tests/ForMappingTests.cs b/Tests/LinqToDB.EntityFrameworkCore.PomeloMySql.Tests/ForMappingTests.cs index 97a67e8..46b7281 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.PomeloMySql.Tests/ForMappingTests.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.PomeloMySql.Tests/ForMappingTests.cs @@ -1,4 +1,5 @@ -using LinqToDB.EntityFrameworkCore.BaseTests; +using System; +using LinqToDB.EntityFrameworkCore.BaseTests; using LinqToDB.EntityFrameworkCore.BaseTests.Models.ForMapping; using LinqToDB.EntityFrameworkCore.PomeloMySql.Tests.Models.ForMapping; using Microsoft.EntityFrameworkCore; @@ -9,12 +10,17 @@ public class ForMappingTests : ForMappingTestsBase { private bool _isDbCreated; - public override ForMappingContextBase CreateContext() + public override ForMappingContextBase CreateContext(Func? optionsSetter = null) { var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseMySql("Server=DBHost;Port=3306;Database=TestData;Uid=TestUser;Pwd=TestPassword;charset=utf8;"); + //var connectionString = "Server=DBHost;Port=3306;Database=TestData;Uid=TestUser;Pwd=TestPassword;charset=utf8;"; + var connectionString = "Server=localhost;Port=3316;Database=TestData;Uid=root;Pwd=root;charset=utf8;"; + optionsBuilder.UseMySql(connectionString); optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory); + if (optionsSetter! != null) + optionsBuilder.UseLinqToDB(builder => builder.AddCustomOptions(optionsSetter)); + var options = optionsBuilder.Options; var ctx = new ForMappingContext(options); @@ -28,6 +34,5 @@ public override ForMappingContextBase CreateContext() return ctx; } - } } diff --git a/Tests/LinqToDB.EntityFrameworkCore.PomeloMySql.Tests/PomeloMySqlTests.cs b/Tests/LinqToDB.EntityFrameworkCore.PomeloMySql.Tests/PomeloMySqlTests.cs index d354d61..c0a6ccf 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.PomeloMySql.Tests/PomeloMySqlTests.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.PomeloMySql.Tests/PomeloMySqlTests.cs @@ -4,8 +4,6 @@ using LinqToDB.EntityFrameworkCore.SqlServer.Tests.Models.Northwind; using Microsoft.EntityFrameworkCore; using NUnit.Framework; -using Pomelo.EntityFrameworkCore.MySql.Infrastructure; -using Pomelo.EntityFrameworkCore.MySql.Storage.Internal; namespace LinqToDB.EntityFrameworkCore.PomeloMySql.Tests { @@ -24,9 +22,9 @@ public PomeloMySqlTests() var optionsBuilder = new DbContextOptionsBuilder(); //new SqlServerDbContextOptionsBuilder(optionsBuilder); - optionsBuilder.UseMySql( - "Server=DBHost;Port=3306;Database=TestData;Uid=TestUser;Pwd=TestPassword;charset=utf8;", - builder => builder.ServerVersion(new ServerVersion(null).Version, ServerType.MySql)); + //var connectionString = "Server=DBHost;Port=3306;Database=TestData;Uid=TestUser;Pwd=TestPassword;charset=utf8;"; + var connectionString = "Server=localhost;Port=3316;Database=TestData;Uid=root;Pwd=root;charset=utf8;"; + optionsBuilder.UseMySql(connectionString); optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory); @@ -41,7 +39,6 @@ private NorthwindContext CreateMySqlSqlExntitiesContext() return ctx; } - [Test] public void SimpleProviderTest() { @@ -50,7 +47,5 @@ public void SimpleProviderTest() var items = db.Customers.Where(e => e.Address != null).ToLinqToDB().ToArray(); } } - - } } diff --git a/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/ForMappingTests.cs b/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/ForMappingTests.cs index 948843b..995ac90 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/ForMappingTests.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/ForMappingTests.cs @@ -1,4 +1,5 @@ -using LinqToDB.EntityFrameworkCore.BaseTests; +using System; +using LinqToDB.EntityFrameworkCore.BaseTests; using LinqToDB.EntityFrameworkCore.BaseTests.Models.ForMapping; using LinqToDB.EntityFrameworkCore.PostgreSQL.Tests.Models.ForMapping; using Microsoft.EntityFrameworkCore; @@ -11,12 +12,16 @@ public class ForMappingTests : ForMappingTestsBase { private bool _isDbCreated; - public override ForMappingContextBase CreateContext() + public override ForMappingContextBase CreateContext(Func? optionsSetter = null) { var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseNpgsql("Server=DBHost;Port=5432;Database=ForMapping;User Id=postgres;Password=TestPassword;Pooling=true;MinPoolSize=10;MaxPoolSize=100;"); + //optionsBuilder.UseNpgsql("Server=DBHost;Port=5432;Database=ForMapping;User Id=postgres;Password=TestPassword;Pooling=true;MinPoolSize=10;MaxPoolSize=100;"); + optionsBuilder.UseNpgsql("Server=localhost;Port=5415;Database=ForMapping;User Id=postgres;Password=Password12!;Pooling=true;MinPoolSize=10;MaxPoolSize=100;"); optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory); + if (optionsSetter! != null) + optionsBuilder.UseLinqToDB(builder => builder.AddCustomOptions(optionsSetter)); + var options = optionsBuilder.Options; var ctx = new ForMappingContext(options); diff --git a/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/NpgSqlTests.cs b/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/NpgSqlTests.cs index 67fe4ff..3dc4d1d 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/NpgSqlTests.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/NpgSqlTests.cs @@ -25,7 +25,8 @@ public NpgSqlTests() var optionsBuilder = new DbContextOptionsBuilder(); //new SqlServerDbContextOptionsBuilder(optionsBuilder); - optionsBuilder.UseNpgsql("Server=DBHost;Port=5432;Database=TestData;User Id=postgres;Password=TestPassword;Pooling=true;MinPoolSize=10;MaxPoolSize=100;"); + //optionsBuilder.UseNpgsql("Server=DBHost;Port=5432;Database=TestData;User Id=postgres;Password=TestPassword;Pooling=true;MinPoolSize=10;MaxPoolSize=100;"); + optionsBuilder.UseNpgsql("Server=localhost;Port=5415;Database=TestData;User Id=postgres;Password=Password12!;Pooling=true;MinPoolSize=10;MaxPoolSize=100;"); optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory); _options = optionsBuilder.Options; @@ -106,7 +107,7 @@ public void TestConcurrencyToken() public void TestUnnest() { using var db = CreateNpgSqlEntitiesContext(); - using var dc = db.CreateLinqToDbConnection(); + using var dc = db.CreateLinqToDBConnection(); var guids = new Guid[] { Guid.Parse("271425b1-ebe8-400d-b71d-a6e47a460ae3"), Guid.Parse("b75de94e-6d7b-4c70-bfa1-f8639a6a5b35") }; diff --git a/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/SampleTests/IdTests.cs b/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/SampleTests/IdTests.cs index 71015c4..86d1cbb 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/SampleTests/IdTests.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/SampleTests/IdTests.cs @@ -20,15 +20,16 @@ public IdTests() .ReplaceService() .UseLoggerFactory(TestUtils.LoggerFactory) .EnableSensitiveDataLogging() - .UseNpgsql("Server=DBHost;Port=5432;Database=IdTests;User Id=postgres;Password=TestPassword;Pooling=true;MinPoolSize=10;MaxPoolSize=100;") + //.UseNpgsql("Server=DBHost;Port=5432;Database=IdTests;User Id=postgres;Password=TestPassword;Pooling=true;MinPoolSize=10;MaxPoolSize=100;") + .UseNpgsql("Server=localhost;Port=5415;Database=IdTests;User Id=postgres;Password=Password12!;Pooling=true;MinPoolSize=10;MaxPoolSize=100;") .Options); _efContext.Database.EnsureDeleted(); _efContext.Database.EnsureCreated(); } - IDataContext CreateLinqToDbContext(TestContext testContext) + IDataContext CreateLinqToDBContext(TestContext testContext) { - var result = testContext.CreateLinqToDbContext(); + var result = testContext.CreateLinqToDBContext(); result.GetTraceSwitch().Level = TraceLevel.Verbose; return result; } @@ -39,7 +40,7 @@ IDataContext CreateLinqToDbContext(TestContext testContext) [Ignore("Incomplete.")] public void TestInsertWithoutTracker([Values("test insert")] string name) => _efContext - .Arrange(c => CreateLinqToDbContext(c)) + .Arrange(CreateLinqToDBContext) .Act(c => c.Insert(new Entity { Name = name })) .Assert(id => _efContext.Entitites.Single(e => e.Id == id).Name.Should().Be(name)); @@ -63,29 +64,29 @@ public void TestInsertEfCore([Values("test insert ef")] string name) [Ignore("Incomplete.")] public void TestIncludeDetails([Values] bool l2db, [Values] bool tracking) => _efContext - .Arrange(c => InsertDefaults(CreateLinqToDbContext(c))) + .Arrange(c => InsertDefaults(CreateLinqToDBContext(c))) .Act(c => c .Entitites .Where(e => e.Name == "Alpha") .Include(e => e.Details) .ThenInclude(d => d.Details) .Include(e => e.Children) - .AsLinqToDb(l2db) + .AsLinqToDB(l2db) .AsTracking(tracking) .ToArray()) - .Assert(e => e.First().Details.First().Details.Count().Should().Be(2)); + .Assert(e => e?.First().Details.First().Details.Count().Should().Be(2)); [Test] public void TestManyToManyIncludeTrackerPoison([Values] bool l2db) => _efContext - .Arrange(c => InsertDefaults(CreateLinqToDbContext(c))) + .Arrange(c => InsertDefaults(CreateLinqToDBContext(c))) .Act(c => { var q = c.Entitites .Include(e => e.Items) .ThenInclude(x => x.Item); - var f = q.AsLinqToDb(l2db).AsTracking().ToArray(); - var s = q.AsLinqToDb(!l2db).AsTracking().ToArray(); + var f = q.AsLinqToDB(l2db).AsTracking().ToArray(); + var s = q.AsLinqToDB(!l2db).AsTracking().ToArray(); return (First: f, Second: s); }) .Assert(r => r.First[0].Items.Count().Should().Be(r.Second[0].Items.Count())); @@ -95,40 +96,40 @@ public void TestManyToManyIncludeTrackerPoison([Values] bool l2db) [Ignore("Incomplete.")] public void TestManyToManyInclude([Values] bool l2db, [Values] bool tracking) => _efContext - .Arrange(c => InsertDefaults(CreateLinqToDbContext(c))) + .Arrange(c => InsertDefaults(CreateLinqToDBContext(c))) .Act(c => c.Entitites .Include(e => e.Items) .ThenInclude(x => x.Item) - .AsLinqToDb(l2db) + .AsLinqToDB(l2db) .AsTracking(tracking) .ToArray()) - .Assert(m => m[0].Items.First().Item.Should().BeSameAs(m[1].Items.First().Item)); + .Assert(m => m?[0].Items.First().Item.Should().BeSameAs(m[1].Items.First().Item)); [Test] [Ignore("Incomplete.")] public void TestMasterInclude([Values] bool l2db, [Values] bool tracking) => _efContext - .Arrange(c => InsertDefaults(CreateLinqToDbContext(c))) + .Arrange(c => InsertDefaults(CreateLinqToDBContext(c))) .Act(c => c .Details .Include(d => d.Master) - .AsLinqToDb(l2db) + .AsLinqToDB(l2db) .AsTracking(tracking) .ToArray()) - .Assert(m => m[0].Master.Should().BeSameAs(m[1].Master)); + .Assert(m => m?[0].Master.Should().BeSameAs(m[1].Master)); [Test] [Ignore("Incomplete.")] public void TestMasterInclude2([Values] bool l2db, [Values] bool tracking) => _efContext - .Arrange(c => InsertDefaults(CreateLinqToDbContext(c))) + .Arrange(c => InsertDefaults(CreateLinqToDBContext(c))) .Act(c => c .Details .Include(d => d.Master) .AsTracking(tracking) - .AsLinqToDb(l2db) + .AsLinqToDB(l2db) .ToArray()) - .Assert(m => m[0].Master.Should().BeSameAs(m[1].Master)); + .Assert(m => m?[0].Master.Should().BeSameAs(m[1].Master)); void InsertDefaults(IDataContext dataContext) { diff --git a/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/SampleTests/QueryableExtensions.cs b/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/SampleTests/QueryableExtensions.cs index 3dfccf2..206f678 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/SampleTests/QueryableExtensions.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/SampleTests/QueryableExtensions.cs @@ -5,7 +5,7 @@ namespace LinqToDB.EntityFrameworkCore.PostgreSQL.Tests.SampleTests { public static class QueryableExtensions { - public static IQueryable AsLinqToDb(this IQueryable queryable, bool l2db) + public static IQueryable AsLinqToDB(this IQueryable queryable, bool l2db) => l2db ? queryable.ToLinqToDB() : queryable; public static IQueryable AsTracking(this IQueryable queryable, bool tracking) diff --git a/Tests/LinqToDB.EntityFrameworkCore.SQLite.Tests/ForMappingTests.cs b/Tests/LinqToDB.EntityFrameworkCore.SQLite.Tests/ForMappingTests.cs index c721026..e209dff 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.SQLite.Tests/ForMappingTests.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.SQLite.Tests/ForMappingTests.cs @@ -1,4 +1,5 @@ -using LinqToDB.EntityFrameworkCore.BaseTests; +using System; +using LinqToDB.EntityFrameworkCore.BaseTests; using LinqToDB.EntityFrameworkCore.BaseTests.Models.ForMapping; using LinqToDB.EntityFrameworkCore.SQLite.Tests.Models.ForMapping; using Microsoft.EntityFrameworkCore; @@ -9,12 +10,15 @@ namespace LinqToDB.EntityFrameworkCore.SQLite.Tests [TestFixture] public class ForMappingTests : ForMappingTestsBase { - public override ForMappingContextBase CreateContext() + public override ForMappingContextBase CreateContext(Func? optionsSetter = null) { var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite("DataSource=:memory:"); optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory); + if (optionsSetter! != null) + optionsBuilder.UseLinqToDB(builder => builder.AddCustomOptions(optionsSetter)); + var options = optionsBuilder.Options; var ctx = new ForMappingContext(options); diff --git a/Tests/LinqToDB.EntityFrameworkCore.SQLite.Tests/InterceptorTests.cs b/Tests/LinqToDB.EntityFrameworkCore.SQLite.Tests/InterceptorTests.cs index 5f51994..dcec3a3 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.SQLite.Tests/InterceptorTests.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.SQLite.Tests/InterceptorTests.cs @@ -17,19 +17,19 @@ public class InterceptorTests private const string SQLITE_CONNECTION_STRING = "DataSource=NorthwindInMemory;Mode=Memory;Cache=Shared"; private readonly DbContextOptions _northwindOptions; private DbConnection? _dbConnection; - static TestCommandInterceptor testCommandInterceptor; - static TestDataContextInterceptor testDataContextInterceptor; - static TestConnectionInterceptor testConnectionInterceptor; - static TestEntityServiceInterceptor testEntityServiceInterceptor; - static TestEfCoreAndLinq2DbComboInterceptor testEfCoreAndLinq2DbInterceptor; + static TestCommandInterceptor _testCommandInterceptor; + static TestDataContextInterceptor _testDataContextInterceptor; + static TestConnectionInterceptor _testConnectionInterceptor; + static TestEntityServiceInterceptor _testEntityServiceInterceptor; + static TestEfCoreAndLinqToDBComboInterceptor _testEfCoreAndLinqToDBInterceptor; static InterceptorTests() { - testCommandInterceptor = new TestCommandInterceptor(); - testDataContextInterceptor = new TestDataContextInterceptor(); - testConnectionInterceptor = new TestConnectionInterceptor(); - testEntityServiceInterceptor = new TestEntityServiceInterceptor(); - testEfCoreAndLinq2DbInterceptor = new TestEfCoreAndLinq2DbComboInterceptor(); + _testCommandInterceptor = new TestCommandInterceptor(); + _testDataContextInterceptor = new TestDataContextInterceptor(); + _testConnectionInterceptor = new TestConnectionInterceptor(); + _testEntityServiceInterceptor = new TestEntityServiceInterceptor(); + _testEfCoreAndLinqToDBInterceptor = new TestEfCoreAndLinqToDBComboInterceptor(); LinqToDBForEFTools.Initialize(); DataConnection.TurnTraceSwitchOn(); } @@ -38,14 +38,15 @@ static DbContextOptions CreateNorthwindOptions() { var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite(SQLITE_CONNECTION_STRING); - optionsBuilder.UseLinqToDb(builder => + optionsBuilder.UseLinqToDB(builder => { - builder.AddInterceptor(testCommandInterceptor); - builder.AddInterceptor(testDataContextInterceptor); - builder.AddInterceptor(testConnectionInterceptor); - builder.AddInterceptor(testEntityServiceInterceptor); - builder.AddInterceptor(testEfCoreAndLinq2DbInterceptor); - builder.AddInterceptor(testCommandInterceptor); //for checking the aggregated interceptors + builder + .AddInterceptor(_testCommandInterceptor) + .AddInterceptor(_testDataContextInterceptor) + .AddInterceptor(_testConnectionInterceptor) + .AddInterceptor(_testEntityServiceInterceptor) + .AddInterceptor(_testEfCoreAndLinqToDBInterceptor) + .AddInterceptor(_testCommandInterceptor); //for checking the aggregated interceptors }); optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory); @@ -74,10 +75,10 @@ public void Setup() { NorthwindData.Seed(ctx); } - var ctxInterceptors = ctx.GetLinq2DbInterceptors(); - if (ctxInterceptors != null) + var options = ctx.GetLinqToDBOptions(); + if (options?.DataContextOptions.Interceptors != null) { - foreach (var interceptor in ctxInterceptors) + foreach (var interceptor in options.DataContextOptions.Interceptors) { ((TestInterceptor)interceptor).ResetInvocations(); } @@ -102,13 +103,13 @@ orderby pd.ProductId select pd; var items = query.Take(2).ToLinqToDB().ToArray(); } - Assert.IsTrue(testCommandInterceptor.HasInterceptorBeenInvoked); - Assert.IsTrue(testConnectionInterceptor.HasInterceptorBeenInvoked); - Assert.IsTrue(testEntityServiceInterceptor.HasInterceptorBeenInvoked); + Assert.IsTrue(_testCommandInterceptor.HasInterceptorBeenInvoked); + Assert.IsTrue(_testConnectionInterceptor.HasInterceptorBeenInvoked); + Assert.IsTrue(_testEntityServiceInterceptor.HasInterceptorBeenInvoked); //the following check is false because linq2db context is never closed together //with the EF core context - Assert.IsFalse(testDataContextInterceptor.HasInterceptorBeenInvoked); + Assert.IsFalse(_testDataContextInterceptor.HasInterceptorBeenInvoked); } [Test] @@ -116,19 +117,19 @@ public void TestExplicitDataContextInterceptors() { using (var ctx = CreateContext()) { - using var linq2DbContext = ctx.CreateLinqToDbContext(); + using var linqToDBContext = ctx.CreateLinqToDBContext(); var query = from pd in ctx.Products where pd.ProductId > 0 orderby pd.ProductId select pd; - var items = query.Take(2).ToLinqToDB(linq2DbContext).ToArray(); - var items2 = query.Take(2).ToLinqToDB(linq2DbContext).ToArray(); + var items = query.Take(2).ToLinqToDB(linqToDBContext).ToArray(); + var items2 = query.Take(2).ToLinqToDB(linqToDBContext).ToArray(); } - Assert.IsTrue(testCommandInterceptor.HasInterceptorBeenInvoked); - Assert.IsTrue(testDataContextInterceptor.HasInterceptorBeenInvoked); - Assert.IsTrue(testConnectionInterceptor.HasInterceptorBeenInvoked); - Assert.IsTrue(testEntityServiceInterceptor.HasInterceptorBeenInvoked); + Assert.IsTrue(_testCommandInterceptor.HasInterceptorBeenInvoked); + Assert.IsTrue(_testDataContextInterceptor.HasInterceptorBeenInvoked); + Assert.IsTrue(_testConnectionInterceptor.HasInterceptorBeenInvoked); + Assert.IsTrue(_testEntityServiceInterceptor.HasInterceptorBeenInvoked); } } } diff --git a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ForMappingTests.cs b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ForMappingTests.cs index 7badcbd..1d8628e 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ForMappingTests.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ForMappingTests.cs @@ -1,5 +1,7 @@ -using System.Linq; +using System; +using System.Linq; using FluentAssertions; +using LinqToDB.DataProvider.SqlServer; using LinqToDB.EntityFrameworkCore.BaseTests; using LinqToDB.EntityFrameworkCore.BaseTests.Models.ForMapping; using LinqToDB.EntityFrameworkCore.SqlServer.Tests.Models.ForMapping; @@ -13,12 +15,15 @@ public class ForMappingTests : ForMappingTestsBase { private bool _isDbCreated; - public override ForMappingContextBase CreateContext() + public override ForMappingContextBase CreateContext(Func? optionsSetter = null) { var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSqlServer("Server=.;Database=ForMapping;Integrated Security=SSPI"); + optionsBuilder.UseSqlServer(Settings.ForMappingConnectionString); optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory); + if (optionsSetter! != null) + optionsBuilder.UseLinqToDB(builder => builder.AddCustomOptions(optionsSetter)); + var options = optionsBuilder.Options; var ctx = new ForMappingContext(options); @@ -38,7 +43,7 @@ public void TestStringMappings() { using (var db = CreateContext()) { - var ms = LinqToDBForEFTools.GetMappingSchema(db.Model, db); + var ms = LinqToDBForEFTools.GetMappingSchema(db.Model, db, null); var ed = ms.GetEntityDescriptor(typeof(StringTypes)); ed.Columns.First(c => c.MemberName == nameof(StringTypes.AnsiString)).DataType.Should() @@ -47,7 +52,14 @@ public void TestStringMappings() ed.Columns.First(c => c.MemberName == nameof(StringTypes.UnicodeString)).DataType.Should() .Be(DataType.NVarChar); } + } + [Test] + public void TestDialectUse() + { + using var db = CreateContext(o => o.UseSqlServer("TODO:remove after fix from linq2db (not used)", SqlServerVersion.v2005)); + using var dc = db.CreateLinqToDBConnectionDetached(); + Assert.True(dc.MappingSchema.DisplayID.Contains("2005")); } } } diff --git a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/IssueTests.cs b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/IssueTests.cs index 097a9f9..c650d2c 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/IssueTests.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/IssueTests.cs @@ -10,7 +10,7 @@ namespace LinqToDB.EntityFrameworkCore.SqlServer.Tests [TestFixture] public class IssueTests : TestsBase { - private DbContextOptions? _options; + private DbContextOptions _options = default!; private bool _created; public IssueTests() @@ -22,7 +22,7 @@ private void InitOptions() { var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSqlServer("Server=.;Database=IssuesEFCore;Integrated Security=SSPI"); + optionsBuilder.UseSqlServer(Settings.IssuesConnectionString); optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory); _options = optionsBuilder.Options; @@ -30,7 +30,7 @@ private void InitOptions() private IssueContext CreateContext() { - var ctx = new IssueContext(_options!); + var ctx = new IssueContext(_options); if (!_created) { @@ -72,7 +72,7 @@ from p in ctx.Patents.Include(p => p.Assessment) var resultEF = query.ToArray(); - using var db = ctx.CreateLinqToDbConnection(); + using var db = ctx.CreateLinqToDBConnection(); _ = query.ToLinqToDB(db).ToArray(); diff --git a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/JsonConverTests.cs b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/JsonConverTests.cs index 7aa7365..19c1532 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/JsonConverTests.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/JsonConverTests.cs @@ -7,7 +7,6 @@ namespace LinqToDB.EntityFrameworkCore.SqlServer.Tests { - [TestFixture] public class JsonConverTests : TestsBase { @@ -79,7 +78,7 @@ public JsonConverTests() var optionsBuilder = new DbContextOptionsBuilder(); //new SqlServerDbContextOptionsBuilder(optionsBuilder); - optionsBuilder.UseSqlServer("Server=.;Database=JsonConvertContext;Integrated Security=SSPI"); + optionsBuilder.UseSqlServer(Settings.JsonConvertConnectionString); optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory); _options = optionsBuilder.Options; @@ -113,18 +112,21 @@ public void TestJsonConvert() var queryable = ctx.EventScheduleItems .Where(p => p.Id < 10).ToLinqToDB(); - var item = queryable + var items = queryable .Select(p => new { p.Id, p.NameLocalized, p.CrashEnum, - p.GuidColumn - }).FirstOrDefault(); - - Assert.That(item.NameLocalized.English, Is.EqualTo("English")); - Assert.That(item.NameLocalized.German, Is.EqualTo("German")); - Assert.That(item.NameLocalized.Slovak, Is.EqualTo("Slovak")); + p.GuidColumn, + }); + + var item = items.FirstOrDefault(); + + Assert.IsNotNull(item); + Assert.That(item!.NameLocalized.English, Is.EqualTo("English")); + Assert.That(item.NameLocalized.German, Is.EqualTo("German")); + Assert.That(item.NameLocalized.Slovak, Is.EqualTo("Slovak")); //TODO: make it work // var concrete = queryable.Select(p => new @@ -135,7 +137,6 @@ public void TestJsonConvert() // // Assert.That(concrete.English, Is.EqualTo("English")); } - } } } diff --git a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/QueryableExtensions.cs b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/QueryableExtensions.cs deleted file mode 100644 index 548bd84..0000000 --- a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/QueryableExtensions.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Linq.Expressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore.Query; - -namespace LinqToDB.EntityFrameworkCore.SqlServer.Tests -{ - public static class QueryableExtensions - { - public static async Task> FilterExistentAsync(this ICollection items, - IQueryable dbQuery, Expression> prop, CancellationToken cancellationToken = default) - { - var propGetter = prop.Compile(); - var ids = items.Select(propGetter).ToList(); - var parameter = prop.Parameters[0]; - - var predicate = Expression.Call(typeof(Enumerable), "Contains", new[] { typeof(TProp) }, Expression.Constant(ids), prop.Body); - var predicateLambda = Expression.Lambda(predicate, parameter); - - var filtered = Expression.Call(typeof(Queryable), "Where", new[] {typeof(T)}, dbQuery.Expression, - predicateLambda); - - var selectExpr = Expression.Call(typeof(Queryable), "Select", new[] {typeof(T), typeof(TProp)}, filtered, prop); - var selectQuery = dbQuery.Provider.CreateQuery(selectExpr); - - var existingIds = await selectQuery.ToListAsync(cancellationToken); - - return items.Where(i => !existingIds.Contains(propGetter(i))); - } - - public static IIncludableQueryable IncludeInline( - this IIncludableQueryable includable, Expression> inlineProp) - { - var path = new List(); - - var current = includable.Expression; - while (current.NodeType == ExpressionType.Call) - { - var mc = (MethodCallExpression) current; - if (mc.Method.Name == "Include" || mc.Method.Name == "ThenInclude") - path.Add(mc.Arguments[1]); - else - break; - } - - return includable; - } - - public static Expression> MakePropertiesPredicate(Expression> pattern, TValue searchValue, bool isOr) - { - var parameter = Expression.Parameter(typeof(T), "e"); - var searchExpr = Expression.Constant(searchValue); - - var predicateBody = typeof(T).GetProperties() - .Where(p => p.PropertyType == typeof(TValue)) - .Select(p => - ExpressionReplacer.GetBody(pattern, Expression.MakeMemberAccess( - parameter, p), searchExpr)) - .Aggregate(isOr ? Expression.OrElse : Expression.AndAlso); - - return Expression.Lambda>(predicateBody, parameter); - } - - public static IQueryable FilterByProperties(this IQueryable query, TValue searchValue, - Expression> pattern, bool isOr) - { - return query.Where(MakePropertiesPredicate(pattern, searchValue, isOr)); - } - - sealed class ExpressionReplacer : ExpressionVisitor - { - readonly IDictionary _replaceMap; - - public ExpressionReplacer(IDictionary replaceMap) - { - _replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap)); - } - - [return: NotNullIfNotNull("node")] - public override Expression? Visit(Expression? node) - { - if (node != null && _replaceMap.TryGetValue(node, out var replacement)) - return replacement; - return base.Visit(node); - } - - public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr) - { - return new ExpressionReplacer(new Dictionary { { toReplace, toExpr } }).Visit(expr); - } - - public static Expression Replace(Expression expr, IDictionary replaceMap) - { - return new ExpressionReplacer(replaceMap).Visit(expr); - } - - public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace) - { - if (lambda.Parameters.Count != toReplace.Length) - throw new InvalidOperationException(); - - return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count) - .ToDictionary(i => (Expression)lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body); - } - } - } -} diff --git a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/Settings.cs b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/Settings.cs new file mode 100644 index 0000000..4983495 --- /dev/null +++ b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/Settings.cs @@ -0,0 +1,11 @@ +namespace LinqToDB.EntityFrameworkCore.SqlServer.Tests +{ + public static class Settings + { + public static readonly string ForMappingConnectionString = "Server=.;Database=ForMapping;Integrated Security=SSPI;Encrypt=true;TrustServerCertificate=true"; + public static readonly string IssuesConnectionString = "Server=.;Database=IssuesEFCore;Integrated Security=SSPI;Encrypt=true;TrustServerCertificate=true"; + public static readonly string JsonConvertConnectionString = "Server=.;Database=JsonConvertContext;Integrated Security=SSPI;Encrypt=true;TrustServerCertificate=true"; + public static readonly string NorthwindConnectionString = "Server=.;Database=NorthwindEFCore;Integrated Security=SSPI;Encrypt=true;TrustServerCertificate=true"; + public static readonly string ConverterConnectionString = "Server=.;Database=ConverterTests;Integrated Security=SSPI;Encrypt=true;TrustServerCertificate=true"; + } +} diff --git a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ToolsTests.cs b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ToolsTests.cs index 31d403c..26b5f14 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ToolsTests.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ToolsTests.cs @@ -18,8 +18,8 @@ namespace LinqToDB.EntityFrameworkCore.SqlServer.Tests [TestFixture] public class ToolsTests : TestsBase { - private readonly DbContextOptions _northwindOptions; private DbContextOptions? _inheritanceOptions; + private readonly DbContextOptions _options; private readonly DbContextOptions _inmemoryOptions; static ToolsTests() @@ -28,17 +28,6 @@ static ToolsTests() DataConnection.TurnTraceSwitchOn(); } - static DbContextOptions CreateNorthwindOptions() - { - var optionsBuilder = new DbContextOptionsBuilder(); - //new SqlServerDbContextOptionsBuilder(optionsBuilder); - - optionsBuilder.UseSqlServer("Server=.;Database=NorthwindEFCore;Integrated Security=SSPI"); - optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory); - - return optionsBuilder.Options; - } - static DbContextOptions CreateInheritanceOptions() { var optionsBuilder = new DbContextOptionsBuilder(); @@ -53,10 +42,15 @@ static DbContextOptions CreateInheritanceOptions() public ToolsTests() { - _northwindOptions = CreateNorthwindOptions(); + var optionsBuilder = new DbContextOptionsBuilder(); + //new SqlServerDbContextOptionsBuilder(optionsBuilder); + optionsBuilder.UseSqlServer(Settings.NorthwindConnectionString); + optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory); - var optionsBuilder = new DbContextOptionsBuilder(); + _options = optionsBuilder.Options; + + optionsBuilder = new DbContextOptionsBuilder(); //new SqlServerDbContextOptionsBuilder(optionsBuilder); optionsBuilder.UseInMemoryDatabase("sample"); @@ -89,13 +83,13 @@ private void SetIdentityInsert(DbContext ctx, string tableName, bool isOn) private NorthwindContext CreateContext(bool enableFilter) { - var ctx = new NorthwindContext(_northwindOptions); + var ctx = new NorthwindContext(_options); ctx.IsSoftDeleteFilterEnabled = enableFilter; //ctx.Database.EnsureDeleted(); if (ctx.Database.EnsureCreated()) { NorthwindData.Seed(ctx); - } + } return ctx; } @@ -128,7 +122,7 @@ public class VwProductAndDescription public void TestToList([Values(true, false)] bool enableFilter) { using (var ctx = CreateContext(enableFilter)) - using (var db = ctx.CreateLinqToDbConnection()) + using (var db = ctx.CreateLinqToDBConnection()) { var items = db.GetTable() .LoadWith(d => d.OrderDetails) @@ -210,7 +204,7 @@ orderby pd.ProductId [Test] public void TestCreateFromOptions() { - using (var db = _northwindOptions.CreateLinqToDbConnection()) + using (var db = _options.CreateLinqToDBConnection()) { } } @@ -229,19 +223,19 @@ public void TestFunctions() // Date = Model.TestFunctions.GetDate(), // Len = Model.TestFunctions.Len(p.Name), DiffYear1 = EF.Functions.DateDiffYear(p.ShippedDate, p.OrderDate), - DiffYear2 = p.OrderDate == null ? (int?)null : EF.Functions.DateDiffYear(p.ShippedDate, p.OrderDate.Value), + DiffYear2 = p.OrderDate == null ? null : EF.Functions.DateDiffYear(p.ShippedDate, p.OrderDate.Value), DiffMonth1 = EF.Functions.DateDiffMonth(p.ShippedDate, p.OrderDate), - DiffMonth2 = p.OrderDate == null ? (int?)null : EF.Functions.DateDiffMonth(p.ShippedDate, p.OrderDate.Value), + DiffMonth2 = p.OrderDate == null ? null : EF.Functions.DateDiffMonth(p.ShippedDate, p.OrderDate.Value), DiffDay1 = EF.Functions.DateDiffDay(p.ShippedDate, p.OrderDate), - DiffDay2 = p.OrderDate == null ? (int?)null : EF.Functions.DateDiffDay(p.ShippedDate, p.OrderDate.Value), + DiffDay2 = p.OrderDate == null ? null : EF.Functions.DateDiffDay(p.ShippedDate, p.OrderDate.Value), DiffHour1 = EF.Functions.DateDiffHour(p.ShippedDate, p.OrderDate), - DiffHour2 = p.OrderDate == null ? (int?)null : EF.Functions.DateDiffHour(p.ShippedDate, p.OrderDate.Value), + DiffHour2 = p.OrderDate == null ? null : EF.Functions.DateDiffHour(p.ShippedDate, p.OrderDate.Value), DiffMinute1 = EF.Functions.DateDiffMinute(p.ShippedDate, p.OrderDate), - DiffMinute2 = p.OrderDate == null ? (int?)null : EF.Functions.DateDiffMinute(p.ShippedDate, p.OrderDate.Value), + DiffMinute2 = p.OrderDate == null ? null : EF.Functions.DateDiffMinute(p.ShippedDate, p.OrderDate.Value), DiffSecond1 = EF.Functions.DateDiffSecond(p.ShippedDate, p.OrderDate), - DiffSecond2 = p.OrderDate == null ? (int?)null : EF.Functions.DateDiffSecond(p.ShippedDate, p.OrderDate.Value), + DiffSecond2 = p.OrderDate == null ? null : EF.Functions.DateDiffSecond(p.ShippedDate, p.OrderDate.Value), DiffMillisecond1 = EF.Functions.DateDiffMillisecond(p.ShippedDate, p.ShippedDate!.Value.AddMilliseconds(100)), - DiffMillisecond2 = p.OrderDate == null ? (int?)null : EF.Functions.DateDiffMillisecond(p.ShippedDate, p.ShippedDate.Value.AddMilliseconds(100)), + DiffMillisecond2 = p.OrderDate == null ? null : EF.Functions.DateDiffMillisecond(p.ShippedDate, p.ShippedDate.Value.AddMilliseconds(100)), }; // var items1 = query.ToArray(); @@ -255,7 +249,7 @@ public async Task TestTransaction([Values(true, false)] bool enableFilter) using (var ctx = CreateContext(enableFilter)) { using (var transaction = ctx.Database.BeginTransaction()) - using (var db = ctx.CreateLinqToDbConnection()) + using (var db = ctx.CreateLinqToDBConnection()) { var test1 = await ctx.Products.Where(p => p.ProductName.StartsWith("U")).MaxAsync(p => p.QuantityPerUnit); @@ -276,7 +270,7 @@ public async Task TestTransaction([Values(true, false)] bool enableFilter) public void TestView([Values(true, false)] bool enableFilter) { using (var ctx = CreateContext(enableFilter)) - using (var db = ctx.CreateLinqToDbConnection()) + using (var db = ctx.CreateLinqToDBConnection()) { var query = ProductQuery(ctx) .ToLinqToDB(db) @@ -347,7 +341,7 @@ public void TestKey() { using (var ctx = CreateContext(false)) { - var ms = LinqToDBForEFTools.GetMappingSchema(ctx.Model, ctx); + var ms = LinqToDBForEFTools.GetMappingSchema(ctx.Model, ctx, null); var customerPk = ms.GetAttribute(typeof(Customer), MemberHelper.MemberOf(c => c.CustomerId)); @@ -355,7 +349,6 @@ public void TestKey() Assert.NotNull(customerPk); Assert.AreEqual(true, customerPk!.IsPrimaryKey); Assert.AreEqual(0, customerPk.PrimaryKeyOrder); - } } @@ -364,7 +357,7 @@ public void TestAssociations() { using (var ctx = CreateContext(false)) { - var ms = LinqToDBForEFTools.GetMappingSchema(ctx.Model, ctx); + var ms = LinqToDBForEFTools.GetMappingSchema(ctx.Model, ctx, null); var associationOrder = ms.GetAttribute(typeof(Customer), MemberHelper.MemberOf(c => c.Orders)); @@ -375,7 +368,6 @@ public void TestAssociations() } } - [Repeat(2)] [Test] public void TestGlobalQueryFilters([Values(true, false)] bool enableFilter) @@ -574,7 +566,7 @@ public async Task TestChangeTracker([Values(true, false)] bool enableFilter) orderDetail.UnitPrice = orderDetail.UnitPrice * 1.1m; ctx.ChangeTracker.DetectChanges(); - var changedEntry = ctx.ChangeTracker.Entries().Where(e => e.State == EntityState.Modified).Single(); + var changedEntry = ctx.ChangeTracker.Entries().Single(e => e.State == EntityState.Modified); ctx.SaveChanges(); } } @@ -597,7 +589,7 @@ public async Task TestChangeTrackerDisabled1([Values(true, false)] bool enableFi orderDetail.UnitPrice = orderDetail.UnitPrice * 1.1m; ctx.ChangeTracker.DetectChanges(); - var changedEntry = ctx.ChangeTracker.Entries().Where(e => e.State == EntityState.Modified).SingleOrDefault(); + var changedEntry = ctx.ChangeTracker.Entries().SingleOrDefault(e => e.State == EntityState.Modified); Assert.AreEqual(changedEntry, null); ctx.SaveChanges(); } @@ -623,7 +615,7 @@ public async Task TestChangeTrackerDisabled2([Values(true, false)] bool enableFi orderDetail.UnitPrice = orderDetail.UnitPrice * 1.1m; ctx.ChangeTracker.DetectChanges(); - var changedEntry = ctx.ChangeTracker.Entries().Where(e => e.State == EntityState.Modified).SingleOrDefault(); + var changedEntry = ctx.ChangeTracker.Entries().SingleOrDefault(e => e.State == EntityState.Modified); Assert.AreEqual(changedEntry, null); ctx.SaveChanges(); } @@ -641,7 +633,7 @@ public async Task TestChangeTrackerTemporaryTable([Values(true, false)] bool ena var query = ctx.Orders; - using var db = ctx.CreateLinqToDbConnection(); + using var db = ctx.CreateLinqToDBConnection(); using var temp = await db.CreateTempTableAsync(query, tableName: "#Orders"); @@ -814,7 +806,7 @@ public void TestCreateTempTable([Values(true, false)] bool enableFilter) { using (var ctx = CreateContext(enableFilter)) { - using var db = ctx.CreateLinqToDbContext(); + using var db = ctx.CreateLinqToDBContext(); using var temp = db.CreateTempTable(ctx.Employees, "#TestEmployees"); Assert.AreEqual(ctx.Employees.Count(), temp.Count()); @@ -898,7 +890,7 @@ public void TestTagWith([Values(true, false)] bool enableFilter) str.Should().Contain("Tagged query"); } } - + [Test] public void TestInheritanceBulkCopy([Values] BulkCopyType copyType) { diff --git a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ValueConversion/ConvertorTests.cs b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ValueConversion/ConvertorTests.cs index c776ff4..4fb95cf 100644 --- a/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ValueConversion/ConvertorTests.cs +++ b/Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ValueConversion/ConvertorTests.cs @@ -29,7 +29,7 @@ public ConvertorTests() optionsBuilder .ReplaceService() - .UseSqlServer("Server=.;Database=ConverterTests;Integrated Security=SSPI") + .UseSqlServer(Settings.ConverterConnectionString) .UseLoggerFactory(TestUtils.LoggerFactory);; _options = optionsBuilder.Options; @@ -40,7 +40,7 @@ public ConvertorTests() public void TestToList() { using (var ctx = new ConvertorContext(_options)) - using (var db = ctx.CreateLinqToDbConnection()) + using (var db = ctx.CreateLinqToDBConnection()) { ctx.Database.EnsureDeleted(); ctx.Database.EnsureCreated(); @@ -58,7 +58,7 @@ public void TestToList() var ef = ctx.Subdivisions.Where(s => s.Id == 1L).ToArray(); var ltdb = ctx.Subdivisions.ToLinqToDB().Where(s => s.Id == 1L).ToArray(); - var id = new Nullable>(0L.AsId()); + var id = new Id?(0L.AsId()); var ltdb2 = ctx.Subdivisions.ToLinqToDB().Where(s => s.Id == id).ToArray(); var ids = new[] {1L.AsId(), 2L.AsId(),}; diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 14974f7..fbbddf6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,12 +1,11 @@ variables: solution: 'linq2db.EFCore.sln' build_configuration: 'Release' - assemblyVersion: 2.9.0 - nugetVersion: 2.9.0 + assemblyVersion: 2.10.0 + nugetVersion: 2.10.0 artifact_nugets: 'nugets' # build on commits to important branches (master + release branches): -# v2: 'version2' + 'release.2' trigger: - version2 - release.2 @@ -33,8 +32,9 @@ stages: steps: + - task: UseDotNet@2 - displayName: 'Use .NET 7' + displayName: 'Install .NET 7' inputs: version: 7.x @@ -64,12 +64,7 @@ stages: displayName: Generate nuspec condition: and(succeeded(), or(eq(variables['Build.SourceBranchName'], 'release.2'), eq(variables['Build.SourceBranchName'], 'version2'))) - - task: NuGetToolInstaller@0 - inputs: - versionSpec: '5.x' - workingDirectory: $(Build.SourcesDirectory)/NuGet - displayName: Install nuget - condition: and(succeeded(), or(eq(variables['Build.SourceBranchName'], 'release.2'), eq(variables['Build.SourceBranchName'], 'version2'))) + - task: NuGetToolInstaller@1 - task: CmdLine@2 inputs: