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));
}
Related
I have an application that generates email notifications when a record changes that includes the previous and current values of the field. So far, I've been relying on EF change tracking to determine which fields have changed and what the original and new values are. This works well for normal record properties. However, for ID fields functioning as a foreign key to another record, while I can easily identify when the ID has changed, I am having trouble taking that Id and getting a field from the dependent record in a generic manner. Each of the relevant dependent fields implement a common interface, and I'd be looking to get the same field for each field.
Here's an example of the data structures:
public class PrimaryRecord {
public int Id { get; set; }
public string SelfProp1 { get; set; }
public int DepRec1Id { get; set; }
public DepRec1 DepRec1 { get; set; }
public int DepRec2Id { get; set; }
public DepRec2 DepRec2 { get; set; }
}
public interface ISetupRecord {
public int Id { get; set; }
public string Value { get; set; }
public string Description { get; set; } // what I want to display
}
public class DepRec1 : ISetupRecord {
}
public class DepRec2 : ISetupRecord {
}
From EF change tracking, I know that, say, DepRec1Id has changed from 1 to 2, and I want to be able to display the Description field from DepRec1 (which implements ISetupRecord). Similarly, if DepRec2Id changed from 3 to 4, I'd like to display the Description field from DepRec2 for each of those two Id values.
So far, I've been going down a reflection route, but am having trouble executing the appropriate LINQ extension methods. I may just need a hint there or perhaps a better way to approach this if this isn't ideal.
Expanding on https://stackoverflow.com/a/64952442 I added this Set method to the db context:
public object Set(Type t)
{
return this.GetType().GetMethods().First(m => m.Name == "Set" && m.IsGenericMethod).MakeGenericMethod(t).Invoke(this, null);
}
which is used in this method that attempts to accomplish what I'm looking at
private static string ConvertValueToDescIfId(PropertyEntry prop, string value, DBContext dbContext)
{
if (value == null) throw new ArgumentNullException("Value cannot be null.");
if (prop == null) throw new ArgumentNullException("Prop cannot be null.");
if (!prop.Metadata.Name.EndsWith("Id")) return value;
if (!prop.Metadata.IsForeignKey()) throw new InvalidOperationException("The provided Id property is not a foreign key.");
if (!Int32.TryParse(value, out var id)) throw new ArgumentException("Value must be int parseable.");
var fk = prop.Metadata.GetContainingForeignKeys().First();
var entityType = fk.PrincipalEntityType;
var clrType = entityType.ClrType;
var dbSet = dbContext.Set(clrType);
var firstOrDefaultMethod = dbSet.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(m => m.Name == "FirstOrDefaultAsync" && m.GetParameters().Count() == 2);
// around here is where I need to do something different if continuing with this approach because firstOrDefaultMethod returns null
var whereExprParam = Expression.Parameter(clrType, "o");
var whereExprIdProp = Expression.Property(whereExprParam, "Id");
var whereExpr = Expression.Equal(whereExprIdProp, Expression.Constant(id));
var resp = firstOrDefaultMethod.Invoke(dbSet, new object[] { whereExpr });
// still more to do to get the Description field....
return value;
}
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.
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
I have an object structure which looks like this.
public class DeliveryManagerQuoteResponse
{
public string OrderId { get; set; }
public List<DeliveryManagerQuotes> Quotes { get; set; }
}
public class DeliveryManagerQuotes
{
public bool IsSuccess { get; set; }
public List<DeliveryManagerQuoteDetails> QuoteDetails { get; set; }
}
public class DeliveryManagerQuoteDetails
{
public string QuoteId { get; set; }
public int? DeliveryFee { get; set; }
}
When I have DeliveryManagerQuoteResponse populated, I'll have multiple Quotesand each Quotes will have multiple QuoteDetails. What I want is, based on DeliveryFee which in DeliveryManagerQuoteDetails, I want to pick the DeliveryManagerQuoteResponse which has a minimum delivery fee. How can I do that using LINQ?
I was thinking of getting DeliveryFee first from all Quotes and then write some code/LINQ to use that to fetch quote with a minimum fee but not sure how to write it :
public void PickDeliveryQuoteBasedOnRate(DeliveryManagerQuoteResponse deliveryManagerQuoteResponse)
{
var feeList = new List<int>();
foreach (var quote in deliveryManagerQuoteResponse.Quotes)
{
if (quote.QuoteDetails != null && quote.QuoteDetails.Any())
{
foreach (var quoteDetail in quote.QuoteDetails)
{
feeList.Add(quoteDetail.DeliveryFee ?? default(int));
}
}
}
if (feeList.Any())
{
// This will give me minimum fee
var minimumDeliveryFee = feeList.Min();
// Need LINQ to use this minimum fee to pick one quote.
}
}
Screen Shot for better understanding:
Return QuoteDetail with minimum DeliveryFee:
deliveryManagerQuoteResponse
.Quotes
.SelectMany(p=>p.QuoteDetails)
.Where(p=>p.DeliveryFee.HasValue)
.Orderby(p=>p.DeliveryFee)
.FirstOrDefault();
Return Quote With minimum Delivery in it's Detil, So transfering Quote with each QuoteDetail to used it in the last expression for return:
deliveryManagerQuoteResponse
.Quotes
.SelectMany(p=>p.QuoteDetails.Select(q=>new{Quote=p,Detail=q}))
.Where(p=>p.Detail.DeliveryFee.HasValue)
.Orderby(p=>p.Detail.DeliveryFee)
.FirstOrDefault()?.Quote;
Rather than pull the detail to the top and then sort through the multiple copies of quotes, I suggest sorting the quotes by the min of all the DeliveryFees:
var minDelivery = deliveryManagerQuoteResponse.OrderBy(
dmqr => dmqr.Quotes // foreach quote
.SelectMany(q => q.QuoteDetails // foreach QuoteDetail
.Where(qd => qd.DeliveryFee.HasValue) // that has a value
.Select(qd => qd.DeliveryFee.Value) // select the DeliveryFree
) // and flattten the result
.Min() // and then select the minimum DeliveryFree
) // sort quotes by minimum DeliveryFee
.FirstOrDefault(); // and select the first quote
Hi I'm a newbie to C# and DTO's and I'm looking for a bit of advice in writing a method. Basically I have two transfer objects, Members and Source. What I'm trying to achieve is display a list of Members from a specific Source.
The only problem is I need to be able to display Members associated with a SourceId from a SourceRef. As I dont want to pass the sensitive MemberID and SourceId so each has a reference id and thats how I will be identifying them in my API
Member Object
public class MemberObj
{
public int memId { get; set; }
public String memRef { get; set; }
public String fName { get; set; }
public String lName { get; set; }
public String email { get; set; }
public String detail { get; set; }
public int sourceId { get; set; }
}
Source Object
public class SourceObj
{
public int sourceId { get; set; }
public String sourceRef { get; set; }
}
So I would like to go to the address for example
http://..../api/Member/Source/{sourceRef}
and display the list of Members associated to the sourceId via the sourceRef
I came up with something along these lines....
public IEnumerable<MemberObj> GetMem(String code)
{
var sc = db.Sources;
var thisSrc = sc.Where(s => s.sourceRef == code).SingleOrDefault();
return db.Members.Select(s => new MemberObj
{
memId = s.memId,
firstName = s.firstName,
lastName = s.lastName,
email = s.emailAddress,
memRef = s.memRef
}).AsEnumerable().Where(s => s.sourceRef== thisSrc.sourceRef);
But this returns nothing.
The following accepts code as the sourceRef and returns the SourceID that the ref corresponds too.
From here, it simply filters all members to only the ones with the matching sourceID. (I don't have a copy of VS near me so the syntax may be out! If only Notepad++ had intelisense...)
public IEnumerable<MemberObj> GetMem(String code)
{
int soureID = db.Sources.Where(s => s.sourceRef == code).SingleOrDefault().sourceID; //I'm assuming code is the source ref??
//Insert and handle your sourceID == 0 checks here.
//...
return db.Members.Where(m => m.sourceId == sourceID);
}
This should work:
public IEnumerable<MemberObj> GetMem(String code)
{
var thisSrc = db.Sources
.Where(s => s.sourceRef == code)
.SingleOrDefault();
if(thisSrc == null)
return Enumerable.Empty<MemberObj>();
return db.Members.Where(m => m.sourceId == thisSrc.sourceId);
}
Take in account, that you should handle the case when there are more than one source by given code (SingleOrDefault throws an exception in that case.)
If you are sure that is not a case use FirstOrDefault instead.
Just building into Hamlet's answer, you could do something like this to return a DTO instead of your Member Entity:
public IEnumerable<MemberDTO> GetMem(String code)
{
//Get the source object by sourceRef
var thisSrc = db.Sources
.Where(s => s.sourceRef == code)
.SingleOrDefault();
if(thisSrc == null)
return Enumerable.Empty<MemberObj>();
//Filter Members by sourceId and map the results to a MemberDTO with less fields
var data = (from m in db.Members
where m.sourceId == thisSrc.sourceId
select new MemberDTO
{
MemberId = m.memId,
//map the rest of your DTO fields..
}).ToList();
return data;
}