ElasticSearch set parent for child object using ElasticSearch NEST .NET library - c#

Hi have a basic one to many structure ..
public class Person {
public int PersonId { get; set; }
public string Name { get; set; }
public List<Skill> Skills { get; set; }
}
public class Skill{
public int PersonId { get; set; }
public int SkillId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Installed ElasticSearch NEST 5.x .. using .NET Framework 4.5 ..
Exploring the web from last 2 days but not able to find the way to set Person as a parent for Skill ..
I was assuming that NEST will automatically map the parent child relationship so I tried following
private ElasticLowLevelClient client = new ElasticLowLevelClient();
public void CreatePerson(Person person)
{
var parentResponse = client.Index(person, i => i.Index("myindex").Type("person").Id(person.PersonId));
foreach (var skill in person.Skills)
{
var skillResponse = client.Index(skill, i => i.Index("myindex").Type("personskills").Parent(person.PersonId.ToString()).Id(skill.SkillId)); //here I am getting error
}
}
Document for person creating without any issue but at the time of personskill doc I am getting this error:
Can't specify parent if no parent field has been configured
While exploring I found may articles saying that I need to set the parent type to the child in mapping .. but how .. what is the procedure to custom map the index and how and where I should do that .. not getting any hint .. please guide

The issue was that I was unable to map the parent-child documents properly .. we can map them by creating index with predefined mapping or we can update the mapping
Create Index
private ElasticLowLevelClient client = new ElasticLowLevelClient();
private CreateIndexDescriptor descriptor = new CreateIndexDescriptor("myindex")
.Mappings(ms => ms
.Map<Person>("person", m => m.AutoMap())
.Map<Skill>("personskills", m => m.AutoMap().Parent("person"))
);
public void CreatePerson(Person person)
{
client.CreateIndex(descriptor); //I am creating it here but one can create it in the class where we will create ElasticClient
var parentResponse = client.Index(person, i => i.Index("myindex").Type("person").Id(person.PersonId));
foreach (var skill in person.Skills)
{
var skillResponse = client.Index(skill, i => i.Index("myindex").Type("personskills").Parent(person.PersonId.ToString()).Id(skill.SkillId)); //here I am getting error
}
}
Update mapping
public void CreatePerson(Person person)
{
client.Map<Skill>(m => m
.Parent("person").Index("myindex")); //this will put the default mapping of default index
var parentResponse = client.Index(person, i => i.Index("myindex").Type("person").Id(person.PersonId));
foreach (var skill in person.Skills)
{
var skillResponse = client.Index(skill, i => i.Index("myindex").Type("skill").Parent(person.PersonId.ToString()).Id(skill.SkillId)); //here I am getting error
}
}
Here I have changed child document type to default value .. but one can set it in mapping. Hope this can help others.

Related

How to update a property of an complexObj. inside a Document inside a List<complexObj>? MongoDB C# net.Driver

how to update one property of a document in MongoDb with C# net.Driver.
My data model in C# looks like this:
public class MyMediListsWrapper
{
[BsonId]
public Guid Id { get; set; }
public DateTime Datum { get; set; }
public bool IsActiv { get; set; }
public List<Medi> MediLsts { get; set; }
}
public class Medi
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Typ { get; set; }
public string Vmax { get; set; }
}
Now I want to update only one property of one Obj. (Vmax='Test') of the nested List MediLsts.
I wand to filter the collection MyMediListsWrapper by SingleOrDefault IsActiv=true and then I want to filter 'in this Document' the Medi-Obj. from the nested List inside by Guid='XcPwoEaB8Ey7XR1AEB+fLQ=='
and then update Vmax='Test'.
I have a little bit of code, but it doesn't achieve what I want. I have trouble to do the filter first for IsActiv and then filter inside of that Document again for a different property. And I don't know how to write the code for the update later (if found). Thx for help.
My Code:
var db = client.GetDatabase(MySecret.MyMongoDbName);
var myMediListsWrapper = db.GetCollection<MyMediListsWrapper>(MySecret.MyWrapperColName);
var filter = Builders<MyMediListsWrapper>.Filter;
var filterColAndFilterLstObj = filter.And(
filter.Eq(x => x.IsActiv, true),
filter.ElemMatch(x => x.MediLsts, lst => lst.Id == mySearchGuid));
//Just a Test -- I want to UPDATE a property NOT find a entry
//and this is WRONG because it give me a obj of the outer class not the Medi-Obj.
var result = await myMediListsWrapper.Find(filterColAndFilterLstObj).SingleOrDefaultAsync(); ```
the following update command should do the trick:
await collection.UpdateOneAsync(
filter: w => w.IsActiv && w.MediLsts.Any(m => m.Id == mySearchGuid),
update: Builders<MyMediListsWrapper>.Update.Set(m => m.MediLsts[-1].Vmax, "TEST"));
the thing to note here is the [-1] array/list index above.
the driver translates that to the following $set operation:
$set: { "MediLsts.$.Vmax" : "TEST" }
-1 index position is something special the mongo driver uses to denominate $ which is the first positional operator.
i.e. update the first array element that qualifies for the $elemMatch condition of the filter.
.MediLsts.Any(...) is the LINQ equivalent of filter.ElemMatch(...)

Sub-Query or Join with embedded/nested document using C# LINQ with MongoDB

I am trying to do something like bellow example but getting exception as -
System.ArgumentException: Expression of type 'System.Collections.Generic.IEnumerable1 ' cannot be used for parameter of type 'System.Linq.IQueryable1' of method.
Here is my code and related classes . how can i resolve this issue, is there any way except which a trying to do.
var channels = _channelService.Collection;
var tracks = _trackService.Collection;
var query = from b in tracks.AsQueryable()
select b;
var data = (from q in channels.AsQueryable()
from p in q.Episodes
//from x in trackcoll.AsQueryable()
select new
{
p,
Tracks = query.Where(w => p.Tracks.Contains(w.Id))
}).ToList();
// Related classes
public class ContentBase : IAuditable
{
public string Id { get; set ; }
public string CreatedBy { get ; set ; }
public string CreatedOn { get ; set ; }
public string UpdatedBy { get ; set ; }
public string UpdatedOn { get; set; }
}
public class Channel: ContentBase
{
public List<Episode> Episodes { get; set; }
}
public class Episode: ContentBase
{
// List of track Id
public List<string> Tracks { get; set; }
}
public class Track: ContentBase
{
public string TrackUrl { get; set; }
public string Duration { get; set; }
public string Size { get; set; }
public string ContentType { get; set;
}
MongoDB's LINQ support for joins is limited to equal joins as described here. Your expression cannot be translated into Aggregation Framework's $lookup since there's no equivalent syntax for .Contains().
Therefore you have to run an operation that's closer to Aggregation Framework syntax. One example is a fluent aggregate interface which allows you to run extension methods having the same name as Aggregation Framework's operators. Try:
var q = _channels.Aggregate()
.Unwind(x => x.Episodes)
.Lookup(
foreignCollectionName:"tracks",
localField:"Episodes.Tracks",
foreignField:"_id",
#as:"Tracks");
var result = q.ToList();
Above code will return a List<BsonDocument>
mickl's answer will get you there with the official driver, but if you don't like dealing with bsondocuments and would like to have some degree of type-safety, you can simply do the following with mongodb.entities library (which i'm the author of):
public class EpisodeWithTracks
{
public Track[] Tracks { get; set; }
}
var pipeline = new Template<Channel, EpisodeWithTracks>(#"
[
{
$unwind: '$<Episodes>'
},
{
$lookup: {
from: '<track_collection>',
localField: '<Episodes.Tracks>',
foreignField: '_id',
as: '<Tracks>'
}
}
]")
.Path(c => c.Episodes)
.Tag("track_collection", collectionName)
.Path(c => c.Episodes[0].Tracks)
.PathOfResult(ewt => ewt.Tracks);
var result = DB.Aggregate(pipeline)
.ToList();
here's the wiki page explaining how it works.

How to get foreign key objects with entity framework into a custom object?

I am trying to make a select from the database using the entity framework 5.0.
I have a table called Persons that is referenced by PersonsImages, so basically one record from Persons can have many PersonsImages.
I've made a select statement that gives the Persons, but I would also like to get the PersonsImages as a List<PersonsImages>, and put them in a custom object.
This is the code that I have so far:
var person = new Persons();
using (var context = new PersonEntities())
{
person = context.Persons.Where(x => x.personId == 555)
.Select(xxx => new Persons
{
personName = xxx.personName,
personLastName = xxx.personLastName,
PersonImages = xxx.PersonsImages // there's an error here
})
.FirstOrDefault();
}
The Persons and the PersonsImages classes look like that (they are copies of the ones generated by the entity framework):
public partial class Persons
{
public Persons()
{
this.PersonsImages = new HashSet<PersonsImages>();
}
public string personName { get; set; }
public string personLastName { get; set; }
public virtual ICollection<PersonsImages> PersonsImages { get; set; }
}
public partial class PersonsImages
{
public string imageName { get; set; }
public byte[] image { get; set; }
public virtual Persons Persons { get; set; }
}
I know I can make a second select and "manually" find them, but isn't it possible to do it in one, just as what the entity framework normally does?
Assuming your error is "can't construct an object in a LINQ to Entities query" - project into an anonymous type, call the ToArray() method to enumerate the results, then project into new instances of Persons:
person = context.Persons.Where(x => x.personId == 555)
.Select(xxx => new
{
personName = xxx.personName,
personLastName = xxx.personLastName,
PersonImages = xxx.PersonsImages
})
.ToArray() // data now local
.Select(xxx => new Persons
{
personName = xxx.personName,
personLastName = xxx.personLastName,
PersonImages = xxx.PersonsImages
})
.FirstOrDefault();

EF4.1 multiple nested entity Includes gets NotSupportedException?

Edit: Updated problem description based on testing - 12 Sep 2011.
I have this query that throws a NotSupportedException ("Specified method is not supported.") whenever I call .ToList().
IQueryable<FileDefinition> query = db
.FileDefinitions
.Include(x => x.DefinitionChangeLogs)
.Include(x => x.FieldDefinitions.Select(y => y.DefinitionChangeLogs)) // bad
.Include(x => x.FieldDefinitions.Select(y => y.FieldValidationTables)) // bad
.Where(x => x.IsActive);
List<FileDefinition> retval = query.ToList();
If I comment out either line that I have commented as "bad", then the query works. I have also tried including different nested entities in my object model with the same effect. Including any 2 will cause a crash. By nested, I mean a navigation property of a navigation property. I also tried using the .Include methods with a string path: same result.
My table structure looks like this:
This is using MySQL 5.1 (InnoDB tables obviously) as the database store with MySQL Connector/NET 6.3.4.
So my question is: Why doesn't this work?
Note: I can get it to work if I explicitly load the related entities like in this link. But I want to know why EF hates my data model.
ANSWER: MySQL Connector is apparently not capable of handling the 2nd nested entity include. It throws the NotSupportedException, not .NET EF. This same error was also present when I tried this using EF4.0, but my research at the time led me to believe it was self-tracking entities causing the issue. I tried upgrading to latest Connector, but it started causing an Out of Sync error. This is yet another reason for me to hate MySQL.
Maybe a little late to the party but i found the following workaround fairly useful in a current project:
IQueryable<FileDefinition> query = db.FileDefinitions
.Include(x => x.FieldDefinitions.Select(y => y.DefinitionChangeLogs.Select(z => z.FieldDefinition.FieldValidationTables)))
Where rather than using a second row of includes, use Select to get back to the original navigation property and another Select to go forwards to the property you need to include.
I have made a little console application to test your scenario and this test application works:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace EFIncludeTest
{
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ChildLevel1> ChildLevel1s { get; set; }
}
public class ChildLevel1
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ChildLevel2a> ChildLevel2as { get; set; }
public ICollection<ChildLevel2b> ChildLevel2bs { get; set; }
}
public class ChildLevel2a
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ChildLevel2b
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
}
class Program
{
static void Main(string[] args)
{
// Create entities to test
using (var ctx = new MyContext())
{
var parent = new Parent
{
Name = "Parent",
ChildLevel1s = new List<ChildLevel1>
{
new ChildLevel1
{
Name = "FirstChildLevel1",
ChildLevel2as = new List<ChildLevel2a>
{
new ChildLevel2a { Name = "FirstChildLevel2a" },
new ChildLevel2a { Name = "SecondChildLevel2a" }
},
ChildLevel2bs = new List<ChildLevel2b>
{
new ChildLevel2b { Name = "FirstChildLevel2b" },
new ChildLevel2b { Name = "SecondChildLevel2b" }
}
},
new ChildLevel1
{
Name = "SecondChildLevel1",
ChildLevel2as = new List<ChildLevel2a>
{
new ChildLevel2a { Name = "ThirdChildLevel2a" },
new ChildLevel2a { Name = "ForthChildLevel2a" }
},
ChildLevel2bs = new List<ChildLevel2b>
{
new ChildLevel2b { Name = "ThirdChildLevel2b" },
new ChildLevel2b { Name = "ForthChildLevel2b" }
}
},
}
};
ctx.Parents.Add(parent);
ctx.SaveChanges();
}
// Retrieve in new context
using (var ctx = new MyContext())
{
var parents = ctx.Parents
.Include(p => p.ChildLevel1s.Select(c => c.ChildLevel2as))
.Include(p => p.ChildLevel1s.Select(c => c.ChildLevel2bs))
.Where(p => p.Name == "Parent")
.ToList();
// No exception occurs
// Check in debugger: all children are loaded
Console.ReadLine();
}
}
}
}
My understanding was that this basically represents your model and the query you are trying (taking also your comments to your question into account). But somewhere must be an important difference which is not visible in the code snippets in your question and which makes your model fail to work.
Edit
I have tested the working console application above with MS SQL provider (SQL Server 2008 R2 Express DB), not MySQL Connector. Apparently this was the "important difference".

Optimizing simple usage of Linq in C#

I have replicated a stripped-down version of my code that has recently been re-written to use linq to access the database.
However, in my opinion, the linq is really simple and could probably be optimized quite a bit, especially around line 90 where there is a linq statement inside a foreach loop.
It'd be really helpful to see how someone else would go about writing this simple task using linq. Thanks in advance! Below is a snippet of my source code.
// Model objects - these are to be populated from the database,
// which has identical fields and table names.
public class Element
{
public Element()
{
Translations = new Collection<Translation>();
}
public int Id { get; set; }
public string Name { get; set; }
public Collection<Translation> Translations { get; set; }
public class Translation
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public Language Lang { get; set; }
}
}
public class Language
{
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
// Stripped-down functions for adding and loading Element
// objects to/from the database:
public static class DatabaseLoader
{
// Add method isn't too bulky, but I'm sure it could be optimised somewhere.
public static void Add(string name, Collection<Translation> translations)
{
using (var db = DataContextFactory.Create<ElementContext>())
{
var dbElement = new Database.Element()
{
Name = name
};
db.Elements.InsertOnSubmit(dbElement);
// Must be submit so the dbElement gets it's Id set.
db.SubmitChanges();
foreach (var translation in translations)
{
db.Translations.InsertOnSubmit(
new Database.Translation()
{
FK_Element_Id = dbElement.Id,
FK_Language_Id = translation.Lang.Id,
Title = translation.Title,
Content = translation.Content
});
}
// Submit all the changes outside the loop.
db.SubmitChanges();
}
}
// This method is really bulky, and I'd like to see the many separate linq
// calls merged into one clever statement if possible (?).
public static Element Load(int id)
{
using (var db = DataContextFactory.Create<ElementContext>())
{
// Get the database object of the relavent element.
var dbElement =
(from e in db.Elements
where e.Id == id
select e).Single();
// Find all the translations for the current element.
var dbTranslations =
from t in db.Translations
where t.Fk_Element_Id == id
select t;
// This object will be used to buld the model object.
var trans = new Collection<Translation>();
foreach (var translation in dbTranslations)
{
// Build up the 'trans' variable for passing to model object.
// Here there is a linq statement for EVERY itteration of the
// foreach loop... not good (?).
var dbLanguage =
(from l in db.Languages
where l.Id == translation.FK_Language_Id
select l).Single();
trans.Add(new Translation()
{
Id = translation.Id,
Title = translation.Title,
Content = translation.Content,
Language = new Language()
{
Id = dbLanguage.Id,
Name = dbLanguage.Name,
Code = dbLanguage.Code
}
});
}
// The model object is now build up from the database (finally).
return new Element()
{
Id = id,
Name = dbElement.Name,
Translations = trans
};
}
}
}
Using some made-up constructors to oversimplify:
public static Element Load(int id)
{
using (var db = DataContextFactory.Create<ElementContext>())
{
var translations = from t in db.Translations
where t.Fk_Element_Id == id
join l in db.Languages on t.FK_Language_Id equals l.Id
select new Translation(t, l);
return new Element(db.Elements.Where(x => x.Id == id).Single(), translations);
}
}
First thing I don't like in here is all the "new Translation() { bla = bla } because they're big blocks of code, I would put them in a method where you hand them the objects and they return the new for you.
Translations.InsertOnSubmit(CreateDatabaseTranslation(dbElement, translation));
and
trans.Add(CreateTranslationWithLanguage(translation, dbLanguage));
etc, wherever you have code like this, it just muddles the readability of what you're doing in here.

Categories