MongoDB.net driver - project into different class - c#

When trying to project a typed document into a different class, I get the following: Could not find a member match for constructor parameter "origItem" on type "NewItem" in the expression tree new NewItem({document}, 1021).
A simplified example of the classes are as follows:
public class OriginalItem
{
public ObjectId Id { get; set; }
public string Name { get; set; }
public IList<double> Data { get; set; }
public OriginalItem() { }
}
public class NewItem
{
public ObjectId Id { get; set; }
public string Name { get; set; }
public double Value { get; set; }
public NewItem() { }
public NewItem( OriginalItem origItem, int targetIdx )
{
Id = origItem.Id;
Name = origItem.Name;
Value = origItem.Data[targetIdx];
}
}
An example of where the issue occurs is as follows:
IList<ObjectId> ids; // list of OriginalItem Ids to get
IMongoCollection<OriginalItem> collection = _db.GetCollection<OriginalItem>("items");
int targetIdx = 50;
IList<NewItem> newItems = await collection.Aggregate()
.Match( item => ids.Contains( item.Id ) )
.Project( origItem => new NewItem( origItem, targetIdx ) )
.ToListAsync();
I looked around and it seems like my only option would be project & transform the the origItem into a BsonDocument, and deserialize that into a NewItem. I've also tested changing new NewItem( origItem, targetIdx ) to new NewItem { //... } works.
I know I can simply read the item and perform the necessary transformations outside of the mongo server, but the real use case is slightly more complicated and I would like to at least figure out what I'm failing to understand.
Thank you

I have run into this issue as well. After scouring the internet for a while, I finally found that using the Builder's Project.Expression method was the only way that worked. For your scenario it would result in something like the following
var project = Builders<OriginalItem>.Projection.Expression(item => new NewItem(origItem, targetInx));
IList<NewItem> newItems = await collection.Aggregate()
.Match( item => ids.Contains( item.Id ) )
.Project(project)
.ToListAsync();
I also removed all of the constructor logic, and instead had it doing straight assignments. So instead of
new NewItem(origItem, targetInx) you would end up with something like new NewItem(item.Id, item.Name, item.Data[targetIdx]). I'm not certain if this step is necessary, but if the above doesn't work then I would definitely try this as well.

Related

Using .Select to get single item from sub list, then assigning individual properties of that item to view model

I have pretty much solved this problem but I am wondering whether there is a more efficient way of doing this using Entity framework / SQL.
Essentially, what i am doing is performing a subquery to get a SINGLE item on a list of objects that are connected to a parent entity. I then want to extract only a few columns from that single entity.
The first way, which doesn't work but shows my possible thought process was to put each object into a temporary variable and then create the view:
_context.IcoInfos.Select((i) =>
{
var reward = i.SocialRewards.OrderByDescending(s => s.EndDate).FirstOrDefault();
return new IcoInfoRewardCountViewModel()
{
CampaignName = i.Name,
CurParticipants = reward.CurParticipants,
Title = reward.CustomTitle,
IsLive = reward.IsLive
};
});
The second way, which works, I am creating a temporary model which stores the single database row of the sublist result...
_context.IcoInfos.Select((i) => new
{
Reward = i.SocialRewards.OrderByDescending(s => s.EndDate).FirstOrDefault(),
IcoName = i.Name
}).Select(t => new IcoInfoRewardCountViewModel()
{
CampaignName = t.IcoName,
CurParticipants = t.Reward.CurParticipants,
Title = t.Reward.CustomTitle,
IsLive = t.Reward.IsLive
}).ToList();
My question is, is this second way the only/best way to achieve this?
Your second approach is ok but for bigger application will cause you trouble if application growth larger and you have a lot information to store in the model.
So I think you can use automapper to make your code more clean.
Example
To use autoampper I need to define a model class and DTO class that share some same properties.
public class Comment
{
public string Content { get; set; }
public virtual Comment ParentComment { get; set; }
public virtual Post Post { get; set; }
public virtual User? User { get; set; }
public CommentStatus CommentStatus { get; set; }
}
public class CommentDto
{
public int Id { get; set; }
public Guid UniqeId { get; set; }
public string Content { get; set; }
public Comment ParentComment { get; set; }
public CommentStatus CommentStatus { get; set; }
public DateTime DateCreated { get; set; }
}
I also need to define the profile class to register mapping
public class CommentProfile : Profile
{
public CommentProfile()
{
CreateMap<Comment, CommentDto>(MemberList.None).ReverseMap();
}
}
Then I will need to register into DI container in startup.cs
services.AddAutoMapper();
Then I can use like this
var comments = await _unitOfWork.Repository<Comment>().Query()
.Include(x => x.User)
.Include(c => c.Post)
.Select(x => new CommentViewModel
{
Comment = _mapper.Map<Comment, CommentDto>(x),
})
.ToListAsync();
It will make the code more clear and I dont have to do manual mapping

Prevent SELECT N+1 issue in Entity Framework query using Joins

I'm trying to query something from an indirectly related entity into a single-purpose view model. Here's a repro of my entities:
public class Team {
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<Member> Members { get; set; }
}
public class Member {
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class Pet {
[Key]
public int Id { get; set; }
public string Name { get; set; }
public Member Member { get; set; }
}
Each class is in a DbSet<T> in my database context.
This is the view model I want to construct from a query:
public class PetViewModel {
public string Name { get; set; }
public string TeamItIndirectlyBelongsTo { get; set; }
}
I do so with this query:
public PetViewModel[] QueryPetViewModel_1(string pattern) {
using (var context = new MyDbContext(connectionString)) {
return context.Pets
.Where(p => p.Name.Contains(pattern))
.ToArray()
.Select(p => new PetViewModel {
Name = p.Name,
TeamItIndirectlyBelongsTo = "TODO",
})
.ToArray();
}
}
But obviously there's still a "TODO" in there.
Gotcha: I can not change the entities at this moment, so I can't just include a List<Pet> property or a Team property on Member to help out. I want to fix things inside the query at the moment.
Here's my current solution:
public PetViewModel[] QueryPetViewModel_2(string pattern) {
using (var context = new MyDbContext(connectionString)) {
var petInfos = context.Pets
.Where(p => p.Name.Contains(pattern))
.Join(context.Members,
p => p.Member.Id,
m => m.Id,
(p, m) => new { Pet = p, Member = m }
)
.ToArray();
var result = new List<PetViewModel>();
foreach (var info in petInfos) {
var team = context.Teams
.SingleOrDefault(t => t.Members.Any(m => m.Id == info.Member.Id));
result.Add(new PetViewModel {
Name = info.Pet.Name,
TeamItIndirectlyBelongsTo = team?.Name,
});
}
return result.ToArray();
}
}
However, this has a "SELECT N+1" issue in there.
Is there a way to create just one EF query to get the desired result, without changing the entities?
PS. If you prefer a "plug and play" repro containing the above, see this gist.
You've made the things quite harder by not providing the necessary navigation properties, which as #Evk mentioned in the comments do not affect your database structure, but allow EF to supply the necessary joins when you write something like pet.Member.Team.Name (what you need here).
The additional problem with your model is that you don't have a navigation path neither from Team to Pet nor from Pet to Team since the "joining" entity Member has no navigation properties.
Still it's possible to get the information needed with a single query in some not so intuitive way by using the existing navigation properties and unusual join operator like this:
var result = (
from team in context.Teams
from member in team.Members
join pet in context.Pets on member.Id equals pet.Member.Id
where pet.Name.Contains(pattern)
select new PetViewModel
{
Name = pet.Name,
TeamItIndirectlyBelongsTo = team.Name
}).ToArray();

Trying to get a list of properties dynamically where count > 0

I have a model like so
public class UserModel
{
List<UserModel> users
}
public class UserModel
{
public List<UserSomeObj> userSomeObj { get; set; }
public List<UserSomeOtherObj> userSomeOtherObj { get; set; }
}
public class UserSomeObj
{
public int someIntProperty { get; set; }
public string someStringProperty { get; set; }
}
public class UserSomeOtherObj
{
public int someIntProperty { get; set; }
public string someStringProperty { get; set; }
}
Each UserModel class List is comprised of several other class Lists.
I am referencing them dynamically like so by looping over a list of targeted properties.
to get a list of properties matching the 'prop' variable;
var props = MethodToGetTargetedProperties();
// props example content would be a list of strings like so "UserSomeObj", "UserSomeOtherObj"
foreach (var prop in props)
{
var results = users.Select(x => x.GetPropertyValue(prop)).ToList();
//results contain lists of prop where count == 0 and i dont want them
}
what I am trying to do is reduce the results where count of the lists targeted is greater than 0 .... problem is that I can't find the correct order/syntax to get it to work.
Thanks
As List<T> implements the non-generic ICollection interface, you can cast to that:
var results = users.Select(x => x.GetPropertyValue(prop))
.Cast<ICollection>()
.Where(list => list.Count > 0)
.ToList();
You could do the cast within the Where if you want, although I prefer the above:
var results = users.Select(x => x.GetPropertyValue(prop))
.Where(list => ((ICollection) list).Count > 0)
.ToList();
Thanks ..... in reviewing your reply I was able to do the following
var results = users.Select(x => x.GetPropertyValue(prop))
.Cast<IEnumerable<object>>().Where(y => y.Count() > 0).ToList();
Then after looking at your answer more and trying to understand all its facets, I wondered if I could do it all in one line. The two List classes in UserModel have 2 common properties (idSomething and idSomeOtherThing) and since I will be combining them and doing a Distinct, i thought, hmm, one liner might be possible.

Remove Item From Object SubList (LINQ)

I have an object that looks like this :
public class Consortium
{
public string Id { get; set; }
[JsonConverter(typeof(EnumDescriptionConverter))]
public SourceType Type { get; set; }
public List<UserLibrary> Branches { get; set; }
}
Each Consortium has a list of UserLibrary's associated with it, and that class looks like this :
public class UserLibrary
{
public string LibraryId { get; set; }
public string RetailerId {get; set;}
public string UserId { get; set; }
public string Name { get; set; }
public DateTime CreatedAt { get; set; }
}
I have a method which will allow the user to remove a library from one of their consortium (note: There may be many branches associated to the consortium).
However, I'm only being supplied a LibraryId, so I'm forced to traverse their list of Consortium, find which one contains the given id, then iterate over the branches and remove the one that matches the id.
Here is how I'm currently accomplishing this :
// Get the current list of consortiums
var user = _mediator.Send(new GetUserProfileCommand { UserProfileId = _principle.UserProfileId });
var userConsortia = user.SavedConsortia;
// the consortium to remove the library from
var branchToRemove = _libraryService.GetLibrary(id);
var consortRemove = new UserConsortium();
foreach (var userConsortium in userConsortia)
{
if (userConsortium.Branches.FirstOrDefault(c => string.Equals(c.LibraryId, id, StringComparison.OrdinalIgnoreCase)) != null)
{
consortRemove = userConsortium;
}
}
// if the consortium id is null, something is f*
if (consortRemove.Id == null)
{
return new JsonDotNetResult(HttpStatusCode.BadRequest);
}
// first remove the consortia
userConsortia.Remove(consortRemove);
// remove the branch from the consortium
consortRemove.Branches.RemoveAll(ul => string.Equals(ul.LibraryId, id, StringComparison.OrdinalIgnoreCase));
// add it back in without the branch
userConsortia.Add(consortRemove);
Question :
Is there a LINQ expression I'm missing here that can help me consolidate this logic, or is there a better way of doing this?
Yes, there are a few approaches you can take depending on taste. The easiest way to simplify what you've got would be this:
var branchToRemove = _libraryService.GetLibrary(id);
// .Single() will throw an exception unless there is one and only one match.
var consortRemove = userConsortia.Single(
c => c.Branches.Any(
b => string.Equals(b.LibraryId, id, StringComparison.OrdinalIgnoreCase));
// remove the consortia
userConsortia.Remove(consortRemove);
Why not something like this? It looks to me from your code that you want to remove the targeted "removal branch" from all consortiums in you collection.
foreach (UserConsortium userConsortium in userConsortia)
{
userConsortium.Branches.RemoveAll(c => string.Equals(c.LibraryId, id, StringComparison.OrdinalIgnoreCase));
}

EF 5.0 Code First adding new children to existing parent

Overview:
I am working on an MVC ASP.Net app using Code First and EF 5.0. I have two tables: Scripts and ScriptItems. A Script can have multiple ScriptItems. ScriptItems are hierarchical as well. ScriptItems can optionally belong to each other, however this relationship is only 1 level deep thankfully. This relationship is indicated by ScriptItem.ParentId.
The problem:
Creating a new Script entry with ScriptItems works just fine. The problem arises when I try to add ScriptItems to an existing Script. If I try to add ScriptItems that do NOT have a ParentId, everything works fine. As soon as I try to add ScriptItems that do have a ParentId, I receive a FK violation exception.
Details:
Script class:
public class Script
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Type { get; set; }
[ForeignKey("ProcessorId")]
public Processor Processor { get; set; }
public int ProcessorId { get; set; }
public string Owner { get; set; }
public DateTime Created { get; set; }
public bool Public { get; set; }
public List<ScriptItem> Items { get; set; }
public List<ScriptRun> Runs { get; set; }
public Script()
{
Items = new List<ScriptItem>();
Created = DateTime.Now;
}
}
ScriptItem class: (Truncated for brevity)
public class ScriptItem
{
[Key]
public int Id { get; set; }
[ForeignKey("ParentId")]
public ScriptItem Parent { get; set; }
public int? ParentId { get; set; }
public Script Script { get; set; }
[ForeignKey("Script")]
public int ScriptId { get; set; }
The function that adds script items:
private void addToScript(ScriptModel model, List<int> ids)
{
Script script = scriptRepository.GetScriptWithItems(model.ScriptId);
List<History> historyItems = historyRespository.History.Where(h => ids.Contains(h.Id)).ToList();
ScriptItem lastScriptItem = script.Items.OrderByDescending(item => item.SortIndex).FirstOrDefault();
int topSortIndex = lastScriptItem == null ? 0 : lastScriptItem.SortIndex;
if (script != null)
{
List<ScriptItem> newItems = new List<ScriptItem>();
Mapper.CreateMap<History, ScriptItem>();
foreach (History h in historyItems)
{
ScriptItem scriptItem = new ScriptItem();
Mapper.Map(h, scriptItem); //Populate new ScriptItem from History entry
scriptItem.SortIndex = ++topSortIndex;
scriptItem.ScriptId = model.ScriptId;
scriptItem.Script = script;
//Only add an entry if it is NOT the parent of another entry. Otherwise, EF will duplicate the Parent entries
if (!historyItems.Any(his => his.ParentId == h.Id))
newItems.Add(scriptItem);
}
scriptRepository.AddScriptItems(newItems);
}
}
And finally the scriptRepository.AddScripItems():
public void AddScriptItems(List<ScriptItem> items)
{
items.ForEach(item => context.Entry(item).State = System.Data.EntityState.Added);
context.SaveChanges();
}
Consider the scenario where I add two ScriptItems A and B to an existing script. A is the parent of B. When I run a SQL Server trace, I see that an attempt is made to insert the parent record A, but with a ScriptId of 0 hence the FK violation exception. No clue why ScriptId is 0. ScriptId is set properly on the ScriptItems, I verified this with the debugger.
I did not include the function that inserts new Scripts and Items, because it is very similar to the addToScript function above. And it works fine. But if someone wants to see it, I can add that too.
Someone smarter than me have any ideas? Thanks!
Not sure what causes this. But I think it may help to add new ScriptItem to script.Items in stead of setting their owner script, i.e. replace
scriptItem.ScriptId = model.ScriptId;
scriptItem.Script = script;
by
script.Items.Add(scriptItem);
Another advantage is that you don't have to change their state manually anymore: the change tracker knows enough when new items are added to a tracked collection. I even wonder if doing this was necessary in your script, because setting Script should also have been enough afaik.
Maybe setting script and ScriptId and changing the state interfered too much with EF's own logic and put it off track.
Thanks to Gert Arnold for pointing me in the right direction. The issue ultimately stemmed from how I was building my ScriptItems via AutoMapper. I cant explain what the exact issue is, but here is my revised working code in case anyone finds it useful.
private void addToScript(ScriptModel model, List<int> ids)
{
Script script = scriptRepository.GetScriptWithItems(model.ScriptId);
List<History> historyItems = historyRespository.History.Where(h => ids.Contains(h.Id)).ToList();
ScriptItem lastScriptItem = script.Items.OrderByDescending(item => item.SortIndex).FirstOrDefault();
int topSortIndex = lastScriptItem == null ? 0 : lastScriptItem.SortIndex;
if (script != null)
{
Mapper.CreateMap<History, ScriptItem>();
List<ScriptItem> Parents = new List<ScriptItem>();
List<History> SourceParents = historyItems
.Where(h => historyItems.Any(h2 => h2.ParentId == h.Id)).ToList();
SourceParents.Each(h =>
{
ScriptItem parent = new ScriptItem();
Mapper.Map(h, parent);
parent.Script = script;
Parents.Add(parent);
});
historyItems.Except(SourceParents).Each(h =>
{
ScriptItem child = new ScriptItem();
Mapper.Map(h, child);
child.Script = script;
if (child.ParentId.HasValue)
child.Parent = Parents.SingleOrDefault(p => p.Id == child.ParentId);
script.Items.Add(child);
});
//Todo: Get sortIndex "sorted" out
scriptRepository.SaveScript(script);
}
}
scriptRepository.SaveScript simply sets the script's state to modified and calls SaveChanges().

Categories

Resources