System.Linq.Queryable.Except is not supported in Entity Framework Core - entity-framework

Please note this error message :
System.NotSupportedException: 'Could not parse expression
'value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable
This overload of the method 'System.Linq.Queryable.Except' is
currently not supported.'
Is supported in newer versions of ef core?
my code :
var tuple = SearchBrand(searchTerm);
var result = GetExceptionsBrand(tuple.Item1, categoryId);
return Json(new
{
iTotalDisplayRecords = tuple.Item2,
iDisplayedBrand = result
.Skip(page * 10)
.Take(10)
.ToList(),
});
public async Task<Tuple<IQueryable<BrandDto>, int, int>>SearchBrand(string searchTerm)
{
var result = _context.Brands
.Where(c => c.IsDeleted == displayIsDeleted)
.WhereDynamic(searchTerm)
return new Tuple<IQueryable<BrandDto>, int, int>(result,
filteredResultsCount, totalResultsCount);
}
public IQueryable<BrandDto> GetExceptionsBrand(IEnumerable<BrandDto> filteredBrand, int categoryId)
{
var query = _context.CategoriesBrands.Where(x => x.CategoryId == categoryId);
var selectedList = new List<BrandDto>();
foreach (var item in query)
{
var cb = new BrandDto()
{
BrandDto_BrandId = item.BrandId
};
selectedList.Add(cb);
}
IQueryable<BrandDto> ExcpetList = filteredBrand.AsQueryable().Except(selectedList, new ComparerBrand());
return ExcpetList;
}

Related

How do check for duplicate before AddRange?

This is my sample model,
Name
Age
I have a list of records for which I will use Entity Framework Core AddRange. This is my sample data, say list 1
John, 21
Mike, 18
Rick, 19
Alex, 20
I have another query which is a list of the existing records, say list 2.
John, 21
Alex, 20
I want to compare these 2 lists to get what exists in list 1 but not in list 2.
Mike, 18
Rick, 19
I tried using LINQ except but it can't be used for complex types. I also tried to use .Any like below but it returns nothing.
var result = list1.Where(x=> !list2.Any(y=>x.Name == y.Name));
I do not want to check and insert one record at one time. I want to insert by list of objects using AddRange.
Is there any Nuget package that can easily get this?
For not so big amount of records, you can use the following extension:
var nonExistent = await ctx.Some.GetNonExistentAsync(list2, x => x.Name);
Or with complex key:
var nonExistent = await ctx.Some.GetNonExistentAsync(list2,
x => new { x.Name, x.Other });
It will generate effective SQL to check which records do not exist.
And implementation:
public static class QueryableExtensions
{
public static async Task<List<T>> GetNonExistentAsync<T, TKey>(this IQueryable<T> query, IEnumerable<T> records,
Expression<Func<T, TKey>> keySelector, CancellationToken cancellationToken = default)
{
var recordsList = records.ToList();
var keySelectorCompiled = keySelector.Compile();
var predicate = BuildPredicate(recordsList, keySelector, keySelectorCompiled);
var existent = (await query
.Where(predicate)
.Select(keySelector)
.ToListAsync(cancellationToken))
.ToHashSet();
var result = recordsList.Where(r => !existent.Contains(keySelectorCompiled(r))).ToList();
return result;
}
private static Expression<Func<T, bool>> BuildPredicate<T, TKey>(IEnumerable<T> records, Expression<Func<T, TKey>> keySelector, Func<T, TKey> keySelectorCompiled)
{
var members = CollectMembers(keySelector.Body).ToList();
if (members.Count == 0)
throw new InvalidOperationException("No key found");
Expression? predicate = null;
if (members.Count == 1 && keySelector.Body.NodeType == ExpressionType.MemberAccess)
{
// we can use Contains
var recordsSequence = Expression.Constant(records.Select(keySelectorCompiled));
predicate = Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new[] { typeof(TKey) },
recordsSequence, keySelector.Body);
}
else
{
foreach (var record in records)
{
Expression? subPredicate = null;
var recordExpression = Expression.Constant(record);
foreach (var m in members)
{
var memberExpression =
ReplacingExpressionVisitor.Replace(keySelector.Parameters[0], recordExpression, m);
var equality = Expression.Equal(m, memberExpression);
subPredicate = subPredicate == null ? equality : Expression.AndAlso(subPredicate, equality);
}
if (subPredicate == null)
throw new InvalidOperationException(); // should never happen
predicate = predicate == null ? subPredicate : Expression.OrElse(predicate, subPredicate);
}
}
predicate ??= Expression.Constant(false);
return Expression.Lambda<Func<T, bool>>(predicate, keySelector.Parameters);
}
private static IEnumerable<Expression> CollectMembers(Expression expr)
{
switch (expr.NodeType)
{
case ExpressionType.New:
{
var ne = (NewExpression)expr;
foreach (var a in ne.Arguments)
foreach (var m in CollectMembers(a))
{
yield return m;
}
break;
}
case ExpressionType.MemberAccess:
yield return expr;
break;
default:
throw new InvalidOperationException();
}
}
}

An sql query returns empty when it should not?

I have this sql query which perform an foreing key validation, and validated whether both entries uphold a certain standard.
The query should return something (since the data I pass in should fail), as long there is data in the database.
But what I've noticed is that, when I in the same transaction insert data and then perform the validation in the same transaction the validation query returns nothing, when it in actuality should return numerous rows.
if I do the same step as above, meaning I already have committed data in the database, and insert the same data, my validation script returns the excepted rows?
is there any limitations in regards to using transcations?
in here I insert data
private IEnumerable<long> PersistEntityRegistrations(IEnumerable<EntityRegistration> payload, string entityName, NpgsqlTransaction transaction)
{
IEnumerable<string> customAttributeNames = GetEntitySpecificAttributes(entityName);
IEnumerable<string> allAttributeNames =
EntityRegistration.standardAttributeNames.Union(customAttributeNames);
var insertNewRegistrationSql =
#$"INSERT INTO ""{entityName}_registration"" ({string.Join(",", allAttributeNames)})
SELECT * FROM unnest({string.Join(",", allAttributeNames.Select(x => "#" + x))})
RETURNING row_id;";
NpgsqlCommand insertRegistrations = new NpgsqlCommand(insertNewRegistrationSql, transaction.Connection, transaction);
DateTime now = DateTime.UtcNow;
NpgsqlParameter entity_id = new NpgsqlParameter("entity_id", payload.Where(x => x.EntityId.HasValue).Select(x => x.EntityId ?? 0).ToArray());
insertRegistrations.Parameters.Add(entity_id);
NpgsqlParameter entryName = new NpgsqlParameter("entry_name", payload.Select(x => x.EntryName).ToArray());
insertRegistrations.Parameters.Add(entryName);
NpgsqlParameter registrationBy = new NpgsqlParameter("registration_by", payload.Select(x => x.RegistrationBy).ToArray());
insertRegistrations.Parameters.Add(registrationBy);
NpgsqlRange<DateTime>[] valid = payload.Select(x => x.Valid).ToArray();
insertRegistrations.Parameters.Add(new NpgsqlParameter("valid", valid));
NpgsqlRange<DateTime>[] registration = payload.Select(x => new NpgsqlRange<DateTime>(now, lowerBoundIsInclusive: true, DateTime.MaxValue, upperBoundIsInclusive: true)).ToArray();
insertRegistrations.Parameters.Add(new NpgsqlParameter("registration", registration));
Entity entity = schemaService.ReadSchema().Entities.SingleOrDefault(x => x.InternalName == entityName);
Relation relation = schemaService.ReadSchema().Relations.SingleOrDefault(x => x.InternalName == entityName);
ICollection<Attribute> entityAttributeDefinitions = entity != null ? entity.Attributes : relation.Attributes ;
foreach (Attribute attributeDefinition in entityAttributeDefinitions)
{
IEnumerable<object> attributeValues = payload.Where(x => x.Attributes.ContainsKey(attributeDefinition.InternalName))
.Select(x => x.Attributes[attributeDefinition.InternalName]);
switch (attributeDefinition.Type)
{
case DataType.Dropdown:
case DataType.WeekDay:
case DataType.Text:
var castedStringValues = attributeValues.Cast<string?>().ToArray();
insertRegistrations.Parameters.Add(new NpgsqlParameter(attributeDefinition.InternalName, castedStringValues));
break;
case DataType.Number:
var castedNumberValues = attributeValues.Cast<decimal>().ToArray();
insertRegistrations.Parameters.Add(new NpgsqlParameter(attributeDefinition.InternalName, castedNumberValues));
break;
case DataType.Lookup:
var castedLookupValues = attributeValues.Cast<decimal>().ToArray();
insertRegistrations.Parameters.Add(new NpgsqlParameter(attributeDefinition.InternalName, castedLookupValues));
break;
case DataType.DateTime:
var castedDateTimeValues = attributeValues.Cast<DateTime?>().ToArray();
insertRegistrations.Parameters.Add(new NpgsqlParameter(attributeDefinition.InternalName, castedDateTimeValues));
break;
case DataType.Boolean:
// Npgsql is currently not able to handle bool?[], hence this exact type cannot handle null values.
// This is currently support in v. 5 but since EFCore has a hard dependency on v.4
// which forces us to use bool[].
var castedBooleanValues = attributeValues.Cast<bool>().ToArray();
insertRegistrations.Parameters.Add(new NpgsqlParameter(attributeDefinition.InternalName, castedBooleanValues));
break;
}
}
insertRegistrations.Prepare();
insertRegistrations.CommandTimeout = 0;
var insertedRowIds = new List<long>();
using (NpgsqlDataReader dataReader = insertRegistrations.ExecuteReader())
{
while (dataReader.Read())
{
insertedRowIds.Add((long)dataReader[0]);
}
}
return insertedRowIds;
}
Here I do the validation
private IEnumerable<(string, long, NpgsqlRange<DateTime>, bool, NpgsqlRange<DateTime>, long, string, bool)> DetectLookupLifespanViolations(string entityName, IEnumerable<long> entity_ids,
NpgsqlTransaction transaction)
{
var sql = File.ReadAllText(Path.Combine("SQLQueries", "IntegrityCheck.sql"));
var integrityCheckCommand = new NpgsqlCommand(sql, transaction.Connection, transaction);
integrityCheckCommand.Parameters.Add(new NpgsqlParameter<long[]>("#entity_ids", entity_ids.ToArray()));
integrityCheckCommand.Parameters.Add(new NpgsqlParameter<string>("table_name", entityName));
var integrity_breached =
new List<(string, long, NpgsqlRange<DateTime>, bool, NpgsqlRange<DateTime>, long, string, bool)>();
var has_values = new NpgsqlCommand($#"select count(*) from {entityName}_registration", transaction.Connection, transaction);
var result = has_values.ExecuteScalar();
Console.WriteLine(result);
using NpgsqlDataReader dataReader = integrityCheckCommand.ExecuteReader();
while (dataReader.Read())
{
bool hasDateTimeIntegrity = (bool)(dataReader.GetValue(3) ?? true);
if (!hasDateTimeIntegrity)
{
integrity_breached.Add((dataReader.GetFieldValue<string>(0),
dataReader.GetFieldValue<long>(1),
dataReader.GetFieldValue<NpgsqlRange<DateTime>>(2),
dataReader.GetFieldValue<bool>(3),
dataReader.GetFieldValue<NpgsqlRange<DateTime>>(4),
dataReader.GetFieldValue<long>(5),
dataReader.GetFieldValue<string>(6),
dataReader.GetFieldValue<bool>(7)
));
}
}
return integrity_breached;
}
The both called within a method were
public bool InsertEntityRegistrations(string entityName, IEnumerable<EntityRegistration> payload)
{
using var connection = new NpgsqlConnection(configuration.GetConnectionString("PostgreSQL"));
connection.Open();
using var transaction = connection.BeginTransaction();
....
IEnumerable<long> insertedRowIds = PersistEntityRegistrations(payload, entityName, transaction);
....
var registrationsCausingIntegrityIssues = DetectLookupLifespanViolations(entityName, allEntityIds, transaction);
...
}
}

Override index include column postgresql and ef core

I'm using entity framework core for SQL Server and PostgreSQL.
And I need to override create index with include columns.
I use NpgsqlMigrationsAnnotationProvider and NpgsqlMigrationsSqlGenerator to do this.
My code is
NpgsqlMigrationsSqlGenerator
Generate function:
base.Generate(operation, model, builder, false);
System.Diagnostics.Debugger.Launch();
var includeIndexAnnotation = operation.FindAnnotation("IncludeIndex");
if (includeIndexAnnotation != null)
{
var includeColumns = includeIndexAnnotation.Value.ToString().Split(",");
var includeOperation = new StringBuilder();
builder.Append(" INCLUDE(");
foreach (var includeColumn in includeColumns)
{
if (includeOperation.Length > 0)
includeOperation.Append(",");
includeOperation.Append($"\"{includeColumn}\"");
}
includeOperation.Append(")");
builder.Append(includeOperation);
}
if (terminate)
{
builder.AppendLine(StatementTerminator);
EndStatement(builder);
}
And
NpgsqlMigrationsAnnotationProvider
For(IIndex index) function:
var baseAnnotations = base.For(index);
var customAnnotations = index.GetAnnotations().Where(a => a.Name == "IncludeIndex");
return baseAnnotations.Concat(customAnnotations);
Extensions:
public static IndexBuilder Include<TEntity>(this IndexBuilder indexBuilder, Expression<Func<TEntity, object>> indexExpression)
{
var includeStatement = new StringBuilder();
foreach (var column in indexExpression.GetPropertyAccessList())
{
if (includeStatement.Length > 0)
includeStatement.Append(",");
includeStatement.AppendFormat("{0}", column.Name);
}
indexBuilder.HasAnnotation("IncludeIndex", includeStatement.ToString());
return indexBuilder;
}
DesignTimeDbContextFactory:
builder.ReplaceService<IMigrationsAnnotationProvider, PostgreSql.DbMigrationsAnnotationProvider>();
builder.ReplaceService<IMigrationsSqlGenerator, PostgreSql.DbMigrationsSqlGenerator>();
DbContext:
entity.HasIndex(log => new { log.Id }).Include<Log>(log => new { log.Type, log.Source, log.Created, log.Message })
This code working fine with sql server but not working with postgresql.
Can't add Include command to CreateIndex query.
Thanks for your support.

How do you create Expression<Func<T, T>> given the property names of T to map?

Here's what the required function would look like:
public static Expression<Func<T, T>> GetExpression<T>(string propertyNames) where T : class
{
var properties = propertyNames.Split(
new char[] { ',' },
StringSplitOptions.RemoveEmptyEntries
).ToList();
//need help here
}
Currently I'm doing it like this:
_context.Questions.Select(q =>
new Question() {
QuestionId = q.QuestionId,
QuestionEnglish = q.QuestionEnglish
}
).ToList();
And I want to replace it with:
_context.Questions.Select(GetExpression<Question>("QuestionId, QuestionInEnglish")).ToList();
Any help would be greatly appreciated.
You can do it like this;
public static Func<T, T> GetExpression<T>(string propertyNames)
{
var xParameter = Expression.Parameter(typeof(T), "parameter");
var xNew = Expression.New(typeof(T));
var selectFields = propertyNames.Split(',').Select(parameter => parameter.Trim())
.Select(parameter => {
var prop = typeof(T).GetProperty(parameter);
if (prop == null) // The field doesn't exist
{
return null;
}
var xOriginal = Expression.Property(xParameter, prop);
return Expression.Bind(prop, xOriginal);
}
).Where(x => x != null);
var lambda = Expression.Lambda<Func<T, T>>(Expression.MemberInit(xNew, selectFields), xParameter);
return lambda.Compile();
}
Usage;
var list = new List<Question>{new Question{QuestionEnglish = "QuestionName",QuestionId = 1}};
var result = list.Select(GetExpression<Question>("QuestionId, QuestionEnglish"));

How To Find data by passing List of IMongoQuery

Below is my code where i am passing List showing no error during building solution but during run time its showing error.
An Array value cannot be written to the root level of a BSON document.
My Code Is :
public IQueryable<Folder> GetFolderByIdList(List<IMongoQuery> GraphIdList)
{
if (ApplicationDbContext.ServerIsDown) return null;
_FolderList.Clear();
if (!GraphIdList.Any())
{
return null;
}
var FolderData = db.Folder.Find(GraphIdList.ToBsonDocument()).ToList();
if (FolderData.Count() > 0)
{
foreach (Folder item in FolderData)
{
_FolderList.Add(item);
}
}
var result = _FolderList.AsQueryable();
return result;
}
and below is my code what i have pass in GraphIdList
var UserFilesData = planetDriveObj.GetFilesOfFolder(
Query.And(
Query<UserFiles>.EQ(u => u.CreatorUserID, userInfoId),
Query<UserFiles>.Matches(u => u.Title, fileTitle)));
foreach(var c in UserFilesData.ToList())
{
idList.Add(Query.And(
Query<Graph>.EQ(u => u.GraphID, c.GraphID),
Query<Graph>.EQ(u => u.isHidden, true)));
}
var GraphData = GraphRepObj.getGraphDataBYIdList(idList);