RavenDB - How to merge related docs of same type into an index projection - indexing

Assuming the following class:
public class Thing
{
public string Id { get; set; }
public string Title{ get; set; }
public string KeyId { get; set; }
public CultureInfo Culture { get; set; }
}
and the following result class:
public class ProjectedThing
{
public string Id { get; set; }
public string Title{ get; set; }
public string KeyId { get; set; }
public CultureInfo Culture { get; set; }
public IEnumerable<Thing> Things { get; set; }
}
How can I build an index that holds the result class?
The closest I've come is with the following index definition:
public class ProjectedThings : AbstractIndexCreationTask<Thing,ProjectedThing>
{
public ProjectedThings()
{
Map = docs => from doc in docs
select new
{
Title = doc.Title,
KeyId = doc.KeyId,
Culture = doc.Culture,
Things = new[] {
new Thing{
Id = doc.Id,
Title = doc.Title,
KeyId = doc.KeyId,
Culture = doc.Culture,
TitlePluralized = doc.TitlePluralized
}
}
};
Reduce = results => from r in results
group r by r.KeyId into g
select new
{
Title = g.FirstOrDefault(x => x.Id == x.KeyId).Title,
KeyId = g.Key,
Culture = g.FirstOrDefault(x => x.Id == x.KeyId).Culture,
Things = from thing in g.SelectMany(x => x.Things).Where(x => x.Id != x.KeyId)
select new
{
Id = thing.Id,
Title = thing.Title,
KeyId = thing.Key,
Culture = thing.Culture
}
};
}
}
That's almost doing the trick, but I can't collect the Title, KeyId, and Culture in the reduction. Only the Things property is being populated.

Look at your code:
g.FirstOrDefault(x => x.Id == x.KeyId)
I don't understand this, but this is likely to be always false.
You probably want:
g.FirstOrDefault().Title,

Related

how to use in clause in Linq Query and pass it dynamically from code

I am converting my project to EF Core in my old project I have a query running.
IDictionary<int, IGrouping<int, UserPurchaseItemAddonWithAmount>> addons =
context.Fetch<UserPurchaseItemAddonWithAmount>($"Select UPIA.*, EA.Amount From UserPurchaseItemAddons UPIA Inner Join ExtraAddons EA on UPIA.AddonID = EA.AddonID Where UPIA.UserPurchaseItemID in ({string.Join(',', userPurchaseItems.Select(S => S.UserPurchaseItemID))})")
.GroupBy(G => G.UserPurchaseItemID).ToDictionary(D => D.Key);
I need to convert this query in to Linq query what I am doing is below
IDictionary<int, IGrouping<int, UserPurchaseItemAddonWithAmount>> addons =
(from f in context.UserPurchaseItemAddons
join s in context.ExtraAddons
on f.AddonId equals s.AddonId
select new
{
Amount = s.Amount,
UserPurchaseItemAddonID = f.UserPurchaseItemAddonId,
UserPurchaseItemID = f.UserPurchaseItemId,
BranchItemVariantID = f.BranchItemVariantId,
AddonID = f.AddonId,
UserID = f.UserId,
IsDeleted = f.IsDeleted,
ModifiedOn = f.ModifiedOn,
ModifiedBy = f.ModifiedBy,
Reason = f.Reason,
}).GroupBy(G => G.UserPurchaseItemID).ToDictionary(D => D.Key);
This query is causing a compiler error related to casting to IGrouping<int, UserPurchaseItemAddonWithAmount> to an anonymous type. The other thing is that how can I apply in clause in where condition in above query, just like the first query .
class
public class UserPurchaseItemAddonWithAmount
{
public decimal Amount { get; set; }
public int UserPurchaseItemAddonID { get; set; }
public int UserPurchaseItemID { get; set; }
public int BranchItemVariantID { get; set; }
public int AddonID { get; set; }
public int UserID { get; set; }
public bool IsDeleted { get; set; }
public DateTime? ModifiedOn { get; set; }
public int? ModifiedBy { get; set; }
public string? Reason { get; set; }
}
Try the following query. Main mistake that you have returned anonymous class.
var purchaseItemIds = userPurchaseItems.Select(S => S.UserPurchaseItemID);
IDictionary<int, IGrouping<int, UserPurchaseItemAddonWithAmount>> addons =
(from f in context.UserPurchaseItemAddons
join s in context.ExtraAddons on f.AddonId equals s.AddonId
where purchaseItemIds.Contains(f.UserPurchaseItemID)
select new UserPurchaseItemAddonWithAmount
{
Amount = s.Amount,
UserPurchaseItemAddonID = f.UserPurchaseItemAddonId,
UserPurchaseItemID = f.UserPurchaseItemId,
BranchItemVariantID = f.BranchItemVariantId,
AddonID = f.AddonId,
UserID = f.UserId,
IsDeleted = f.IsDeleted,
ModifiedOn = f.ModifiedOn,
ModifiedBy = f.ModifiedBy,
Reason = f.Reason,
})
.AsEnumerable()
.GroupBy(G => G.UserPurchaseItemID)
.ToDictionary(D => D.Key);

Filter with Left Join in Entity Framework

I have User, Contact and ContactOfUser entities in an ASP.NET Core API project. I want to filter users based on input over these tables.
My entity classes are like this:
public class User
{
public int Id { get; set; }
[MaxLength(50)]
public string Name { get; set; }
[MaxLength(50)]
public string Surname { get; set; }
[MaxLength(60)]
public string Username { get; set; }
}
public class Contact
{
public int Id { get; set; }
[MaxLength(50)]
public string Value{ get; set; }
}
public class ContactOfUser
{
public int Id { get; set; }
public int UserId { get; set; }
[ForeignKey(nameof(UserId))]
public User User { get; set; }
public int ContactId { get; set; }
}
I want to get filtered users based on this FilterModel object:
public class FilterModel
{
public string Name { get; set; }
public string Surname { get; set; }
public string Username { get; set; }
public List<int> ContactId { get; set; }
}
How can I make this filtering process in Entity Framework with Linq methods considering that don't apply special filter data when that data will be accepted as null?
I did something like that method but it is not properly working:
List<User> GetFilteredUsers(FilterModel filter)
{
var query1 = dbContext.Users
.Where(u => u.Name.Contains(filter.Name ?? string.Empty) &&
u.Surname.Contains(filter.Surname ?? string.Empty) &&
u.Username.Contains(filter.Username ?? string.Empty));
var query2 = from u in query1
join cu in dbContext.ContactOfUsers on u.Id equals cu.UserId
into res
from item in res.DefaultIfEmpty()
where filter.Contacts.Contains(item.ContactId)
select new InitialUserModel
{
Id = u.Id,
Name = u.Name,
Surname = u.Surname,
Username = u.Username
};
}
You can achieve it in this way
Using GroupBy to get userId and ContactIds accordingly.
var userContactIds = _dbContext.ContactOfUser.GroupBy(p => p.UserId).Select(g => new { UserId = g.UserId, ContactIds = g.Select(p => p.ContactId).ToList() });
Get the result :
var result = _dbContext.User.Select(p => new FilterModel
{
Name = p.Name, Surname = p.Surname, Username = p.Username,
ContactId = userContactIds.Where(c => p.Id == c.UserId).ToList()
});
Updated
List<User> GetFilteredUsers(FilterModel filter)
{
return (from u in _dbContext.User
join c in _dbContext.ContactOfUser on u.Id equals c.UserId
join fContactId in filter.ContactId on c.ContactId equals fContactId
where u.Name == filter.Name && u.Surname == filter.Surname && u.Username == filter.Username
select u).ToList();
}

Take function doesn't work and cannot be sent to RavenDB for query

Query Code:
var query = session.IndexQuery<App_OrgSearch.IndexResult, App_OrgSearch>();
var organizationUnitResults = query.Statistics(out stats)
.Skip(0)
.Take(5)
.AsProjection<Org>().ToList();
public static IRavenQueryable<TResult> IndexQuery<TResult, TIndex>(this IDocumentSession session)
where TIndex : AbstractIndexCreationTask, new()
{
return session.Query<TResult, TIndex>();
}
App_OrgSearch is the index I defined as below:
public class App_OrgSearch : AbstractIndexCreationTask<Org, App_OrgSearch.IndexResult>
{
public class IndexResult
{
public string Id { get; set; }
public string BusinessName { get; set; }
public string ShortName { get; set; }
public IList<string> Names { get; set; }
public List<string> PhoneNumbers { get; set; }
public List<OrganizationUnitPhone> OrganizationUnitPhones { get; set; }
}
public App_OrganizationUnitSearch()
{
Map = docs => from doc in docs
select new
{
Id = doc.Id,
Names = new List<string>
{
doc.BusinessName,
doc.ShortName,
},
BusinessName = doc.BusinessName,
ShortName = doc.ShortName,
PhoneNumbers = doc.OrganizationUnitPhones.Where(x => x != null && x.Phone != null).Select(x => x.Phone.Number),
};
Indexes.Add(x => x.Names, FieldIndexing.Analyzed);
}
}
I have 27 records in database. I want to take 5, but after query, all 27 records are returned. Why does Take function not work?
Your sample code seems wrong.
var query = session.IndexQuery<App_OrgSearch.IndexResult, App_OrgSearch>();
var organizationUnitResults = organizationUnitsQuery.Statistics(out stats)
What is organizationUnitsQuery ? You have the query as query, but there is no IndexQuery method on the session

ASP.NET MVC query lambda expression

Hello I have problem in one query. Why it's always return no value.
public List<UserDetail> userSearchModel(UserSearchModel searchModel)
{
string userid = User.Identity.GetUserId();
var user = _dbContext.UserDetails.Where(x => x.Id == userid);
var result = _dbContext.UserDetails.Except(user).ToList().AsQueryable();
if (searchModel != null)
{
if (searchModel.LanguageId.Count() != 0)
{
List<UserDetailLanguage> usrDetails = new List<UserDetailLanguage>();
foreach (var item in searchModel.LanguageId)
{
var details = _dbContext.UserDetailLanguages.Where(x => x.LanguageId == item).ToList();
foreach (var item2 in details)
{
usrDetails.Add(item2);
}
}
result = result.Where(x => x.UserDetailLanguages == usrDetails);
}
}
return result.ToList();
}
I want to get results which are the same in usrDetails list and in result.UserDetailLanguages.
In result.UserDetailLanguages I have record equals to record in usrDetails but this not want retrieve.
Here is my model:
public class UserDetail
{
public UserDetail()
{
this.UserDetailLanguages = new HashSet<UserDetailLanguage>();
}
[Key, ForeignKey("User")]
public string Id { get; set; }
public DateTime Birthday { get; set; }
public string Sex { get; set; }
public string Country { get; set; }
public string About { get; set; }
[NotMapped]
public int Age { get { return DateTime.Now.Year - Birthday.Year; } }
public virtual ApplicationUser User { get; set; }
public virtual ICollection<UserDetailLanguage> UserDetailLanguages { get; set; }
}
public class UserDetailLanguage
{
public Int32 Id { get; set; }
public virtual UserDetail UserDetail { get; set; }
public string UserDetailId { get; set; }
public virtual Language Language { get; set; }
public Int32 LanguageId { get; set; }
public Boolean IsKnown { get; set; }
public static implicit operator List<object>(UserDetailLanguage v)
{
throw new NotImplementedException();
}
}
public class Language
{
public Language()
{
this.UserDetailLanguages = new HashSet<UserDetailLanguage>();
}
public int Id { get; set; }
public string Value { get; set; }
public string Name { get; set; }
public virtual ICollection<UserDetailLanguage> UserDetailLanguages { get; set; }
}
What I'm doing wrong?
If you want to see if your value is in a list you use the Contains function of the list -- like this:
result = result.Where(x => usrDetails.Contains(x.UserDetailLanguage));
If you want to see if there are any items in both lists you can use intersection like this:
result = result.Where(x => usrDetails.Intersect(x.UserDetailLanguage).Count() > 0);
Looks like you are checking equality between lists in following code
result = result.Where(x => x.UserDetailLanguages == usrDetails);
This might not work, to check equality for lists you can use something like
Enumerable.SequenceEqual(FirstList.OrderBy(fList => fList),
SecondList.OrderBy(sList => sList))

Can I use an index as the source of an index in RavenDB

I'm trying to define an index in RavenDb that uses the output of another index as it's input but I can't get it to work.
I have the following entities & indexes defined.
SquadIndex produces the result I expect it to do but SquadSizeIndex doesn't even seem to execute.
Have I done something wrong or is this not supported?
class Country
{
public string Id { get; private set; }
public string Name { get; set; }
}
class Player
{
public string Id { get; private set; }
public string Name { get; set; }
public string CountryId { get; set; }
}
class Reference
{
public string Id { get; set; }
public string Name { get; set; }
}
class SquadIndex : AbstractIndexCreationTask<Player, SquadIndex.Result>
{
public SquadIndex()
{
Map = players => from player in players
let country = LoadDocument<Country>(player.CountryId)
select new Result
{
Country = new Reference
{
Id = country.Id,
Name = country.Name
},
Players = new[]
{
new Reference
{
Id = player.Id,
Name = player.Name
}
}
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
Players = g.SelectMany(x => x.Players)
};
}
internal class Result
{
public Reference Country { get; set; }
public IEnumerable<Reference> Players { get; set; }
}
}
class SquadSizeIndex : AbstractIndexCreationTask<SquadIndex.Result, SquadSizeIndex.Result>
{
public SquadSizeIndex()
{
Map = squads => from squad in squads
select new Result
{
Country = squad.Country,
PlayerCount = squad.Players.Count()
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
PlayerCount = g.Sum(x => x.PlayerCount)
};
}
internal class Result
{
public Reference Country { get; set; }
public int PlayerCount { get; set; }
}
}
No, you can't. The output of indexes are not documents to be indexed.
You can use the scripted index results to chain indexes, but that isn't trivial.