I have collection of objects where each object contains other collections of objects. All I want to modify my original collection of objects in a way, that no extra memory should be allocated. Everything should happen in memory.
I am looking for some templatized Action or Func so that with the mixture of declarative and functional approach, no new collection is formed and in memory my original collection is also modified.
Following is the hierarchy of collection
public class ConsignmentAddress
{
public int ConsignmentAddressId { get; set; }
public int AddressTypeId { get; set; }
public string Street { get; set; }
public string Town { get; set; }
public string ZipCode { get; set; }
public int ConsignmentId { get; set; }
}
public class ConsignmentLine
{
public int ConsignmentLineId { get; set; }
public int PackagingId { get; set; }
public double Amount { get; set; }
public double Weight { get; set; }
public int ConsignmentId { get; set; }
public int? PackagingAidId { get; set; }
}
public class PackagingAid
{
public int PackagingAidId { get; set; }
public int ConsignmentId { get; set; }
public int PackagingId { get; set; }
public double Quantity { get; set; }
}
public class Consignment
{
public int ConsignmentId { get; set; }
public int ClientSubsidiaryId { get; set; }
public int ForwarderId { get; set; }
public int Sourcepaty { get; set; }
public int ParentId { get; set; }
public int ProductId { get; set; }
public ICollection<PackagingAid> PackagingAids { get; set; }
public ICollection<ConsignmentLine> ConsignmentLines { get; set; }
public ICollection<ConsignmentAddress> ConsignmentAddresses { get; set; }
public Consignment()
{
PackagingAids = new List<PackagingAid>();
ConsignmentLines = new List<ConsignmentLine>();
ConsignmentAddresses = new List<ConsignmentAddress>();
}
}
My intention is to write generic extension method which should operate on all kind of objects in the hierarchy I mentioned above.
public static class ConsignmentExtension
{
public static T Set<T>(this T input, Action<T> updater)
{
updater(input);
return input;
}
}
Where as my client code is which is first getting list of 10000 of objects in list and just resetting few properties of ( consignment, consignmentaddresses for corresponding consignment, consignmentlines of corresponding consignment and packagingaid of corresponding consignment)
My written Foreach approach worked fine but i want templatized and efficient approach for the same collection.
consignments.ForEach(cons =>
{
cons.ConsignmentId = -1;
cons.ConsignmentAddresses.ToList().ForEach(address =>
{
address.ConsignmentId = -1;
address.ConsignmentAddressId = -1;
});
cons.ConsignmentLines.ToList().ForEach(line =>
{
line.ConsignmentId = -1;
line.ConsignmentLineId = -1;
line.PackagingAidId = -1;
});
cons.PackagingAids.ToList().ForEach(aid =>
{
aid.ConsignmentId = -1;
aid.PackagingAidId = -1;
});
});
But I am looking for something like declarative style.
var updatedConsignments = from consignment in consignments
select consignment.Set<Consignment>(con =>
{
con.ConsignmentId = -1;
con.ConsignmentAddresses.Set<dynamic>(address => { address.ConsignmentId = -1; address.ConsignmentAddressId = -1; });
con.ConsignmentLines.Set<dynamic>(line => { line.PackagingAidId = -1; line.ConsignmentId = -1;line.ConsignmentLineId = -1; });
con.PackagingAids.Set<dynamic>(aid => { aid.ConsignmentId = -1; aid.PackagingAidId = -1; });
});
Is that possible somehow?
Thanks in advance.
This could be written as
public static class ConsignmentExtension
{
public static IEnumerable<dynamic> SetEach(this IEnumerable<dynamic> input, Action<dynamic> updater)
{
foreach (var item in input)
{
updater(item);
}
return input;
}
}
Usage:
var addresses = new []
{
new ConsignmentAddress()
};
addresses.SetEach(a => a.ConsignmentId = -1);
var packages = new []
{
new PackagingAid()
};
packages.SetEach(p => p.PackagingAidId = -1);
Related
I have a List<TerminalLink> from which I want to retrieve a subset of. See the class structure below.
The subset is defined by a "map" which identifies which ChannelLink to select in any given scenario. Multiple ChannelLinks may be required per scenario, perhaps from the same ChannelGroup, perhaps from different ChannelGroups on the same TerminalLink, and perhaps from multiple TerminalLinks in my original in memory list.
The ChannelLink can only be select if it's ChannelGroupLink and TerminalLink match too (they form part of the index, I guess).
I have tried various Linq and foreach approaches, but I'm running into tens of lines of code, which is ugly and difficult to follow. I'm sure there is an elegant approach.
How do I return a pruned List<TerminalLink> containing just the data that matches my map (i.e. containing only TerminalLinks and ChannelGroupLinks with required ChannelLinks)?
Class structure:
public class TerminalLink
{
public string TerminalName { get; set; }
public string TerminalType { get; set; }
public ChannelGroupLink[] ChannelGroups { get; set; }
}
public class ChannelGroupLink
{
public string GroupName { get; set; }
public int ItemType { get; set; }
public int SubType { get; set; }
public ChannelLink[] Channels { get; set; }
}
public class ChannelLink
{
public string ChannelName { get; set; }
public string PathName { get; set; }
public string DataType { get; set; }
public bool IsOutput { get; set; }
}
EDIT to include the latest attempt:
public static List<TerminalLink> GetCompatibleChannelLinks(List<TerminalLinkMap> terminalLinkMaps, List<TerminalLink> asBuiltList)
{
List<TerminalLink> matching = new List<TerminalLink>();
var terminalsWithSomeMatchingChannelGroups = from terminal in asBuiltList
from tlm in terminalLinkMaps
where terminal.TerminalType == tlm.ItemSubTypeName
from cgsMaps in tlm.ChannelGroupsToLink
from cgs in terminal.ChannelGroups
where cgsMaps.IsLinkMatch(cgs.GroupName, cgs.ItemType, cgs.SubType)
select (
link: new TerminalLink()
{
TerminalName = terminal.TerminalName,
TerminalType = terminal.TerminalType
},
original: terminal, map: tlm
);
foreach (var possibleLink in terminalsWithSomeMatchingChannelGroups)
{
var linkCandidate = possibleLink.link;
var originalData = possibleLink.original;
var map = possibleLink.map;
foreach (var cgs in originalData.ChannelGroups)
{
foreach (var channel in cgs.Channels)
{
// WIP
}
}
}
}
EDIT 2: inclusion of TeminalLinkMap
public class TerminalLinkMap
{
public string TerminalTypeName { get; set; }
public string ItemSubTypeName { get; set; } // Match on this.
public ChannelGroupToLink[] ChannelGroupsToLink { get; set; }
}
public class ChannelGroupToLink
{
public string GroupName { get; set; } // regex match is ok.
public int ItemType { get; set; }
public int SubType { get; set; }
public ChannelToLink[] Channels { get; set; }
public ChannelGroupToLink GetClone() => GetClone(this);
public bool IsLinkMatch(string groupName, int itemType, int subType)
{
return
GroupName == groupName
&& ItemType == itemType
&& SubType == subType;
}
}
public class ChannelToLink
{
public int ItemType { get; set; }
public string DataType { get; set; }
public int DataSizeInBits { get; set; }
public bool IsOutput { get; set; }
public ChannelToLink GetClone() => GetClone(this);
public bool IsLinkMatch(string dataType, bool isOutput)
{
return
DataType == dataType
&& IsOutput == isOutput;
}
public static ChannelToLink GetClone(ChannelToLink original) => new ChannelToLink()
{
ItemType = original.ItemType,
DataType = original.DataType,
DataSizeInBits = original.DataSizeInBits,
IsOutput = original.IsOutput
};
}
There is no need for a join.
Try following:
class Program
{
static void Main(string[] args)
{
List<TerminalLink> asBuiltList = new List<TerminalLink>();
List<TerminalLink> links = asBuiltList
.Select(x => new { TerminalLink = x, ChannelGroups = x.ChannelGroups.Where(y => y.IsLinkMatch("Apple", 123, 456)) })
.Where(x => x.ChannelGroups.Count() > 0)
.Select(x => new TerminalLink() { TerminalName = x.TerminalLink.TerminalName, TerminalType = x.TerminalLink.TerminalType, ChannelGroups = x.ChannelGroups.ToArray()})
.ToList();
}
}
public class TerminalLink
{
public string TerminalName { get; set; }
public string TerminalType { get; set; }
public ChannelGroupLink[] ChannelGroups { get; set; }
}
public class ChannelGroupLink
{
public string GroupName { get; set; }
public int ItemType { get; set; }
public int SubType { get; set; }
public ChannelLink[] Channels { get; set; }
public Boolean IsLinkMatch(string groupName, int itemType, int subType)
{
return (this.GroupName == groupName) && ( this.ItemType == itemType) && (this.SubType == subType);
}
}
public class ChannelLink
{
public string ChannelName { get; set; }
public string PathName { get; set; }
public string DataType { get; set; }
public bool IsOutput { get; set; }
}
}
I am attempting to retrieve information using the Steam API. I created the classes offerStuff and itemsClass, offerStuff contains public List<itemsClass> items { get; set; }, however, whenever I attempt to access this list through os.items.Add(item), my program crashes with NullReferenceException. Is there some declaration I am missing? If so, how would I declare it so I can access it without the exception?
public static List<offerStuff> pollOffers()
{
using (dynamic tradeOffers = WebAPI.GetInterface("IEconService", config.API_Key))
{
List<offerStuff> OfferList = new List<offerStuff>();
offerStuff os = new offerStuff();
KeyValue kvOffers = tradeOffers.GetTradeOffers(get_received_offers: 1);//, active_only: 1
foreach (KeyValue kv in kvOffers["trade_offers_received"].Children)
{
os.tradeofferid = kv["tradeofferid"].AsInteger(); ;
os.accountid_other = Convert.ToUInt64(kv["accountid_other"].AsInteger());
os.message = kv["message"].AsString();
os.trade_offer_state = kv["trade_offer_state"].AsInteger();
foreach (KeyValue kv2 in kv["items_to_receive"].Children)
{
itemsClass item = new itemsClass();
item.appid = (kv2["appid"].AsInteger());
item.assetid = kv2["assetid"].AsInteger();
item.classid = kv2["classid"].AsInteger();
item.instanceid = kv2["instanceid"].AsInteger();
item.amount = kv2["amount"].AsInteger();
item.missing = kv2["missing"].AsInteger();
os.items.Add(item);
}
os.is_our_offer = kv["is_our_offer"].AsBoolean();
os.time_created = kv["time_created"].AsInteger();
os.time_updated = kv["time_updated"].AsInteger();
OfferList.Add(os);
}
return OfferList;
}
}
}
public class offerStuff
{
public int tradeofferid { get; set; }
public SteamID accountid_other { get; set; }
public string message { get; set; }
public int trade_offer_state { get; set; }
public List<itemsClass> items { get; set; }
public bool is_our_offer { get; set; }
public int time_created { get; set; }
public int time_updated { get; set; }
}
public class itemsClass
{
public int appid { get; set; }
public int assetid { get; set; }
public int classid { get; set; }
public int instanceid { get; set; }
public int amount { get; set; }
public int missing { get; set; }
}
The problem is probably that you're not initializing the collection items. You could do it on your contructor like this
public offerStuff()
{
items = new List<itemsClass>();
}
I'm trying to build a filtering system. Say you have these models.
public class FilterVM
{
public string ContentRating { get; set; }
public List<FilterChars> FilterChars { get; set; }
public List<FilterCats> FilterCats { get; set; }
public List<FilterTags> FilterTags { get; set; }
}
public class FilterChars
{
public int CharID { get; set; }
public int CharVal { get; set; }
}
public class Book
{
public int BookID { get; set; }
public ICollection<BookCharacteristic> BookCharacteristic { get; set; }
}
public class BookCharacteristic
{
public int ID { get; set; }
public int BookID { get; set; }
public int CharacteristicID { get; set; }
public Book Book { get; set; }
public int Value { get; set; }
public Characteristic Characteristic { get; set; }
}
So a form gets posted and there's a FilterVM with a list of FilterChars now I need to find the books that have the characteristic (CharID) and have a value greater than the submitted value.
This is what I am trying but I can't figure out the right way to write the query.
List<FilterChars> fc = new List<FilterChars>();
foreach (var filter in f.FilterChars.Where(x => x.CharVal > 0)) {
fc.Add(filter);
}
var books = db.Books
.Where(t => fc.Select(y => y.CharID)
.Contains(t.BookCharacteristic
.Any(u => u.CharacteristicID)
)
&& //if there's a match, use the matched BookCharacteristic and Value??
//not sure how to do that
);
public class BookCharacteristicEqualityComparer : IEqualityComparer<BookCharacteristic>
{
public bool Equals(BookCharacteristic x, BookCharacteristic y)
{
return x.CharacteristicID == y.CharacteristicID && x.Value == y.Value;
}
public int GetHashCode(BookCharacteristic obj)
{
return obj.CharacteristicID * obj.Value;
}
}
That's for comparing BookCharacteristics with id and value
var books = db.Books
.Where((book) =>
{
foreach (var filterChar in fc)
{
if (!book.BookCharacteristic.Contains(new BookCharacteristic() {CharacteristicID = filterChar.CharID, Value = filterChar.CharVal},
new BookCharacteristicEqualityComparer()))
return false;
}
return true;
});
find all books that contains all characteristics and values from the filter
hope that helped :)
Edit:
Here is my code, compiles and runs fine (i have no results because i have no data, but no errors )
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var fc = new List<FilterChars>();
var dbBooks = new List<Book>();
var books = dbBooks
.Where((book) =>
{
foreach (var filterChar in fc)
{
if (!book.BookCharacteristic.Contains(new BookCharacteristic() { CharacteristicID = filterChar.CharID, Value = filterChar.CharVal },
new BookCharacteristicEqualityComparer()))
return false;
}
return true;
});
}
}
public class FilterVM
{
public string ContentRating { get; set; }
public List<FilterChars> FilterChars { get; set; }
}
public class FilterChars
{
public int CharID { get; set; }
public int CharVal { get; set; }
}
public class Book
{
public int BookID { get; set; }
public ICollection<BookCharacteristic> BookCharacteristic { get; set; }
}
public class BookCharacteristic
{
public int ID { get; set; }
public int BookID { get; set; }
public int CharacteristicID { get; set; }
public Book Book { get; set; }
public int Value { get; set; }
}
public class BookCharacteristicEqualityComparer : IEqualityComparer<BookCharacteristic>
{
public bool Equals(BookCharacteristic x, BookCharacteristic y)
{
return x.CharacteristicID == y.CharacteristicID && x.Value == y.Value;
}
public int GetHashCode(BookCharacteristic obj)
{
return obj.CharacteristicID * obj.Value;
}
}
I want to use LINQ to pass data from one custom collection to another. Its complicated because the collection has 2 sub collections.
Want to copy data to:
public class Quote
{
public int Id { get; set; }
public string Type { get; set; }
public virtual ICollection<Rate> Rates { get; set; }
}
public class Rate
{
public int Id { get; set; }
public virtual ICollection<Option> Options { get; set; }
}
public class Option
{
public int Id { get; set; }
public decimal Price { get; set; }
}
from:
public class Quote
{
public int QuoteId { get; set; }
public string Type { get; set; }
public string Destination { get; set; }
public List<RateSet> RateSets { get; set; }
}
public class RateSet
{
public int Id { get; set; }
public decimal ValueMin { get; set; }
public decimal ValueMax { get; set; }
public List<Option> Options { get; set; }
}
public class Option
{
public int Id { get; set; }
public string Name { get; set; }
public decimal? Price { get; set; }
}
I was getting somewhere with this but keeping hitting problems...
newQuotes = Quotes
.Select(x => new Quote() {
Id = x.QuoteId,
Rates = x.RateSets.Select( y => new Rate() {
Id = y.Id,
Options = y.Options.Select(z => new Option() {
Id = z.Id,
Price = z.Price
}).ToList(),....
to
Compiled without any errors
// to
public class Quote2
{
public int Id { get; set; }
public string Type { get; set; }
public virtual ICollection<Rate> Rates { get; set; }
}
public class Rate
{
public int Id { get; set; }
public virtual ICollection<Option2> Options { get; set; }
}
public class Option2
{
public int Id { get; set; }
public decimal Price { get; set; }
}
// from
public class Quote1
{
public int QuoteId { get; set; }
public string Type { get; set; }
public string Destination { get; set; }
public List<RateSet> RateSets { get; set; }
}
public class RateSet
{
public int Id { get; set; }
public decimal ValueMin { get; set; }
public decimal ValueMax { get; set; }
public List<Option1> Options { get; set; }
}
public class Option1
{
public int Id { get; set; }
public string Name { get; set; }
public decimal? Price { get; set; }
}
void Main()
{
var Quotes = new List<Quote1>();
var newQuotes = Quotes
.Select(x => new Quote2 {
Id = x.QuoteId,
Rates = x.RateSets == null ? null : x.RateSets.Select( y => new Rate {
Id = y.Id,
Options = y.Options == null ? null : y.Options.Select(z => new Option2 {
Id = z.Id,
Price = z.Price.Value
}).ToList()}).ToList()}).ToList();
}
I would make it a bit more modular:
newQuotes = Quotes.Select(x => new Quote
{
ID = x.QuoteID,
Type = x.Type,
Rates = ConvertRates(x.RateSets)
});
ConvertRates would use the same approach to create its sub objects and could either be a method or a Func:
ICollection<Rate> ConvertRates(IEnumerable<RateSet> oldRates)
{
return oldRates.Select(x => new Rate
{
ID = x.ID,
Options = ConvertOptions(x.Options)
}).ToList();
}
Basically, this is the same approach you used, just split up and readable.
I think what you need to do is define casting between each two corresponding classes, then cast one list into the other.
A simpler way may be to create methods in each class that would convert itself to the other type. Or if you don't want that kind of coupling, create a factory class that will do the conversion for you, one item at a time. Then use link to loop through and convert each item.
Like so:
public class Quote
{
public int Id { get; set; }
public string Type { get; set; }
public virtual ICollection<Rate> Rates { get; set; }
public static Quote FromData(Data.Quote input){
if (input == null) return null;
Quote output = new Quote()
{
Id = input.QuoteId,
Type = input.Type
};
output.Rates = (from i in input.RateSets
select Rate.FromData(i)).ToList();
}
}
public class Rate
{
public int Id { get; set; }
public virtual ICollection<Option> Options { get; set; }
public static Rate FromData(Data.RateSet input)
{
if (input == null) return null;
Rate output = new Rate()
{
Id = input.Id
};
output.Options = (from i in input.Options
select Option.FromData(i)).ToList();
return output;
}
}
public class Option
{
public int Id { get; set; }
public decimal Price { get; set; }
public static Option FromData(Data.Option input)
{
if (input == null) return null;
Option output = new Option()
{
Id = input.Id,
Price = input.Price ?? 0m
};
return output;
}
}
namespace Data {
public class Quote
{
public int QuoteId { get; set; }
public string Type { get; set; }
public string Destination { get; set; }
public List<RateSet> RateSets { get; set; }
}
public class RateSet
{
public int Id { get; set; }
public decimal ValueMin { get; set; }
public decimal ValueMax { get; set; }
public List<Option> Options { get; set; }
}
public class Option
{
public int Id { get; set; }
public string Name { get; set; }
public decimal? Price { get; set; }
}
}
I'm using a generic list. For example (with some properties):
public class randomList
{
public string propertyA { get; set; }
public string propertyB { get; set; }
public string propertyC { get; set; }
}
So on my retrieving query I used to write the following:
_grouppedResto.Select((value, index) => new { index = index, value = value });
dgvHeader.DataSource = _grouppedResto;
But now it shows blank on index column. I will like to get something like this :
this is all about the datagrid :
this.dgvHeader.AllowUserToAddRows = false;
this.dgvHeader.AllowUserToDeleteRows = false;
this.dgvHeader.AllowUserToResizeRows = false;
this.dgvHeader.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvHeader.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.FECRGV,
this.TDORGV,
this.NDORGV,
this.RUCCLI,
this.RAZCLI,
this.VVTRGVS,
this.IGVRGVS,
this.TOTRGVS,
this.TCARGV,
this.VVTRGVD,
this.IGVRGVD,
this.TOTRGVD});
this.dgvHeader.Location = new System.Drawing.Point(12, 128);
this.dgvHeader.Name = "dgvCACD";
this.dgvHeader.Size = new System.Drawing.Size(1265, 611);
this.dgvHeader.TabIndex = 13;
**dgvHeader.AutoGenerateColumns = false;**
How can I resolve this?
It looks like you need to assign _grouppedResto to an anonymous type as in:
var yourSequence = _groupedResto.Select((value, index) => new { index = index, value = value });
dgvHeader.DataSource = yourSequence;
I'd be happy to help with any particulars you care to provide.
Good luck, hope this helped :)
To combine the index with the properties of your list element you need to change your select statement into something like this:
_grouppedResto.Select(
(value, index) => new {
index = index, propertyA = value.propertyA, propertyB = value.propertyB
}
);
Well, I use this way to solve this... (I think is the worst way, cause you have to modified the domain class)
public class RGVCAFAC
{
public int index { get; set; }
public string CODEAUX { get; set; }
public string FECRGV { get; set; }
public string TDORGV { get; set; }
public string MONRGV { get; set; }
public string NDORGV { get; set; }
public string RUCCLI { get; set; }
public string RAZCLI { get; set; }
public string CDCRGV { get; set; }
public string TERRGV { get; set; }
public string PDSRGV { get; set; }
public double VVTRGVS { get; set; }
public double IGVRGVS { get; set; }
public double TOTRGVS { get; set; }
public string TCARGV { get; set; }
public double VVTRGVD { get; set; }
public double IGVRGVD { get; set; }
public double TOTRGVD { get; set; }
public string PACEST { get; set; }
public string CODUNI { get; set; }
public string DIRCLI { get; set; }
public string TELCLI { get; set; }
public int PFLAG { get; set; }
//I have to add the following, so generators are useless :(
public RGVCAFAC()
{
}
public RGVCAFAC(RGVCAFAC x)
{
this.CODEAUX = x.CODEAUX;
this.FECRGV = x.FECRGV;
this.TDORGV = x.TDORGV;
this.MONRGV = x.MONRGV;
this.RUCCLI = x.RUCCLI;
this.RAZCLI = x.RAZCLI;
this.CDCRGV = x.CDCRGV;
this.TERRGV = x.TERRGV;
this.PDSRGV = x.PDSRGV;
this.VVTRGVS = x.VVTRGVS;
this.IGVRGVS = x.IGVRGVS;
this.TOTRGVS = x.TOTRGVS;
this.TCARGV = x.TCARGV;
this.VVTRGVD = x.VVTRGVD;
this.IGVRGVD = x.IGVRGVD;
this.TOTRGVD = x.TOTRGVD;
this.PACEST = x.PACEST;
this.CODUNI = x.CODUNI;
this.DIRCLI = x.DIRCLI;
this.TELCLI = x.TELCLI;
this.PFLAG = x.PFLAG;
}
}
and finally :
var yourSequence = _grouppedResto.
Select((value, index) => new RGVCAFAC(value) { index = index+1, rgvcafac = value }).ToList(); //need to begin on 1 not on 0
dgvCabecera.DataSource = yourSequence;
int theIndex = 1;
foreach(var x in _grouppedResto)
{
x.index = theIndex;
theIndex += 1;
}