How to improve this code of query in mongodb - mongodb

This is what my employee use
var client = new MongoClient(System.Configuration.ConfigurationManager.AppSettings["MongoDbServer"]);
var server = client.GetServer();
MongoDatabase db = server.GetDatabase("Edbert");
var collection = db.GetCollection("testInstagram");
var query = Query.And(Query.Not(Query.Or(Query.Size("PossibleInstagramIDs", 1), Query.Size("PossibleInstagramIDs", 0))),Query.EQ("InstagramID",BsonNull.Value));
I think this part is ugly:
var query = Query.And(Query.Not(Query.Or(Query.Size("PossibleInstagramIDs", 1), Query.Size("PossibleInstagramIDs", 0))),Query.EQ("InstagramID",BsonNull.Value));
What he tries to do is to set the query to return true if size of PossibleInstagramIDs are bigger than 1.
What should he have done?

I would make the collection strongly typed by creating a TestInstagram class and then use LINQ.
Your TestInstagram class will look something like this:
public class TestInstagram
{
public ObjectId Id { get; set; }
public IEnumerable<int> PossibleInstagramIDs { get; set; }
public int InstagramID { get; set; }
}
And this will allow you to simply query as follows:
var collection = _mongoDatabase.GetCollection<TestInstagram>("testInstagram");
collection.AsQueryable().Where(ti => ti.PossibleInstagramIDs.Count() > 1 && ti.InstagramID == null);

Related

.NET Core MongoDB. Find by guid returns null

I have the following setup:
The document:
[BsonCollection("Users")] // I get the collection name with a custom extension
[BsonIgnoreExtraElements]
public class UserDocument
{
[BsonId]
public Guid Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public UserSettingsModel UserSettings { get; set; }
}
public class UserSettingsModel
{
// ...
}
The repository:
public class UserRepository
{
private readonly IMongoCollection<UserDocument> _collection;
private readonly ILogger<UserRepository> _logger;
public UserRepository(IMongoDatabase database, ILogger<UserRepository> logger)
{
// returns "Users"
var collectionName = typeof(UserDocument).GetCollectionName();
_collection = database.GetCollection<UserDocument>(collectionName);
}
// ...
public async Task<UserDocument> GetById(Guid id)
{
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, id);
var user = await _collection.FindAsync(filter);
// var user = await _collection.FindAsync(x => x.Id == id); - doesn't work either
var request = filter.Render(
_collection.DocumentSerializer,
_collection.Settings.SerializerRegistry).ToString();
_logger.LogDebug(request);
return user.FirstOrDefault();
}
}
And I initialize the client this way:
// ...
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
var client = new MongoClient(connectionString);
var database = client.GetDatabase(dbName);
services.AddSingleton(c => database);
// convention pack and registries
// ...
// if moved here doesn't work either
// BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
The filter generated in GetById is still the following: { "_id" : CSUUID("459f165a-4a91-4f39-906c-dc7401ee2468") } when I expect it to be UUID instead of CSUUID.
So, the query doesn't find anything and returns null. In the database the document I'm searching for has _id: UUID('459f165a-4a91-4f39-906c-dc7401ee2468')
What am I doing wrong?
I was able to fixing by this trick:
var mongoConnectionUrl = new MongoUrl(connectionString);
var mongoClientSettings = MongoClientSettings.FromUrl(mongoConnectionUrl);
// before initializing client
mongoClientSettings.GuidRepresentation = GuidRepresentation.Standard;
However setting the GuidRepresentation in client settings is obsolete, which is quite confusing. Also, the query generated still has CSUUID instead of UUID. I was able to log the query the following way:
mongoClientSettings.ClusterConfigurator = cb =>
{
cb.Subscribe<CommandStartedEvent>(e => logger.LogDebug($"{e.CommandName} - {e.Command.ToJson()}"));
};
If anyone finds a better way and post it here it would be appreciated.

Project into a (list of) concrete object instead of an (list of) anonymous object

The problem simplified is as follows: in Entity Framework i am doing a join involving 3 tables, and returning the joined result set, which involves (some) fields from the 3 tables.
var query = (
from t1 in dbCtx.TB_Entity1
from t2 in dbCtx.TB_Entity2
.Where(p => p.someCol == t1.someCol && t.IsActive == true)
.DefaultIfEmpty() //LEFT JOIN
from t3 in dbCtx.TB_Entity3
.Where(q => q.someCol == t2.someCol)
where t1.IsLatest == true
&& (t1.istatus == 2
|| t1.istatus == 3
)
select new {
t1.col100,
t1.col101,
t2.col200,
t2.col201,
t3.col300,
t3.col301
}).OrderByDescending(t1 => t1.ID);
var anonObjList = query.ToList();
Thus, at the end of the query I write a projection to select the fields i want.
Finally i run the query with .ToList() and get a list of Anonymous objects.
How do i modify the query to project into a List of MyConcreteClass
i.e. i want to be able to write something similar to
List<MyConcreteClass> myObjList = query.ToList();
You may assume my concrete class looks like
public class MyConcreteClass
{
public string Col100 { get; set; }
public string Col101 { get; set; }
public string Col200 { get; set; }
public string Col201 { get; set; }
public string Col300 { get; set; }
public string Col301 { get; set; }
}
You just use the object initializer syntax:
new MyConcreteClass
{
Col100 = t1.col100,
Col101 = t1.col101,
Col200 = t2.col200,
Col201 = t2.col201,
Col300 = t3.col300,
Col301 = t3.col301
}

The entity or complex type cannot be constructed in a LINQ to Entities query [duplicate]

This question already has answers here:
The entity cannot be constructed in a LINQ to Entities query
(14 answers)
Closed 10 years ago.
I have two functions that look exactly the same except they create lists of two different objects. The two different objects look very much alike, but when I try to run one of the functions on one of the objects, I get the error message, "The entity or complex type cannot be constructed in a LINQ to Entities query.". Can someone explain to me what is happening in very simple terms? Also, can you tell me how to change my code so that it works? Thanks, Allan.
Function 1 (works):
public static List<ChartApp> ListChartApplications()
{
using (var db = new LatencyDBContext())
{
var appNames = db.LoginApplications.Select(item => new ChartApp()
{
LoginApplicationID = item.LoginApplicationID,
LoginAppName = item.LoginAppName,
}).OrderBy(item => item.LoginAppName);
return appNames.ToList();
}
}
Function 2 (throws error on "return appNames.ToList();"):
public static List<LoginApplication> ListApplications()
{
using (var db = new LatencyDBContext())
{
var appNames = db.LoginApplications.Select(item => new LoginApplication()
{
LoginApplicationID = item.LoginApplicationID,
LoginAppName = item.LoginAppName,
}).OrderBy(item => item.LoginAppName);
return appNames.ToList();
}
}
Classes:
public class ChartApp
{
public ChartApp()
{
this.LoginHistories = new List<ChartHist>();
}
public int? LoginApplicationID { get; set; }
public string LoginAppName { get; set; }
public virtual ICollection<ChartHist> LoginHistories { get; set; }
public int Step { get; set; }
}
public class LoginApplication
{
public LoginApplication()
{
this.LoginHistories = new List<LoginHistory>();
}
public int LoginApplicationID { get; set; }
public string LoginAppName { get; set; }
public virtual ICollection<LoginHistory> LoginHistories { get; set; }
}
Edit: Could the difference possibly be that one of the objects are mapped to the database?
public class LoginApplicationMap : EntityTypeConfiguration<LoginApplication>
{
public LoginApplicationMap()
{
// Primary Key
this.HasKey(t => t.LoginApplicationID);
// Properties
this.Property(t => t.LoginAppName)
.HasMaxLength(500);
// Table & Column Mappings
this.ToTable("LoginApplication");
this.Property(t => t.LoginApplicationID).HasColumnName("LoginApplicationID");
this.Property(t => t.LoginAppName).HasColumnName("LoginAppName");
}
}
My solution in this case was to just delete the non-working function and use the working one in all places. For, similar functions that are mapped, I use the following function to return values.
public static List<LoginEnvironment> ListEnvironments(bool allSelection)
{
using (var db = new LatencyDBContext())
{
//GET ALL THE ENVIRONMENT NAMES
var envNames = from e in db.LoginEnvironments
orderby e.LoginEnvName
select e;
//PUT ALL THE ENVIRONMENTS INTO A LOCAL LIST
var listEnv = new List<LoginEnvironment>();
if (allSelection)
{
var defaultAll = new LoginEnvironment();
defaultAll.LoginEnvironmentID = 0;
defaultAll.LoginEnvName = "All";
listEnv.Add(defaultAll);
}
foreach (var item in envNames)
{
var localEnv = new LoginEnvironment();
localEnv.LoginEnvironmentID = item.LoginEnvironmentID;
localEnv.LoginEnvName = item.LoginEnvName;
listEnv.Add(localEnv);
}
return listEnv;
}
}

Converting ESQL to LINQ to Entities. Sort by related entities

I am using EF + RIA and unfortunately meet some problems with sorting by related entities.
For such purpose there is ESQL query that I implemented (found only this solution):
var queryESQL = string.Format(
#" select VALUE ent from SomeEntities as ent
join Attributes as ea ON ea.EntityId = ent.Id
where ea.AttributeTypeId = #typeId
order by ea.{0} {1}", columnName, descending ? "desc" : "asc");
var query = ObjectContext.CreateQuery<SomeEntity>(queryESQL, new ObjectParameter("typeId", attributeTypeId));
Tables have following structure:
<Attribute>:
int Id;
decimal DecimalColumn;
string StringColumn;
int EntityId;
int AttributeTypeId;
<SomeEntity>:
int Id;
string Name;
Is there any way to rewrite this stuff(sorting), using LINQ to Entities approach?
Here's my attempt, I can't guarantee it will work. I need to think more on how to get a dynamic column name, I'm not sure on that one. EDIT: you can use a string for the order column.
int typeId = 1115;
bool orderAscending = false;
string columnName = "StringColumn";
var query = from ent in SomeEntities
join ea in Attributes on ea.EntityId = ent.Id
where ea.AttributeTypeId = typeId;
if(orderAscending)
{
query = query.OrderBy(ea => columnName).Select(ea => ea.Value);
}
else
{
query = query.OrderByDescending(ea => columnName).Select(ea => ea.Value);
}
var results = query.ToList(); // call toList or enumerate to execute the query, since LINQ has deferred execution.
EDIT: I think that ordering after the select stops is from ordering by. I moved the select statement to after the order by. I also added the "query =", but I'm not sure if that is needed. I don't have a way to test this at the moment.
EDIT 3: I fired up LINQPad today and made a few tweaks to what I had before. I modeled your data in a Code-first approach to using EF and it should be close to what you have.
This approach works better if you're just trying to get a list of Attributes (which you aren't). To get around that I added an Entity property to the MyAttribute class.
This code works in LINQPAD.
void Main()
{
// add test entities as needed. I'm assuming you have an Attibutes collection on your Entity based on your tables.
List<MyEntity> SomeEntities = new List<MyEntity>();
MyEntity e1 = new MyEntity();
MyAttribute a1 = new MyAttribute(){ StringColumn="One", DecimalColumn=25.6M, Id=1, EntityId=1, AttributeTypeId = 1, Entity=e1 };
e1.Attributes.Add(a1);
e1.Id = 1;
e1.Name= "E1";
SomeEntities.Add(e1);
MyEntity e2 = new MyEntity();
MyAttribute a2 = new MyAttribute(){ StringColumn="Two", DecimalColumn=198.7M, Id=2, EntityId=2, AttributeTypeId = 1, Entity=e2 };
e2.Attributes.Add(a2);
e2.Id = 2;
e2.Name = "E2";
SomeEntities.Add(e2);
MyEntity e3 = new MyEntity();
MyAttribute a3 = new MyAttribute(){ StringColumn="Three", DecimalColumn=65.9M, Id=3, EntityId=3, AttributeTypeId = 1, Entity=e3 };
e3.Attributes.Add(a3);
e3.Id = 3;
e3.Name = "E3";
SomeEntities.Add(e3);
List<MyAttribute> attributes = new List<MyAttribute>();
attributes.Add(a1);
attributes.Add(a2);
attributes.Add(a3);
int typeId = 1;
bool orderAscending = true;
string columnName = "StringColumn";
var query = (from ent in SomeEntities
where ent.Attributes.Any(a => a.AttributeTypeId == typeId)
select ent.Attributes).SelectMany(a => a).AsQueryable();
query.Dump("Pre Ordering");
if(orderAscending)
{
// query = is needed
query = query.OrderBy(att => MyEntity.GetPropertyValue(att, columnName));
}
else
{
query = query.OrderByDescending(att => MyEntity.GetPropertyValue(att, columnName));
}
// returns a list of MyAttributes. If you need to get a list of attributes, add a MyEntity property to the MyAttribute class and populate it
var results = query.Select(att => att.Entity).ToList().Dump();
}
// Define other methods and classes here
}
class MyAttribute
{
public int Id { get; set; }
public decimal DecimalColumn { get; set; }
public string StringColumn { get; set; }
public int EntityId { get; set; }
public int AttributeTypeId { get; set; }
// having this property will require an Include in EF to return it then query, which is less effecient than the original ObjectQuery< for the question
public MyEntity Entity { get; set; }
}
class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MyAttribute> Attributes { get; set; }
public MyEntity()
{
this.Attributes = new List<MyAttribute>();
}
// this could have been on any class, I stuck it here for ease of use in LINQPad
// caution reflection may be slow
public static object GetPropertyValue(object obj, string property)
{
// from Kjetil Watnedal on http://stackoverflow.com/questions/41244/dynamic-linq-orderby
System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
return propertyInfo.GetValue(obj, null);
}

MongoDB array object Update and Page

I've a entity class stored in MongoDB that looks similar to these:
public class Theme
{
public ObjectId Id { get; set; }
public string Description { get; set; }
public string Title { get; set; }
public List<Comment> CommentList { get; set; }
}
public class Comment
{
public string Content { get; set; }
public string Creator { get; set; }
public string Date { get; set; }
}
Using the MongoDB C# driver,and based on the model, how can i resolve for the following questions:
Update a comment of the theme, eg: theme1.CommetList[10].Creator = "Jack"
How to page for the array object
Thanks.
Wentel
#Andrew Orsich
Thanks for your help.
And there is another trouble:
var query = Query.EQ("Id", id); //The 'id' type is ObjectId
List<Theme> newDatas = themeCollection.FindAs<Theme>(query).ToList();
Theme newData = themeCollection.FindOneByIdAs<Theme>(allDatas[0].Id);
Result: 'newDatas' is null and 'newData' has data, why?
1.Using positional operator:
var query = Query.And(Query.EQ("Id", id));
var update = Update.Set("CommetList.10.Creator", "Jack");
Also you probably need to add id to the Comment class. In this case you can updated matched by query comment like this:
var query = Query.And(Query.EQ("Id", id), Query.EQ("CommentList.Id", commentId));
var update = Update.Set("CommentList.$.Creator", "Jack");
2.You can load entire theme and do paging of comments from c# using linq for example. Or you can also use $slice like this:
var comments = themeCollection
.FindAs<Comment>()
.SetFields(Fields.Slice("Comments", 40, 20))
.ToList();
For your second question you need to do the following:
ObjectId oid = new ObjectId(id);
var query = Query.EQ("_id", oid);