Update List<T> through self join? - c#

First off I am very new to LINQ.
I have a list which has data something like this :
list
list[0] = id=1,block=10,sg=320,dc=null
list[1] = id=1,block=10,sg=null,dc=320
list[2] = id=2,block=15,sg=400,dc=null
list[3] = id=2,block=15,sg=null,dc=400
I want to update this list such that :
if(sg where block=x and id=y is null)
then set sg = (sg where block=x and id=y is not null)
and similarly for dc
Desired Result:
list[0] = id=1,block=10,sg=320,dc=320
list[1] = id=2,block=15,sg=400,dc=400
NOTE: id and block are identifying factors here.
CLASS:
public class dcsg
{
public long id { get; set; }
public Nullable<decimal> dcvalue { get; set; }
public Nullable<decimal> sgvalue { get; set; }
public Nullable<int> revision { get; set; }
public Nullable<long> timestampid { get; set; }
public decimal fuelcost { get; set; }
public Nullable<short> isdeleted { get; set; }
public Nullable<long> blockno { get; set; }
public int stageid { get; set; }
}

You could achieve this using Linq, GroupBy.
lists = lists.GroupBy(x=> new {x.id, x.blockno})
.Select(x=>
{
var sg1 = x.FirstOrDefault(f=>f.sgvalue.HasValue);
var dc1 = x.FirstOrDefault(f=>f.dcvalue.HasValue);
return new dcsg() // create class instance if have one.
{
id= x.Key.id,
blockno= x.Key.blockno,
sgvalue = sg1==null? null; sg1.sgvalue,
dcvalue = dc1==null? null; dc1.dcvalue,
// copy other properties (if needed).
};
})
.ToList();
Obviously, code snippet written with two assumptions.
In case of multiple sg appear for same block first sg will betaken (but this can be changed based on need).
Based on your example, id,block are used to group list items.

Related

how can I preserve my class after list clear

I have 2 classes
public class Product
{
public DateTime Date { get; set; }
public string Name { get; set; }
public int Amount { get; set; }
}
public class Campaign
{
public long CampaignId { get; set; }
public string CampaignName { get; set; }
public List<Product> Products { get; set; }
}
Code:
var campaign = new Campaign();
campaign.CampaignId = Item.CampaignId;
campaign.CampaignId = Item.CampaignId;
campaign.CampaignName = Item.CampaignName;
campaign.Products = productList;
campaignList.Add(campaign);
productList.Clear();
When I call productList.Clear(), my "campaign" deletes its campaign.Products.
How can I prevent that from happening?
campaign.Products = new List<Product>(productList);
because campaign.Products is the same reference of productList
they are both pointing to the same list , any action on one will be reflected in the other varialbe
you need to clone (make another copy of the list) by different ways as follwoing
campaign.Products = productList.GetClone();
or
campaign.Products = productList.ToList();
or
campaign.Products.AddRange(productList);
check the following url
https://www.techiedelight.com/clone-list-in-csharp/

How do I iteratively assign to a nested list within an object in C#?

I've been asked to replicate a complex xml structure we use within our internal systems, against data retrieved from another system. Unfortunately the xml structure is improvised and we have no specification for it.
I've been mapping out it's structure in C# classes so that I can assign those properties with values from the database and ultimately serialise it as xml.
I've hit a bit of a road block in terms of iteratively adding new list items to a nested list within an object that's already being intialised as a list and being looped through. To make matters more complicated I need to use a value from the iteration to filter down the dataset being used to instantiate the second loop round.
Sorry for the poor explanation - open to rewording... hopefully however the example I've written will demonstrate what I'm trying to do:
using System;
using System.Collections.Generic;
using System.Data;
public class TransactionModel
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime SysDate { get; set; }
public List<TransactionItemModel> Trade { get; set; } = new List<TransactionItemModel>();
}
public class TransactionItemModel
{
public int ItemId { get; set; }
public int TransactionId { get; set; }
public string ItemDescription { get; set; }
public decimal ItemNetAmount { get; set; }
}
public class Program
{
public void Main()
{
DataTable tranResultSet = MethodToReturnResultsFromTranQuery();
DataTable itemResultSet = MethodToReturnResultsFromItemQuery();
var transactions = new List<TransactionModel>();
foreach (DataRow tran in tranResultSet.Rows)
{
transactions.Add(
new TransactionModel() {
Id = (dynamic)tran["Id"],
Description = (dynamic)tran["Description"],
SysDate = (dynamic)tran["SysDate"],
//Trade = <Stuck Here>
// Need to iterate through itemResultSet, adding to TransactionModel.Trade
// where item["TransactionId"] = tran["Id"]
}
);
}
}
}
This approach doesn't set the Trade collection initially, but populates it once you go through the Items. There's likely a lot of optimization that can be added, but this might get you started.
public class TransactionModel
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime? SysDate { get; set; }
public List<TransactionItemModel> Trade { get; set; }
public TransactionModel(DataRow row)
{
if(row == null)
throw new ArgumentNullException(nameof(row));
Id = row.Field<int>("Id");
Description = row.Field<string>("Description");
SysDate = row.Field<DateTime?>("SysDate");
Trade = new List<TransactionItemModel>();
}
}
public class TransactionItemModel
{
public int ItemId { get; set; }
public int TransactionId { get; set; }
public string ItemDescription { get; set; }
public decimal? ItemNetAmount { get; set; }
public TransactionItemModel(DataRow row)
{
if(row == null)
throw new ArgumentNullException(nameof(row));
ItemId = row.Field<int>("Id");
TransactionId = row.Field<int>("TransactionId");
ItemDescription = row.Field<string>("ItemDescription");
ItemNetAmount = row.Field<decimal?>("ItemNetAmount");
}
}
public static class Program
{
private static void Main()
{
DataTable tranResultSet = MethodToReturnResultsFromTranQuery();
DataTable itemResultSet = MethodToReturnResultsFromItemQuery();
var transactions = tranResultSet.AsEnumerable()
.Select(r => new TransactionModel(r));
foreach(TransactionModel transaction in transactions)
{
var items = itemResultSet.AsEnumerable()
.Where(r => r.Field<int>("TransactionId") == transaction.Id)
.Select(r => new TransactionItemModel(r));
transaction.Trade.AddRange(items);
}
}
}
It's likely going to be ideal to query your ItemResultSet based on the current TransactionId instead of grabbing them all up front. You could implement a DataReader, or use Dapper.

Cannot initialize type with a collection initializer because it does not implement 'System.Collections.IEnumerable'

I have 3 entities for main menus. and sub menus. I created menu view model.
My Entities:
MainMenu.cs:
public UstMenuler()
{
AltMenuler = new HashSet<AltMenuler>();
}
public int ID { get; set; }
public string Name { get; set; }
public bool AktifMi { get; set; }
public int YetkiID { get; set; }
public string ControllerName { get; set; }
public string ActionName { get; set; }
public virtual Yetkilendirme Yetkilendirme { get; set; }
public virtual ICollection<AltMenuler> AltMenuler { get; set; }
SubMenu.cs:
public partial class AltMenuler
{
public AltMenuler()
{
UstMenuler = new HashSet<UstMenuler>();
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ID { get; set; }
public int? YetkiID { get; set; }
public string Name { get; set; }
public string ActionName { get; set; }
public string ControllerName { get; set; }
public virtual Yetkilendirme Yetkilendirme { get; set; }
}
MyViewModel:
public class MenuViewModel
{
public UstMenuler UstMenuler { get; set; }
public AltMenuler AltMenuler { get; set; }
}
My Controller:
dbContext = new MarkettingDbContext();
int sessionYetkiID = (int)Session["yetkiID"];
MenuViewModel menuler = new MenuViewModel();
var query = (from menu in dbContext.UstMenuler
where (menu.YetkiID == sessionYetkiID)
select new MenuViewModel
{
menuler.UstMenuler.ID = menu.ID,
menuler.UstMenuler.Name = menu.Name,
menuler.UstMenuler.YetkiID = menu.YetkiID,
menuler.UstMenuler.ControllerName = menu.ControllerName,
menuler.UstMenuler.ActionName = menu.ActionName
}).ToList();
I am getting error when I execute my query. For example my sessionID is 1, I want to take my main and submenus.
The problem is your object initializer:
select new MenuViewModel
{
menuler.UstMenuler.ID = menu.ID,
menuler.UstMenuler.Name = menu.Name,
menuler.UstMenuler.YetkiID = menu.YetkiID,
menuler.UstMenuler.ControllerName = menu.ControllerName,
menuler.UstMenuler.ActionName = menu.ActionName
}
Firstly you're using menuler in the object initializer, and I'm not sure why. Next, you're trying to assign to subproperties, which even if it did compile, would result in a NullReferenceException. Collectively, that's basically not what you want to do :)
Instead, I'd use one of the two options below.
If you're happy to just copy the UstMenuler reference from the menu parameter into the new view model instance, you can do that really easily:
select new MenuViewModel { UseMenuler = menu }
Or if you want to create a new UstMenuler and copy properties from menu:
select new MenuViewModel
{
UstMenuler = new UstMenuler
{
ID = menu.ID,
Name = menu.Name,
YetkiID = menu.YetkiID,
ControllerName = menu.ControllerName,
ActionName = menu.ActionName
}
}
Either way, you don't need your menuler variable which you appear not to be using anywhere else.
As it sounds like you're using LINQ to Entities, it sounds like you may want to separate the database query part from the projection part. The simplest way to do that in your case is to avoid the query expression syntax, so you can call AsEnumerable easily. So for example:
var query = dbContext.UstMenuler
.Where(menu => menu.YetkiID == sessionYetkiID)
.AsEnumerable() // Do the projection in-process
.Select(menu => new MenuViewModel
{
UstMenuler = new UstMenuler
{
ID = menu.ID,
Name = menu.Name,
YetkiID = menu.YetkiID,
ControllerName = menu.ControllerName,
ActionName = menu.ActionName
}
})
.ToList();

Unable to create a constant value of type 'AddressModel'. Only primitive types or enumeration types are supported in this context

In the EF query below, any time an child contact address field is assigned to the Address property of the TaxReceiptsWithChargesModel, I get the error message:
"Cannot create constant value of type 'AddressModel'"
If I comment out the line that assigns the address, then the error does not occur. What could the issue be?
Edit: Typically, the causes of this problem I've seen elsewhere have to do with using the contains or equals LINQ methods, but the cause of this particular issue seems to lie elsewhere.
Here are the relevant sections of code:
//GetChildContactAddress
var childContactAddress = lgcCcpModel.ChildContacts
.Where(cc => cc.UUID == ltrm.ChildContactId)
.Select(cc => new AddressModel()
{
Address1 = cc.Address.STREET,
City = cc.Address.CITY,
Province = cc.Address.PROVINCE,
PostalCode = cc.Address.POSTALCODE
}).FirstOrDefault();
//Create the Tax Receipt Model with the Charge List
return unitOfWork.LegacyTaxReceiptStore.GetQuery()
.Where(ltr => ltr.LegacyTaxReceiptId == ltrm.LegacyTaxReceiptId)
.Select(c => new TaxReceiptsWithChargesModel()
{
LegacyTaxReceiptId = ltrm.LegacyTaxReceiptId,
ChildContactId = ltrm.ChildContactId,
ChildContact = ltrm.ChildContact,
EmailAddress = ltrm.EmailAddress,
ChildId = ltrm.ChildId,
ChildName = ltrm.ChildName,
ChargesTotal = ltrm.ChargesTotal,
TaxReceiptAmount = ltrm.TaxReceiptAmount.Value,
TaxReceiptYear = ltrm.TaxReceiptYear,
Address = childContactAddress,
ReceiptNumber = $"{ltrm.TaxReceiptYear}-{ltrm.LegacyTaxReceiptId.ToString().PadLeft(6, '0')}",
Charges = taxReceiptChargesModelList,
}).FirstOrDefault();
public class TaxReceiptsWithChargesModel : ITaxReceiptsModel
{
public int LegacyTaxReceiptId { get; set; }
public string ChildContactId { get; set; }
public string ChildContact { get; set; }
public string EmailAddress { get; set; }
public IAddressModel Address { get; set; }
public decimal? OpeningBalance { get; set; }
public decimal? InvoicesTotal { get; set; }
public decimal? PaymentsTotal { get; set; }
public string ChildId { get; set; }
public string ChildName { get; set; }
public decimal? ChargesTotal { get; set; }
public decimal? TaxReceiptAmount { get; set; }
public string State { get; set; }
public int TaxReceiptYear { get; set; }
public string ReceiptNumber { get; set; }
public int? BinaryDocumentId { get; set; }
public List<TaxReceiptsChargesModel> Charges { get; set; }
}
public interface IAddressModel
{
string Address1 { get; set; }
string Address2 { get; set; }
string City { get; set; }
string Country { get; set; }
string PostalCode { get; set; }
string Province { get; set; }
}
That is because childContactAddress object (and also taxReceiptChargesModelList) is already in memory and when you try to assign a complex object in the projection of your second query, the Linq provider can't translated that object to SQL. One option can be call AsEnumerable extension method:
return unitOfWork.LegacyTaxReceiptStore.GetQuery()
.Where(ltr => ltr.LegacyTaxReceiptId ==ltrm.LegacyTaxReceiptId)
.AsEnumerable()
.Select(c => new TaxReceiptsWithChargesModel()
{
LegacyTaxReceiptId = ltrm.LegacyTaxReceiptId,
ChildContactId = ltrm.ChildContactId,
ChildContact = ltrm.ChildContact,
EmailAddress = ltrm.EmailAddress,
ChildId = ltrm.ChildId,
ChildName = ltrm.ChildName,
ChargesTotal = ltrm.ChargesTotal,
TaxReceiptAmount = ltrm.TaxReceiptAmount.Value,
TaxReceiptYear = ltrm.TaxReceiptYear,
Address = childContactAddress,
ReceiptNumber = $"{ltrm.TaxReceiptYear}-{ltrm.LegacyTaxReceiptId.ToString().PadLeft(6, '0')}",
Charges = taxReceiptChargesModelList,
}).FirstOrDefault();
Update
Your issue can be also solve this way:
var result=unitOfWork.LegacyTaxReceiptStore.GetQuery()
.FirstOrDefault(ltr => ltr.LegacyTaxReceiptId ==ltrm.LegacyTaxReceiptId);
return new TaxReceiptsWithChargesModel()
{
LegacyTaxReceiptId = result.LegacyTaxReceiptId,
ChildContactId = result.ChildContactId,
ChildContact = result.ChildContact,
EmailAddress = result.EmailAddress,
ChildId = result.ChildId,
ChildName = result.ChildName,
ChargesTotal = result.ChargesTotal,
TaxReceiptAmount = result.TaxReceiptAmount.Value,
TaxReceiptYear = result.TaxReceiptYear,
Address = childContactAddress,
ReceiptNumber = $"{result.TaxReceiptYear}-{result.LegacyTaxReceiptId.ToString().PadLeft(6, '0')}",
Charges = taxReceiptChargesModelList,
};
You have to apply [Serializable()] decorator however you cannot do it to interfaces.
Instead, implement your custom interface with ISerializable {}
A similar question has been answered below, credit of this post goes to Oded.
Why are interfaces not [Serializable]?
Cheers
Can you please tell what is point of using interfaces as a model property.
Microsoft do not encourage use of in interfaces as a model property. You can restrict changes in your property by using getter and setter methods .
See this post
https://stackoverflow.com/a/8455558/2173098

How to group multiple lists using LINQ - Sum and GroupBy

I have three generic lists and I am trying to combine and get them into one (List<One>). I am not sure how to use LINQ group by on different lists. I want to group by AccountCode, AccountDate, and SalesCode; and sum the amount.
public class One
{
public string AccountCode { get; set; }
public DateTime AccountDate { get; set; }
public string SalesCode { get; set; }
public decimal? Amount { get; set; }
}
public class Two
{
public string AccountCode { get; set; }
public DateTime AccountDate { get; set; }
public string SalesCode { get; set; }
public decimal? Amount { get; set; }
}
public class Three
{
public string AccountCode { get; set; }
public DateTime AccountDate { get; set; }
public string SalesCode { get; set; }
public decimal? Amount { get; set; }
}
List<One> oneList = new List<One>();
List<Two> twoList = new List<Two>();
List<Three> threeList = new List<Three>();
This is the sample query I have, which is not working. Note: I have not included List<Three>.
from first in oneList
join second in twoList
on first.AccountCode equals second.AccountCode
where
(first.AccountDate.Date == second.AccountDate.Date && first.SalesCode == second.SalesCode)
select new
{
first.AccountCode,
first.AccountDate,
first.SalesCode
first.Amount,
second.Amount
}).Distinct()
group bill by new { bill.AccountCode, bill.AccountDate, bill.SalesCode }
into groupedList
select new
{
groupedList.Key.UAN,
groupedList.Key.AccountDate,
Amount = groupedList.Sum(a => a.Amount)
};
As mentioned in the comment, the first thing you'll need is a common type so that the 3 lists can be joined into a single list. That could be a base class or an interface as I've shown here.
public interface IAccount
{
string AccountCode { get; set; }
DateTime AccountDate { get; set; }
string SalesCode { get; set; }
decimal? Amount { get; set; }
}
public class One : IAccount
{
// ...
}
public class Two : IAccount
{
// ...
}
public class Three : IAccount
{
// ...
}
Once that's in place, then it's easy to combine your 3 lists into one like this:
var allList = oneList.Cast<IAccount>().Union(twoList).Union(threeList);
From there, the group by becomes much simpler as well. When using LINQ to group by multiple fields, you'll want an IEqualityComparer class that looks like this:
public class AccountComparer : IEqualityComparer<IAccount>
{
public bool Equals(IAccount x, IAccount y)
{
return x.AccountCode == y.AccountCode &&
x.AccountDate == y.AccountDate &&
x.SalesCode == y.SalesCode;
}
public int GetHashCode(IAccount obj)
{
return (obj.AccountCode + obj.AccountDate + obj.SalesCode).GetHashCode();
}
}
The group by call is then one line:
var groups = allList.GroupBy(a => a, new AccountComparer());
Alternatively, you could create an AccountKey class that only has the 3 key fields and return that from the keySelector lambda. That feels a little cleaner, but is a bit more code.
From there, you can select the sums like this:
var amountSums = from g in groups
select new
{
g.Key.AccountCode,
g.Key.AccountDate,
g.Key.SalesCode,
Amount = g.Sum(a => a.Amount)
};

Categories