RavenDB SelectMany alternative - c#

Am building a Photo gallery using Nancy and RavenDB
I have my model classes as below:
[JsonObject(IsReference=true)]
public class Album
{
public Album()
{
this.Photos = new List<Photo>();
this.Tags = new List<String>();
}
public String Id {get; set;}
public String Name {get; set;}
public String Path {get; set;}
public DateTime ModifiedDate{get; set;}
public IList<String> Tags {get; set;}
public IList<Photo> Photos {get; set;}
}
public class Photo
{
public String Id {get; set;}
public String Name {get; set;}
public String Path {get; set;}
public IList<String> Tags {get; set;}
public Album Album {get; set;}
public DateTime ModifiedDate{get; set;}
public bool IsPrivate{get; set;}
}
And my attempt at map reduce index on Photo->Tags:
public class TaggedPhotosIndex:AbstractIndexCreationTask<Album, TaggedPhotos>
{
public TaggedPhotosIndex()
{
Map = albums =>
from a in albums
from p in a.Photos
from t in p.Tags
select new TaggedPhotos
{
Tag = t,
PhotoIds = new List<String> {p.Id}
};
Reduce = results =>
from result in results
group result by result.Tag into agg
select new TaggedPhotos
{
Tag = agg.Key,
PhotoIds = agg.SelectMany(a => a.PhotoIds).ToList()
};
}
}
public class TaggedPhotos
{
public String Tag {get; set;}
public IList<String> PhotoIds {get; set;}
}
Here is what I want to achieve:
Given an array of tags, I want to get all the Photo objects that have atleast one matching tag.
RavenDB doesn't allow SelectMany in the query and am out of ideas.
Found a solution (using LuceneQuery # Workaround for selectmany in ravendb using client api) , but looking forward for any other alternatives.

There are some issues with your document structure that are going to make your query difficult.
Queries are designed to return whole documents. You are asking for a partial result from the document. That can be done, but only if you store all fields in the index and project the results from there. You give up the ACID guarantees of the document store, which is one of RavenDB's strongest features.
You have a reference from Photo back to Album which would normally be embedded, causing a circular reference, except you are setting [JsonObject(IsReference=true)] to avoid that. This may work for basic serialization within a single document, but it is meaningless when it comes to referencing a whole document back from a projected index value. You've set up your own "chicken & egg" problem.
You still are missing basic functionality one would expect from this problem domain. Specifically, you should be able to load a single photo without having to load the whole album.
I strongly suggest that you put photos in their own documents. Then you can have much simpler indexes and avoid the circular references. In DDD terms, both Photo and Album are aggregates. And a DDD aggregate == a RavenDB document. You are currently modeling the Photo as an entity that is not an aggregate, but then you are asking for operations that one would only do against an aggregate - like the search.
If you were asking only "give me all albums that contain a photo tagged with one of these tags", then you would be ok. But you're not asking for the albums, you're asking for the photos.
Assuming you did model it that way, Brett's answer is close - but has some extraneous stuff. Here is a unit test showing a full implementation. Posting on gist since it does not directly address the original question. https://gist.github.com/4499724

This one took me a while but I verified the solution below is working.
First, modify your Index and index result as shown below.
public class TaggedPhotosIndex:AbstractIndexCreationTask<Photo, TaggedPhotos>
{
public TaggedPhotosIndex()
{
Map = photos =>
from p in Photos
from t in p.Tags
select new TaggedPhotos
{
Tag = t,
PhotoId = p.Id
};
}
}
public class TaggedPhotos
{
public string Tag {get; set;}
public string PhotoId {get; set;}
}
Now that the index is created, here is how you query against it to get your photos.
var tagsToSearch = new List<string>(){"test1", "test2", "test3"};
var photoIds = documentSession.Query<TaggedPhotos, TaggedPhotoIndex>()
.Customize(x => x.Include<Photo>(p => p.Id))
.Where(x => x.Tag.In(tagsToSearch))
.Select(x => x.PhotoId)
.Distinct()
.ToArray();
// This doesn't actually make a second call as the photos are already loaded
// in the document session
var photos = documentSession.Load<Photo>(photoIds);
Based on the requirement
Here is what I want to achieve:
Given an array of tags, I want to get all the Photo objects that have
at least one matching tag.
I didn't see how album really tied into it all

Related

how to group by ef entity with two properties

I have for example entity with multiple properties 'myEntity' and I want to Group By 'Age' and 'sex
public class myEntity
{
public int Id{get; set;}
public string FirstName{get; set;}
public string LastName{get; set;}
public int sex{get; set;}
public int Age{get; set;}
}
I try the following query but I get error
var resultTable= dbContext.CashVacs.GroupBy(g => g.sex, g => g.age);
To expand on #MikeMozhaev's comment, the GroupBy method only takes one property which it uses for grouping.
In your case, you need to wrap the two properties you want to group by onto one. The simplest way to do this is to use a tuple...
var resultTable = dbContext.CashVacs.GroupBy(g => new {g.sex, g.Age});
P.S. All credit to #MikeMozhaev who answered first, I'm trying to add some explanation so the OP can (hopefully) understand why that comment answers the question. If #MikeMozhaev wants to post this as an answer, I would be happy for the OP to accept that rather than mine.

Intersecting elements in Entity Framework entities with Linq

I have an Entity Framework entity in my ASP.NET Core 3.1 MVC project called Tag, which is a simple model:
public class Tag
{
public string TagName { get; set; }
public string TagNameNormalized { get; set; }
public DateTime CreatedAt {get; set; }
public string CreatedBy {get; set; }
}
A user can add tags from the website, and these tags are stored in a ICollection<Tag>. But I want to store only new tags, and not duplicates.
At some point, I need to evaluate the ICollection that holds the newly added tags against the ones already stored in the database (_context). I just want unique names (the TagName property), the other properties can be the same.
Right now, I'm doing this:
ICollection<Tag> duplicateTags = _context.Tags.ToList().Intersect(tagsToCheck).ToList();
but this is probably not doing what I want, because it will evaluate all of the model's properties.
What kind of Linq query should I use, where I evaluate just the TagName property, but still select the whole entity?
Any help and suggestions are appreciated!
You can use linq Join
ICollection<Tag> duplicateTags = _context.Tags.Join(
tagsToCheck,
t => t.TagName, c => c.TagName,
(t,c) => t).ToList()
The above will give the duplicate tag objects.
Another way
var tagsNameToCheck = tagsToCheck.Select(t => t.TagName);
ICollection<Tag> duplicateTags = _context.Tags.Where(
t => tagsNameToCheck.Contains(t.TagName))
.ToList();
Point to notice that _context.Tags.ToList() would bring all tags from database into memory which can be a performance issue in your case if there are numerous tags. Would suggest to go for 2nd solution.
Morgan,
You can use Distinct(). For example:
ICollection<Tag> duplicateTags = _context.Select(t => t.TagName).Distinct().ToList()
Does this answer your question?

Circular reference issue in webapi2

Scenario:
An Intern can learn multiple technologies
db design
ef view
Result
controller code:
private InternEntities db = new InternEntities();
// GET: api/Interns
public IQueryable<Intern> GetInterns()
{
return db.Interns;
}
What am i doing wrong here?
This is an expected error, and the reason is because your types reference each other like an Infinity Mirror. In order to solve the problem, you have several options.
1- You can develop a ViewModel and then serialize that one:
public class InternViewModel{
public int Id {get; set;}
public String Name {get; set;}
public List<String> Tehcnologies {get; set;}
}
2- You can select the properties that you need when returning the entity in your actions:
public async Task<List<Technology>> Get() {
var data = dbContext.Set<Technology>().Select(x=> new Technology{
Id = x.Id,
Name = x.Name,
Intern= new Intern {
Id = x.Technology.Id,
Name = x.Technology.Name,
Technologies = null
}
});
return await data.ToListAsync();
}
3- Load only the what you need which is known as Explicit Loading.

automapper with runtime mapping configuration

In my ASP.NET MVC application I need to implemenet mapping from one object to another with some kind of UI for mapping configuration in runtime, so the user can define mapping "on the go". Is there any libraries that supports such functionality?
Description
This is objects in my application. I need to somehow allow user to configure mapping of this objects via UI during application runs. For exmaple some kind of page in my application where user will be able to define mapping in simple way like so map Amout of OrderDTO to Order Qty and later without application recompile change this mapping for exmaple for ExactAmmount
//Object in DAL
public class Order
{
public int Id {get; set;}
public string Name {get; set;}
public decimal Qty {get; set;}
//Lots of other fields
}
//Object from XSD generation (for example)
public class OrderDTO
{
public int Id {get; set;}
public string Description {get; set;}
public decimal Ammout {get; set;}
public decimal VAT {get; set;}
public decimal ExactAmmount {get; set;}
//Lots of other fields
}
Note: for legacy reasons I based this answer on AutoMapper 4.2.1 instead of the current 5.x version. The overall approach should be similar with the new version.
It is possible to create different mapping configurations and different mappers within a program. Also, it is possible to create member mappings by member names (string) instead of lambda expressions. However, some static type information is still necessary (as far as my example goes).
See the following example of a profile, that prepares a custom mapping based on property names:
class MemberProfile : Profile
{
private string from;
private string to;
public MemberProfile(string from, string to)
{
this.from = from;
this.to = to;
}
protected override void Configure()
{
this.CreateMap<Order, OrderDTO>()
.ForMember(to, c => c.MapFrom<decimal>(from));
}
}
This could be extended to support different source property types and a collection of custom mappings instead of a single one.
Usage example:
var order = new Order() { Id = 1, Name = "Test", Qty = 0.5m };
var conf1 = new MapperConfiguration(c => c.AddProfile(new MemberProfile("Qty", "Ammout")));
var conf2 = new MapperConfiguration(c => c.AddProfile(new MemberProfile("Qty", "ExactAmmount")));
var res1 = conf1.CreateMapper().Map<OrderDTO>(order);
var res2 = conf2.CreateMapper().Map<OrderDTO>(order);
For res1, Qty is mapped to Ammout and for res2, Qty is mapped to ExactAmmount. Since the difference is described as string property names, it should be possible to let the user influence this configuration.

Is there a way to map nested collections using ValueInjecter?

I have an EF data structure which has data nested two levels deep, it looks roughly like this:
class Country {
string Name {get; set;}
ICollection<State> States {get; set;}
}
class State {
string Name {get; set;}
ICollection<County> Counties {get; set;}
}
class County {
string Name {get; set;}
}
Or: a country has states, and each state has counties.
I've simplified much of the remote facade for this example so my DTO models are effectively the same structure here. Using ValueInjector I'd like my code to look something like.
var dbCountry = _dbContext.Countries.Find(id);
var dtoCountry = new DTO.Country();
dtoCountry.InjectFrom<FlatLoopInjection>(dbCountry);
foreach (var dbState in dbCountry.States) {
var dtoState= new DTO.State();
dtoState.InjectFrom(dbState);
dtoState.Counties = dbState
.Counties
.Select(m => new DTO.County().InjectFrom(m))
.Cast<DTO.County>()
.ToList();
dtoCountry.States.Add(dtoState);
}
The question is: is there a way to to this without the foreach loop? Not that I'm anti-loop (and I'd love to start a holy war on this with the trolls), but this seems to mixing two patterns together (linq and loop).
Thanks.

Categories

Resources