Single Element Using Lookup operator - c#

I am stuck in an issue with MongoDB. I am using C# driver for MongoDB.
So basically I am trying to join Categories document with Users document. Each category document has one property:
public Guid UserId { get; set; }
This property is an ObjectId behind the scenes. I have another property:
public UserDoc User { get; set; }
Now I am trying to fill this User property with all the user details based on the UserId property. This is the code I am trying to achieve this:
categoriesCollection.Aggregate()
.Lookup<CategoryDoc, UserDoc, CategoryDoc>(
usersCollection,
x => x.UserId,
x => x.Id,
x => x.User)
As expected, 'Lookup' is expecting an array of User documents but I have a property referencing a single user object and thus, it throws an error:
An error occurred while deserializing the User property of class TatSat.API.Documents.CategoryDoc: Expected a nested document representing the serialized form of a TatSat.API.Documents.UserDoc value, but found a value of type Array instead.
Can someone help me with this? I am new to Mongo so this is a bit of a pain for me. Kindly note that I am looking for a strongly typed solution and don't want to mess with BsonDocuments if that can be avoided.
Thanks in advance.

So I finally figured out in some ways. Basically I decided to have another class:
public class CategoryDocWithTemporaryData : CategoryDoc
{
public UserDoc[] Users { get; set; }
public static Expression<Func<CategoryDocWithTemporaryData, CategoryDoc>> ToCategoryDoc => c =>
new CategoryDoc
{
Id = c.Id,
//other properties
User = c.Users.First()
};
}
Then I use the lookup as:
categoriesCollection.Aggregate()
.Lookup<CategoryDoc, UserDoc, CategoryDocWithTemporaryData>(
usersCollection,
c => c.UserId,
c => c.Id,
c => c.Users)
.Project(CategoryDocWithTemporaryData.ToCategoryDoc)
This, however needs me to use additional projection where I have to manually select all properties which is something I wanted to avoid. But I think I can live with it until I come across a better approach.

Lookup will always return an array, even if it only has 1 element. If you need just the first (or only) document in that array, use a project/set/addFields stage with arrayElemAt.

Related

Entity Framework include children that grand children are null

Seem like a simple one, but not coming up with the right thing. All that I need to do is get a count of child items that have it's own child null. Here is what I have been working with:
var data = await _context.award.Include(a => a.expenses.Select(p => p.invoice == null)).ToListAsync();
I have also tried other combinations here with no luck. The error I get is
InvalidOperationException: The property expression 'a => {from expense p in [a].expenses select ([p].invoice == null)}' is not valid. The expression should represent a property access: 't => t.MyProperty'.
I change it to match and it just triggers a new error.
I just want to get a list of award with it's list of expenses listed (fine with just the .ID if that influences the solution) where the invoice parent object is not set and is null.
UPDATE
requested models
public class invoice
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[ForeignKey("INV_NUM_ForeignKey")]
public invoice_number fin_invoice_number { get; set; }
}
public class invoice_number
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public int number { get; set; }
public invoice invoice { get; set; }
public string display { get { return string.Format("sps-{0}", (new String('0', 6) + number.ToString())).Substring(number.ToString().Count()-7, number.ToString().Count()); } }
}
You have to use .Include together with .ThenInclude. Docs explains it clearly here (Including multiple levels).
var data = await _context.award
.Include(a => a.expenses)
.ThenInclude(e => e.invoice)
.ToListAsync();
Notice: But notice, that ThenInclude has two overloads and chances are big, that Visual Studio will select the wrong one or just display one (wrong one) and give you either errors while typing or do not offer autocompetition for e if e is not a collection. If you ignore the error and type the correct property and close the bracket, the error will disappear.
It seems you know what you are doing but
sometimes it happens and a magic letter solves nightmare problems...
Generally (Which version of EF you use dunno),
as far as i know, like described here
https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
Check your model has related properties
Decide Eagerly or Lazy ...
If thesee are not solution then switch your computer :)..
Then Just in EF configuration check relation definitions
Sorry still can not comment out.I had to write answer...
Try re-writing your code like this
var data = await _context.award.Include(a => a.expenses).Where(p => p.expenses.Any(a => a.invoice == null)).ToListAsync();

How to store a c# List of objects into ElasticSearch with NEST 2.x

I'm developing a cross-platform app with xamarin.forms and I'm trying to look for a way to store a List of Objects directly into ElasticSearch so I can later search for results based on the objects of the lists. My scenario is the folloring:
public class Box {
[String(Index = FieldIndexOption.NotAnalyzed)]
public string id { get; set; }
public List<Category> categories { get; set; }
}
public class Category {
[String(Index = FieldIndexOption.NotAnalyzed)]
public string id { get; set; }
public string name { get; set; }
}
My aim is to be able to search for all the boxes that have a specific category.
I have tried to map everything properly like it says in the documentation but if I do it like that, when I store a box, it only stores the first category.
Is there actually a way to do it or is it just not possible with NEST?
Any tips are very welcome!
Thanks
It should just work fine with AutoMap using the code in the documentation:
If the index does not exist:
var descriptor = new CreateIndexDescriptor("indexyouwant")
.Mappings(ms => ms
.Map<Box>(m => m.AutoMap())
);
and then call something like:
await client.CreateIndexAsync(descriptor).ConfigureAwait(false);
or, when not using async:
client.CreateIndex(descriptor);
If the index already exists
Then forget about creating the CreateIndexDescriptor part above and just call:
await client.MapAsync<Box>(m => m.Index("existingindexname").AutoMap()).ConfigureAwait(false);
or, when not using async:
client.Map<Box>(m => m.Index("existingindexname").AutoMap());
Once you succesfully created a mapping for a type, you can index the documents.
Is it possible that you first had just one category in a box and mapped that to the index (Before you made it a List)? Because then you have to manually edit the mapping I guess, for example in Sense.
I don't know if you already have important data in your index but you could also delete the whole index (the mapping will be deleted too) and try it again. But then you'll lose all the documents you already indexed at the whole index.

AutoMapper sets related entities to null despite using opt => opt.Ignore()

I am trying to map two objects of the same type through AutoMapper (most recent version installed through NuGet). My code:
public class School
{
public int Id {get; set;}
public string Name{get; set;}
public User CreatedBy {{get; set;}
}
public class User
{
public int Id {get; set;}
public string Code {get; set;}
}
I have EF 6 with MVC. In edit, a copy of School is given to the view and when it is submitted, I am trying to map it to the one in the database so that I can save it. I don't want to change the User in School while mapping. So my code is:
In Edit Controller Action
//...
var school = db.Schools.FirstOrDefault(s => s.Id == viewModel.SchoolId);
Mapper.CreateMap<School, School>().ForMember(r => r.User, opt => opt.Ignore());
//viewModel.School is an object of School type.
school = Mapper.Map<School>(viewModel.School);
//Here school.User is set to null!
Amazed to see school.User is null. I thought I have given express exclusion to the mapper to ignore the destination value. I presume in such cases, it should retain the source property. I have also tried UseDestinationValue() instead. But no luck. What's causing this error? I sometimes wonder if any other better approach is available for EF-specific object mapping.
This is happening because you are creating a new instance of school rather than updating the one you've pulled from the database.
Instead of:
var school = db.Schools.FirstOrDefault(s => s.Id == viewModel.SchoolId);
school = Mapper.Map<School>(viewModel.School);
Do:
var school = db.Schools.FirstOrDefault(s => s.Id == viewModel.SchoolId);
Mapper.Map(viewModel.School, school);
Rather than creating a new instance, this will update the one you already have.
As an aside, you should not be creating maps in your action methods - this should be run in your application initialisation, as it does not need to run on each method.

Code a SQL projection and mapping at the same time?

This works to carve out a DDL object from an Address object from our database:
public class DDL {
public int? id { get; set; }
public string name { get; set; }
}
List<DDL> mylist = Addresses
.Select( q => new DDL { id = q.id, name = q.name })
.ToList();
However, we'd like to keep our POCO to ViewModel mappings in a single place outside of our MVC controller code. We'd like to do something like this:
List<DDL> mylist = Addresses
.Select( q => new DDL(q)) // <-- constructor maps POCO to VM
.ToList();
But SQL cannot use the constructor function. The object initializer above doesn't use functions to map fields. Of course you could do .AsEnumerable().Select( q => new DDL(q)), but this selects all the fields in SQL (including the data), sends it to C#, then C# carves out the fields we need (terribly inefficient to transfer data we don't need.)
Any suggestions? We happen to be using Entity Framework 6 to pull data.
All you need is to define the expression somewhere and use it. For example, in your ViewModel as a static read-only field.
public class SomeViewModel
{
public static readonly Expression<Func<SomeEntity, SomeViewModel>> Map = (o) => new SomeViewModel
{
id = o.id,
name = o.name
}
public int id { get; set; }
public string name { get; set; }
}
// Somewhere in your controller
var mappedAddresses = Addresses.Select(SomeViewModel.Map);
I personally made myself a little static Mapper that keeps all the maps and use them for me. The maps are declared in a static initializer in all my ViewModels. The result gives me something that feels like AutoMapper, yet doesn't require the lib or the complicated mapping code (but also won't do any magic for you).
I can write something like this:
MyCustomMapper.Map<Entity, ViewModel>(entity);
and it's overloaded to accept IEnumerables, IQueryables and a single ViewModel. I also added overloads with only 1 generic type (the entity) that accept a type parameter. This was a requirement for me.
You can use anonymous types to restrict what to select from the DB and then use those fields to construct your object :
List<DDL> mylist = Addresses
.Select( q => new { id, name })
.AsEnumerable()
.Select(i => new DDL(i.id, i.name) // <- On the Enumerable and not on the Queryable
.ToList();
Are you against using 3rd party libraries? Automapper's QueryableExtensions does exactly what you want.
List<DDL> mylist = Addresses
.Project().To<DDL>()
.ToList();
It even has nice features like being able to filter on the transformed object and that filter being performed server side.
List<DDL> mylist = Addresses
.Project().To<DDL>()
.Where(d => d.name = "Smith") //This gets translated to SQL even though it was performed on DDL.
.ToList();

LINQ Group By if an object has x number of properties, then select on each group

I have some experience with LINQ but writing this query is proving to be a bit above my head.
Excuse my pseudo-code...
class Person{
Collection<Communications> {get;set}
}
class Communication{
Collection<PersonSender> {get;set;}
Collection<BuildingSender> {get;set;}
}
class PersonSender{
Collection<Posts> {get;set}
}
class BuildingSender{
Collection<Posts> {get;set}
}
What I want to accomplish: Group the Communication collection on whether it contains an instance of PersonSender or BuildingSender when those instances have a Post instance themselves.
Then I want to perform a select query on each group of Collection objects so I can return an IEnumerable collection of another object Package that is created in the select statement using each Collection's properties. The key here is that I need to perform a seperate select statement on each group returned
This is what I've got for the actual query so far
m.Communications.GroupBy(x =>
new {fromMember = (x.PersonSender.Any() && x.Posts.Any()),
fromBuilding = (x.BuildingSender.Any() && x.Posts.Any())})
.Select(u => new Package(u.PersonSender.First().Member,u.Posts.First()));
However I'm pretty sure this doesn't compile and it doesn't offer me the multiple select statements I need.
Is GroupBy the right way to go about this? Is it even possible?
UPDATE: Per #Hogan I have been able to hack together a work solution. Let me try to clear up what I was trying to do though, my original question wasn't very clear...
This code is part of the class PackageFactory . Each method in this class can be invoked by a Controller in my application asking for a set of Package objects. The Package accepts several types of IEntity objects as parameters and wraps the content that is associated with the relationships the IEntity objects have into an interface that any other controller displaying information on my application can read. TLDR Package is a glorified Adapter pattern design object.
Each method in PackageFactory has the job of querying the Communication repository, finding the relevant Communication objects with the right set of properties, and then passing the subset of objects(that are properties of the Communication object) to the a new Package instance to be wrapped before returning the whole set of Package objects to the controller so they can be rendered on a page.
In the case of the method I am writing for this question the user m has a collection of Communication objects where each Communication comes from an IEntity object(either PersonSender or BuildingSender) that was directed at the user m . My query was an attempt to segregate the Communication objects into two sets where one contains all Communication where PeronSender exists and one where BuildingSender exists. This way my method knows which group gets passed to their respective Package type.
My logic for trying to use GroupBy was that I would rather make the query as generic as possible so that I can expand to more sets later AND/OR increase the performance of the method by not having to call many seperate queries and then join them all. However it seems like specifying distinct select queries on each group is not a possibility.
#Hogan 's answer is close to what I want to be able to do.
Hogan's Answer
var result =
m.comm.SelectMany(x => x.person).Where(x => x.posts.Any()).Select(new () { x.name, x.posts})
.Union(m.comm.SelectMany(x=> x.building).Where(x => x.posts.Any()).Select(new () {x.name, x.posts}));
Modified Answer
This is what works:
return m.Communications.Where(x => x.SendingPerson.Any() && x.Posts.Any()).Select(u =>
new PostPackage(u.SendingPerson.First().Member,m,u.Posts.First()))
.Union(m.Communications.Where(x=> x.BuildingSender.Any() && x.Posts.Any()).Select(u =>
new PostPackage(u.BuildingSender.First().Building,m,u.Posts.First())));
Not exactly the same -- My head was a bit foggy when I wrote this question yesterday.
I think the key is the SelectMany not GroupBy -- This will "flatten" a sub-lists. As I've shown below:
With
class Person{
Collection<Communications> comm {get; set;}
}
class Communication{
Collection<PersonSender> person {get; set;}
Collection<BuildingSender> building {get; set;}
}
class PersonSender{
string name {get; set; }
Collection<Posts> posts {get; set;}
}
class BuildingSender{
string name {get; set; }
Collection<Posts> posts {get; set;}
}
given that m is a person:
var result =
m.comm.SelectMany(x => x.person).Where(x => x.posts.Any()).Select(new () { x.name, x.posts})
.Union(m.comm.SelectMany(x=> x.building).Where(x => x.posts.Any()).Select(new () {x.name, x.posts}));

Categories