C# MongoDB, LINQ unable to match by Id - c#

I'm using MongoDB.Driver (2.12.4) while trying to stay away from Bson documents. Apart from the Mongo collection methods I use LINQ for all of my queries.
I have a baseclass like this:
public abstract class Entity
{
public Guid Id { get; protected set; }
}
And a subclass like this:
public class User : Entity
{
public string Name { get; set; }
public string Email { get; set; }
}
public User(string name = "", string email = "")
{
Id = Guid.NewGuid();
Name = name;
Email = email;
}
And my database class is initialized like this:
class Database
{
const string MongoConnection = "mongodb+srv://user:password#cluster0.mongodb.net/";
public static MongoClient Client { get; private set; }
public static IMongoDatabase Directory { get; private set; }
public static IMongoCollection<User> Users { get; private set; }
public Database()
{
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
Client = new MongoClient(MongoConnection);
Directory = Client.GetDatabase("DB");
Users = Directory.GetCollection<User>("users");
}
}
What has been working so far:
Database.Users.InsertOne((new User("name", "email#domain.com")); //cloud.mongodb.com shows {_id : UUID('a12937ba-7a7c-4c5d-a8ff-4049e9878ca3')}
var document = Database.Users.AsQueryable().Where(x => x.Email == "email#domain.com").FirstOrDefault();
Database.Users.AsQueryable().ToList().Where(x => x.Id == document.Id).FirstOrDefault()
But none of the following is working:
Database.Users.Find(x => x.Id == document.Id).FirstOrDefault(); //returns null
Database.Users.AsQueryable().Any(x => x.Id == document.Id); //returns false
Database.Users.ReplaceOne(x => x.Id == document.Id, document); //returns MatchedCount = 0
So it seems that as soon as I use .ToList then everything works as expected. But if I use .AsQueryable, .Find, .Any or .ReplaceOne directly on the collection then it's not able to perform a match between Ids.
I am not able to find a similar question that does not use [BsonId] or [BsonElement] and does not make use of filters and builders. If I understand correctly the MongoDB.Driver is supposed to do the mapping automatically, and for every property it works except the Id. What am I missing?
PS: I am not using Entity Framework

you need to add this on start of the app:
BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;
this makes the query possible with the Guid Id.
it does have a comment of "obsolete" on the latest driver but i guess that's a bug.

Related

Entity Framework "FromSqlRaw or FromSqlInterpolated was called with non-composable SQL and with a query composing over it." error for a return object

I have this view
[Table("vw_Entity", Schema = "c")]
public partial class vw_Entity : BaseEntity
{
public long? PredictedEntityTypeID { get; set; }
public bool IsManuallyChanged { get; set; }
}
where BaseEntity is the class that stores only my ID and UUID.
This is my DTO return object:
public class EntityDTO
{
public long ID { get; set; }
public LookupTableDetails PredictedEntityTypeId { get; set; }
public bool IsManuallyChanged { get; set; }
}
where LookupTableDetails looks like:
public class LookupTableDetails
{
public long Id { get; set; }
public string Name { get; set; }
}
Now I have this stored procedure that does basically a PATCH. I call it using the following snippet:
var data = await _context.vw_Entity.FromSqlRaw("EXECUTE core.Update_Entity #EntityID", parameters)
.Select(x => new EntityDTO()
{
ID = x.ID,
PredictedEntityTypeId = new LookupTableDetails() { Id = x.PredictedEntityTypeId, Name = x.PredictedEntityTypeId == 1 ? "Entity1" : "Entity2" },
IsManuallyChanged = x.IsManuallyChanged
}).ToListAsync();
However, this crashes with an error
FromSqlRaw or FromSqlInterpolated was called with non-composable SQL and with a query composing over it
I'm aware what this error does, if I have a object of some other class inside my view then the stored procedure couldn't map it properly and return the error but in this case, my view is clear from obstacles of that type and all I need to do is just return the LookupTableDetails in my DTO object. The error is in
PredictedEntityTypeId = new LookupTableDetails() { Id = x.PredictedEntityTypeId, Name = x.PredictedEntityTypeId == 1 ? "Entity1" : "Entity2" }
I tried most of the solutions that the Internet offers, such as wrapping it with IgnoreFilters.., AsEnumerable() etc.
Any ideas what is the cause and how can I prevent it from happening again in the future i.e fix it? :D
Since You iterate over result from the first query, try changing to:
var data = _context.vw_Entity.FromSqlRaw("EXECUTE core.Update_Entity #EntityID", parameters)
.ToList() // force change from IQueryable to IEnumerable and resolve result
.Select(x => new EntityDTO() // You are anyway using all results, so this is ok
{
ID = x.ID,
PredictedEntityTypeId = new LookupTableDetails() { Id = x.PredictedEntityTypeId, Name = x.PredictedEntityTypeId == 1 ? "Entity1" : "Entity2" },
IsManuallyChanged = x.IsManuallyChanged
}).ToList();

Query MongoDB in C# by MongoDBRef

I'm trying to get all Photos of an User, querying by it's UserId reference. (I know about embedded documents in Mongo, but this is how I'd like to use it).
Here is the error I get: "System.InvalidOperationException: '{UserId.$id}.ToString() is not supported'"
public ICollection<Photo> GetAllUserPhotos(string userId)
{
var photos = _photos.Find(photo => photo.UserId.Id.ToString() == userId);
var photoListTest = photos.ToList() // here I get the error
return photoListTest;
}
A "normal" query like this works without problems:
public List<User> GetAllUsers() => _users.find(user => true).ToList();
Here are my models:
public class User
{
[BsonRepresentation(BsonType.ObjectId)]
[BsonId]
public string Id { get; set; }
public string Name { get; set; }
}
public class Photo
{
[BsonRepresentation(BsonType.ObjectId)]
[BsonId]
public string Id { get; set; }
public MongoDBRef UserId { get; set; }
}
The problem here is that .Find() method takes Expression<T,bool> as parameter and then when you hit .ToList() MongoDB driver tries to convert such expression into MongoDB query / aggregation language. MongoDB .NET driver doesn't understand {UserId.$id}.ToString() and therefore you're getting an exception.
To fix it you should try the other way around - convert your variable in-memory to a type that's stored in the database, try:
var userIdConverted = ObjectId.Parse(userId); // or use string if it's string in your database
var dbRef = new MongoDBRef("colName", userIdConverted);
var photos = _photos.Find(photo => photo.UserId.Id.ToString() == dbRef );

Implementing all read api with a intercepting filter using Servicestack.Ormlite

To elaborate what I try to achieve with servicestack.ormlite. Imagine that a franchise business has some branches, each branch has system and local database, all of these database are replicating each other. In the system, each model is with a property called store_id like below.
public class UserEntity : EntityBase
{
[PrimaryKey, AutoIncrement]
public int id { get; set; }
public string user_id { get; set; }
public string name { get; set; }
public string email { get; set; }
public string password { get; set; }
public int role { get; set; }
}
public class EntityBase
{
public int store_id {get;set;}
public bool is_delete {get;set;}
}
We have 40+ entity and repos, is there any way to have all servicestack.ormlite read api filtered by store_id in one action instead of coding repo by repo ? I've a abstract repobase from which all repos are derived. And some repos needs to read all data across different store_id.
any help is much appreciated !!
This question is still unclear on what answer it wants, the screenshot says it doesn't know which API to use to filter by store_id but your screenshot includes 2 different examples of filtering by store_id?
db.Where<T>(new { store_id = _store_id });
db.Where<T>("store_id", _store_id);
Both of which should work. Although I'd recommend using the Typed version when possible, you can also use nameof() instead of magic strings:
db.Where<T>(nameof(EntityBase.store_id), _store_id);
Maybe you're after different examples of doing the same thing inside a generic repo?
You can also query using a typed SqlExpression<T>:
var q = db.From<T>().Where(x => (x as EntityBase).store_id == _store_id);
var all = db.Select(q);
Or if you want to combine it with an additional typed expression:
var q = db.From<T>().Where(x => (x as EntityBase).store_id == _store_id);
var filtered = db.Select(q.And(expr));
Since you're already using generic constraints, you can also add a constraint that the entity must be a EntityBase as well, e.g:
class RepoBase<T> where T : EntityBase, new() { ... }
That way you can query without casting, e.g:
var q = db.From<T>().Where(x => x.store_id == _store_id);
var all = db.Select(q);
and
var q = db.From<T>().Where(x => x.store_id == _store_id);
var filtered = db.Select(q.And(expr));

C# - The ObjectContext instance has been disposed and can no longer be used for operations that require a connection

I'm sorry for the repetition. I'm heading with a same problem but still could not handle it. I'm using Angularjs framework and Asp.net mvc.
My person class:
public partial class Person
{
public int PersonID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Telephone { get; set; }
public string Adress { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
}
My User class:
public partial class User
{
public User()
{
this.People = new HashSet<Person>();
}
public int UserID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
public string Gender { get; set; }
public virtual ICollection<Person> People { get; set; }
}
My js code:
$http.get('/Data/GetPeople', { params: { UserID: "1" } }).success(function (data) {
$scope.model = data;
});
I'm trying to get records from my database:
public ActionResult GetPeople(int UserID)
{
using (PersonEntities dc = new PersonEntities())
{
var model = new PersonIndexVM();
model.People = dc.People.Where(b => b.UserID == UserID).ToList();
//model.People = dc.People.Include("User").ToList();
return Json(model, JsonRequestBehavior.AllowGet);
}
}
As I see with debugging, I'm getting the right objects from database in GetPeople method. But after that I'm getting this error:
'The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.'
I tried to Eagerly load: model.People = dc.People.Include("User").Where(b => b.UserID == UserID).ToList(); Still getting the same error.
It would be a pleasure if you help me.
The problem is solved. I get help from my friend. Its about unclosed connection. Its my fault. I didn't mention it.
In PersonIndexVM(), I created People = new List<Person>(); Person class is created by entity framework. It is related with database. When I create a model that is an object of PersonIndexVM() in GetPeople() method and return this model object as a json, model object try to reach User class informations after the connection closed. And I'm getting this error. To solve this problem:
Closing the lazy loading to prevent reaching User information. dc.Configuration.LazyLoadingEnabled = false;
Creating another class not related with database and return its object as Json.
You can strongly type your inclusion, which will give you hints as to whether or not your object structure is correctly related. This solves any "magic strings" issues, such as your table being named Users instead of User inside of your EF context, after including DbExtension.
using System.Data.Entity.DbExtension;
model.People = dc.People.Include(c => c.User).ToList();
However, if you are using ObjectContext instead of DbContext, you may be stuck with magic strings. That said, EF will "pluralize" most names, so your table is most likely named "Users". Check to see if the table exists as dc.Users and, if it does, change your magic string to match.
model.People = dc.People.Include("Users").ToList();
It's look like the problem is when you using keyword Using.
Look at this How to solve the error The ObjectContext
In my case, i was passsing all model 'Users' to a column, and it doesn't mapped correctly, so i just pass 'Users.Name' and it fixed it.!!
var data = db.ApplicationTranceLogs
.Include(q=>q.Users).Include(q => q.LookupItems)
.Select(q => new { Id = q.Id, FormatDate = q.Date.ToString("yyyy/MM/dd"), Users = ***q.Users***, ProcessType = q.ProcessType, CoreProcessId = q.CoreProcessId, Data = q.Data })
.ToList();
--
var data = db.ApplicationTranceLogs
.Include(q=>q.Users).Include(q => q.LookupItems)
.Select(q => new { Id = q.Id, FormatDate = q.Date.ToString("yyyy/MM/dd"), Users = ***q.Users.Name***, ProcessType = q.ProcessType, CoreProcessId = q.CoreProcessId, Data = q.Data })
.ToList();
In my case I had a bug in the front end, which was causing the same function in the backend to be triggered 3 times, causing a threading issue.
Maybe look into this as a possibility too.

How can I extend the EF Repository Model to do more complex queries?

I have the following:
public partial class Subject
{
public Subject()
{
this.Contents = new List<Content>();
}
public int SubjectId { get; set; }
public string Name { get; set; }
public virtual ICollection<Content> Contents { get; set; }
}
public partial class Content
{
public int ContentId { get; set; }
public int ContentTypeId { get; set; }
public string Title { get; set; }
public string Text { get; set; }
public int SubjectId { get; set; }
public virtual Subject Subject { get; set; }
}
In my SQL Server database I have an index on the Content table of SubectId and ContentTypeId
My classes are working find with a standard repository that has such methods such as GetAll() and GetId(id) however using the repository model is there a way I can do more complex queries. In this case I would somehow want todo a query for a particular SujectId and a contentTypeId. What I want to avoid is having a query that gets every content record and then filters out what I need. I'd like some way to send a real query of exactly what I need to SQL Server.
Currently my generic repository has the following:
public virtual T GetById(int id)
{
return DbSet.Find(id);
}
Could I do what I need by implementing creating a ContentRepository and having something like the following:
public IQuerable<Content> GetAllBySubjectId(int id)
{
return DbSet.Where(c => c.SubjectId == id);
}
If so then how could I use the GetAllBySubjectId and add in the check for where ContentId == "01" for example?
You could add to your repository a method like this:
public IQueryable<T> Find(Expression<Func<T, bool>> predicate)
{
return DbSet.Where<T>(predicate);
}
Then write sth like:
repository.Find(c => c.SubjectId == myId);
If you use Entity Framework with LINQ, it will try to generate and send optimized queries to the database, for example, if you do something like:
var contents =
from c in Context.Contents // or directly the DbSet of Contents
where c.ContentTypeId == 2
select new { c.Title, c.ContentId };
It should generate a query along the lines of the following (you can use a SQL Profiler):
select
c.Title as Title,
c.ContentId as ContentId
from Contents c
where
c.ContentTypeId == 2
There are some considerations to think of, but most of the time EF will generate good performance queries.
To know more about that, I recommend the following URL: http://www.sql-server-performance.com/2012/entity-framework-performance-optimization/

Categories

Resources