Get only a specified field in MongoDB with C# - mongodb

first time i'm using MongoDB.
I have read this example:
SELECT a,b FROM users WHERE age=33
db.users.find({age:33}, {a:1,b:1})
But I can't translate it into C#. Can anyone help me?

I have translated your query below using the new C# driver (2.2)
var mongoClient = new MongoClient(""mongodb://127.0.0.1:27017"");
var database = mongoClient.GetDatabase("databaseName");
IMongoCollection<Users> _collection = database.GetCollection<Users>("Users");
var condition = Builders<Users>.Filter.Eq(p => p.age, 33);
var fields = Builders<Users>.Projection.Include(p => p.a).Include(p => p.b);
var results= _collection.Find(condition).Project<Users>(fields).ToList().AsQueryable();

You can do it using SetFields method of MongoCursor class, below full example:
var server = MongoServer.Create(connectionString);
var db = _server.GetDatabase("dbName");
var users = db.GetCollection("users");
var cursor = users.FindAs<DocType>(Query.EQ("age", 33));
cursor.SetFields(Fields.Include("a", "b"));
var items = cursor.ToList();

you can use anonymous class
public class User
{
public int age;
public string a;
public string b;
}
var collection = db.GetCollection<User>("Users");
var results = collection.Find(Builders<User>.Filter.Eq(user => user.age, 33))
.Project(u => new { u.a, u.b }).ToList();

//create user class
//(not sure how your class looks like)
public class User
{
public int age;
public string a;
public string b;
}
//then you can use LINQ easily
var server = MongoServer.Create(connectionString);
var db = server.GetDatabase("dbName");
var usersCollection = db.GetCollection<User>("users");
var filteredCollection = usersCollection.AsQueryable().Where(x=> x.age < 33).Where(x=> x.a != null).Contains(x=> x.b != null);

Related

How to loop through dbcontext all dbset in Entity Framework Core to get count?

I have 20 Dbsets in my context which I want to get the row count for each dbset to make sure all dbset row count is 0. To get the count for one dbset, this is my code:
var person = context.Persons.Count();
Is there a way to loop through the context, get the count for each dbset dynamically?
There is solution. Usage is simple:
var tablesinfo = ctx.GetTablesInfo();
if (tablesinfo != null)
{
var withRecords = tablesinfo
.IgnoreQueryFilters()
.Where(ti => ti.RecordCount > 0)
.ToArray();
}
Extension returns IQueryable<TableInfo> and you can reuse this query later. Probably you will need to filter out Views, but I think you can handle that. Note that IgnoreQueryFilters can be important if you have Global Query Filters defined.
What extension do:
It scans Model for entity types registered for particular DbContext and generates big Concat of Count queries. Here we have to do that via grouping by constant value.
Schematically it will generate the following LINQ query:
var tablesinfo =
ctx.Set<Entity1>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity1", RecordCount = g.Count()})
.Concat(ctx.Set<Entity2>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity2", RecordCount = g.Count()}))
.Concat(ctx.Set<Entity3>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity3", RecordCount = g.Count()}))
...
Which wll be converted to the following SQL:
SELECT "Entity1" AS TableName, COUNT(*) AS RecordCount FROM Entity1
UNION ALL
SELECT "Entity2" AS TableName, COUNT(*) AS RecordCount FROM Entity2
UNION ALL
SELECT "Entity3" AS TableName, COUNT(*) AS RecordCount FROM Entity3
...
Implementation:
public static class QueryableExtensions
{
public class TableInfo
{
public string TableName { get; set; } = null!;
public int RecordCount { get; set; }
}
public static IQueryable<TableInfo> GetTablesInfo(this DbContext ctx)
{
Expression query = null;
IQueryProvider provider = null;
var ctxConst = Expression.Constant(ctx);
var groupingKey = Expression.Constant(1);
// gathering information for MemberInit creation
var newExpression = Expression.New(typeof(TableInfo).GetConstructor(Type.EmptyTypes));
var tableNameProperty = typeof(TableInfo).GetProperty(nameof(TableInfo.TableName));
var recordCountProperty = typeof(TableInfo).GetProperty(nameof(TableInfo.RecordCount));
foreach (var entityType in ctx.Model.GetEntityTypes())
{
var entityParam = Expression.Parameter(entityType.ClrType, "e");
var tableName = entityType.GetTableName();
// ctx.Set<entityType>()
var setQuery = Expression.Call(ctxConst, nameof(DbContext.Set), new[] {entityType.ClrType});
// here we initialize IQueryProvider, which is needed for creating final query
provider ??= ((IQueryable) Expression.Lambda(setQuery).Compile().DynamicInvoke()).Provider;
// grouping paraneter has generic type, we have to specify it
var groupingParameter = Expression.Parameter(typeof(IGrouping<,>).MakeGenericType(typeof(int), entityParam.Type), "g");
// g => new TableInfo { TableName = "tableName", RecordCount = g.Count() }
var selector = Expression.MemberInit(newExpression,
Expression.Bind(tableNameProperty, Expression.Constant(tableName)),
Expression.Bind(recordCountProperty,
Expression.Call(typeof(Enumerable), nameof(Enumerable.Count), new[] {entityParam.Type}, groupingParameter)));
// ctx.Set<entityType>.GroupBy(e => 1)
var groupByCall = Expression.Call(typeof(Queryable), nameof(Queryable.GroupBy), new[]
{
entityParam.Type,
typeof(int)
},
setQuery,
Expression.Lambda(groupingKey, entityParam)
);
// ctx.Set<entityType>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "tableName", RecordCount = g.Count()}))
groupByCall = Expression.Call(typeof(Queryable), nameof(Queryable.Select),
new[] {groupingParameter.Type, typeof(TableInfo)},
groupByCall,
Expression.Lambda(selector, groupingParameter));
// generate Concat if needed
if (query != null)
query = Expression.Call(typeof(Queryable), nameof(Queryable.Concat), new[] {typeof(TableInfo)}, query,
groupByCall);
else
query = groupByCall;
}
// unusual situation, but Model can have no registered entities
if (query == null)
return null;
return provider.CreateQuery<TableInfo>(query);
}
}

Using Dynamic LINQ with EF.Functions.Like

On the Dynamic LINQ website there's an example using the Like function.
I am unable to get it to work with ef core 3.1
[Test]
public void DynamicQuery()
{
using var context = new SamDBContext(Builder.Options);
var config = new ParsingConfig { ResolveTypesBySimpleName = true };
var lst = context.Contacts.Where(config, "DynamicFunctions.Like(FirstName, \"%Ann%\")".ToList();
lst.Should().HaveCountGreaterThan(1);
}
Example from the Dynamic LINQ website
var example1 = Cars.Where(c => EF.Functions.Like(c.Brand, "%t%"));
example1.Dump();
var config = new ParsingConfig { ResolveTypesBySimpleName = true };
var example2 = Cars.Where(config, "DynamicFunctions.Like(Brand, \"%t%\")");
example2.Dump();
Looks like my code. But I am getting the following error
System.Linq.Dynamic.Core.Exceptions.ParseException : No property or field 'DynamicFunctions' exists in type 'Contact'
you don't need the ResolveTypesBySimpleName, implement your wont type provider.
The piece below people to use PostgreSQL ILike with unnaccent
public class LinqCustomProvider : DefaultDynamicLinqCustomTypeProvider
{
public override HashSet<Type> GetCustomTypes()
{
var result = base.GetCustomTypes();
result.Add(typeof(NpgsqlFullTextSearchDbFunctionsExtensions));
result.Add(typeof(NpgsqlDbFunctionsExtensions));
result.Add(typeof(DbFunctionsExtensions));
result.Add(typeof(DbFunctions));
result.Add(typeof(EF));
return result;
}
}
// ....
var expressionString = $"EF.Functions.ILike(EF.Functions.Unaccent(People.Name), \"%{value}%\")";
var config = new ParsingConfig()
{
DateTimeIsParsedAsUTC = true,
CustomTypeProvider = new LinqCustomProvider()
};
return query.Where(config, expressionString);
Hope this helps people, took me some time to get this sorted.

Entity Framework - LINQ - Use Expressions in Select

I am using within my code some EF LINQ expressions to keep complex queries over my model in one place:
public static IQueryable<User> ToCheck(this IQueryable<User> queryable, int age, bool valueToCheck = true)
{
return queryable.Where(ToBeReviewed(age, valueToCheck));
}
public static Expression<Func<User, bool>> ToCheck(int age, bool valueToCheck = true)
{
return au => au.Status == UserStatus.Inactive
|| au.Status == UserStatus.Active &&
au.Age.HasValue && au.Age.Value > age;
}
I am then able to use them in queries:
var globalQuery = db.Users.ToCheck(value);
And also in selects:
var func = EntityExtensions.ToCheck(value);
var q = db.Department.Select(d => new
{
OrdersTotal = d.Orders.Sum(o => o.Price),
ToCheck = d.Users.AsQueryable().Count(func),
})
What I am trying to achieve is to actually use the same expression/function within a select, to evaluate it for each row.
var usersQuery = query.Select(au => new {
Id = au.Id,
Email = au.Email,
Status = au.Status.ToString(),
ToBeChecked = ???, // USE FUNCTION HERE
CreationTime = au.CreationTime,
LastLoginTime = au.LastLoginTime,
});
I am pretty that threre would be a way using plain EF capabilities or LINQKit, but can't find it.
Answering my own question :)
As pointed by #ivan-stoev, the use of Linqkit was the solution:
var globalQueryfilter = db.Users.AsExpandable.Where(au => au.Department == "hq");
var func = EntityExtensions.ToCheck(value);
var usersQuery = globalQueryfilter.Select(au => new
{
Id = au.Id,
Email = au.Email,
Status = au.Status.ToString(),
ToBeChecked = func.Invoke(au),
CreationTime = au.CreationTime,
LastLoginTime = au.LastLoginTime,
});
return appUsersQuery;
It's required to use the AsExpandable extension method from Linqkit along with Invoke with the function in the select method.
I want to add one more example:
Expression<Func<AddressObject, string, string>> selectExpr = (n, a) => n == null ? "[no address]" : n.OFFNAME + a;
var result = context.AddressObjects.AsExpandable().Select(addressObject => selectExpr.Invoke(addressObject, "1"));
Also, expression can be static in a helper.
p.s. please not forget to add "using LinqKit;" and use "AsExpandable".

GroupBy Clause: To be able to pass in a Column name as a string

In EF, Order By allows a column name to be used to eg OrderBy("Description")
I need to be able to do some thing similar with GroupBy
Other posts have solutions when the column type is known
var groupByExpressionGN2 = GetGroupByExpressionGuidNull<DebtWaiver>("PersonUID");
...
// in the query
.GroupBy(groupByExpression2)
// the Expression function
private static Expression<Func<TEntity,Guid?>> GetGroupByExpressionGuidNull<TEntity>(string property)
{
var item = Expression.Parameter(typeof(TEntity), "gb");
var itemProperty = Expression.PropertyOrField(item, property);
var lambda = Expression.Lambda<Func<TEntity, Guid?>>(itemProperty, item);
return lambda;
}
But my users may select 1 of any columns by which to group by
So how can I make the function above return an expression for group by
I have tried this:
public static Expression<Func<T, object>> GetMember<T>(string memberName)// where T : EntityObject
{
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
System.Reflection.PropertyInfo pi = typeof(T).GetProperty(memberName);
return (Expression<Func<T, object>>)Expression.Lambda<Func<T, object>>(Expression.Convert(Expression.Property(pe, pi), typeof(object)), pe);
}
but it produces : p =>Convert(p.PersonUID)
instead of:p =>p.PersonUID
Regards
GregJF
After a bit more testing (and a good night's sleep) I got the second method to work (Thanks to JA Rreyes )
My issue was that I was using this:
var groupByExpressionGN2 = GetGroupByExpressionGuidNull<DebtWaiver>("PersonUID");
...
// in the query
.GroupBy(groupByExpression2)
I should have being doing this:
var groupByExpression2 = GetMember<DebtWaiver>("PersonUID");
...
// in the query
.GroupBy(groupByExpression2.Compile())
You can use the second method in my original post( GetMember ), but I use this method('cos I like it!): Thanks: Taher Rahgooy
public static Expression<Func<T, object>> GetPropertySelector<T>(string propertyName)
{
var arg = Expression.Parameter(typeof(T), "gb");
var property = Expression.Property(arg, propertyName);
var conv = Expression.Convert(property, typeof(object));
var exp = Expression.Lambda<Func<T, object>>(conv, new ParameterExpression[] { arg });
return exp;
}
Regards
GregJF

Return rows from stored procedure

I have this code:
public IEnumerable<SomeClass> GetAvalibleThingies(DateTime startDate, DateTime endDate, int categoryID)
{
if (_entities.Connection.State == System.Data.ConnectionState.Closed)
_entities.Connection.Open();
using (EntityCommand c = new EntityCommand("SomeEntities.GetAvalibleThingies", (EntityConnection)this._entities.Connection))
{
c.CommandType = System.Data.CommandType.StoredProcedure;
EntityParameter paramstartDate = new EntityParameter("startDate", System.Data.DbType.DateTime);
paramstartDate.Direction = System.Data.ParameterDirection.Input;
paramstartDate.Value = startDate;
c.Parameters.Add(paramstartDate);
........
var x = c.ExecuteReader();
return x as IEnumerable<SomeClass>;
};
But I can't get it to return a list of SomeClass. What's needed to do here? I use the entity framework 3.5sp1 one
/M
EntityDataReader is a class similar to SqlDataReader and should be handled in the similar way.
Instead of the lines
var x = c.ExecuteReader();
return x as IEnumerable;
should be something like
List list = new List();
using(EntityDataReader reader = c.ExecuteReader()) {
while(reader.Read()) {
SomeClass item = new SomeClass() {
};
list.Add(item);
}
}
return list;