I'm new to linq. I'm using linq to fill some classes from the database.
All my objects are being filled correctly using my linq except for one small thing. Below you can see that for "SAR" "Flight Direction" (rows 15,16) I should have a value for Ascending and a value for Descending. But what I'm getting are two value objects for "Ascending".
Why isn't it looping through my values? What is the LINQ missing?
Here are my DB results:
Here is my LINQ:
using (NpgsqlDataReader reader = command.ExecuteReader())
{
if (reader.HasRows)
{
var groups = reader.Cast<System.Data.Common.DbDataRecord>()
.GroupBy(dr => new { ID = (int)dr["id"], AttID = (int)dr["AttId"] })
.GroupBy(g => g.Key.ID);
typeDataList = (
from typeGroup in groups
let typeRow = typeGroup.First().First()
select new TypeData()
{
ID = (int) typeRow["id"],
Type = (string) typeRow["type"],
Attributes =
(
from attGroup in typeGroup
let attRow = attGroup.First()
select new TypeDataAttribute()
{
ID = (int)attRow["AttId"],
Label = (string)attRow["label"],
PossibleValues =
(
from row in attGroup
where !DBNull.Value.Equals(attRow["AttValueId"])
select new TypeDataAttributeValue() { ID = (int)attRow["AttValueId"], Value = (string)attRow["value"] }
).ToArray()
}
).ToArray()
}
);
}
}
You are taking Value from attRow, you should take it from row.
Related
I have following code,
public List<ActiveViewUser> GetAllActiveUsers(int branchId){
List<ActiveViewUser> status = new List<ActiveViewUser>();
DataSet ds = DataAccessManager.ExecuteStoredProcedure("spGetAllActiveUsers", parameters);
// add an all user option to the list
ActiveViewUser allusr = new ActiveViewUser();
List<ActiveViewUser> allActiveusers = new List<ActiveViewUser>();
allusr.UserId = -1;
allusr.UserName = "AllUsers";
allusr.FirstName = "All";
status.Add(allusr);
foreach (DataRow dr in ds.Tables[0].AsEnumerable())
{
ActiveViewUser usr = new ActiveViewUser();
usr.UserId = dr["UserId"].ToString().Length == 0
? 0
: Convert.ToInt32(dr["UserId"].ToString());
usr.UserName = dr["UserName"].ToString();
usr.FirstName = dr["FirstName"].ToString();
allActiveusers.Add(usr);
}
var newli = allActiveusers.OrderBy(x => x.FirstName).ToList();
status.Add(newli); //Error occurred in this line
}
As per the above code, I need to first insert All as the first index and, other all active users need insert after the that order by user's FirstName. So i tried above query. its returns following error.
cannot convert from 'System.Collections.Generic.List<Lib.DataView.ActiveViewUser>' to 'Lib.DataView.ActiveViewUser'. What did i do wrong here. how can I fix this?
Add requires a single element of the list type, now the code is trying to add a whole list (newli) instead of each single objects of that list.
This should insert, in the status list, all the elements of the newli list
status.AddRange(newli);
The List<T> class has also an InsertTo method that allows to insert an element to a specific position. So you could even simplify a bit the code with
status = new List<ActiveUser>();
foreach (DataRow dr in ds.Tables[0].AsEnumerable())
{
ActiveViewUser usr = new ActiveViewUser();
usr.UserId = dr["UserId"].ToString().Length == 0 ? 0 : Convert.ToInt32(dr["UserId"].ToString());
usr.UserName = dr["UserName"].ToString();
usr.FirstName = dr["FirstName"].ToString();
// Add directly to the status list....
status.Add(usr);
}
// Order the status list and reassign the result to the same list
status = status.OrderBy(x => x.FirstName).ToList();
// Finally insert at position 0 the "AllUsers" user.
status.InsertTo(0, new ActiveUser{UserId=-1, UserName="AllUsers", FirstName="All"}
return status;
You can try materialize (i.e. create) status with a help of Linq from top and body records:
// Top record(s)
var allusr = new ActiveViewUser[] {
new ActiveViewUser() {
UserId = -1,
UserName = "AllUsers",
FirstName = "All",
}
};
// Body records
var newli = DataAccessManager
.ExecuteStoredProcedure("spGetAllActiveUsers", parameters)
.Tables[0]
.AsEnumerable()
.Select(dr => new ActiveViewUser() {
UserId = dr["UserId"] == DBNull.Value ? 0 : Convert.ToInt32(dr["UserId"]),
UserName = Convert.ToString(dr["UserName"]),
FirstName = Convert.ToString(dr["FirstName"]),
})
.OrderBy(x => x.FirstName);
// Top and Body combined:
List<ActiveViewUser> status = allusr
.Concat(newli)
.ToList();
IEnumerable<WebsiteWebPage> data = GetWebPages();
foreach (var value in data)
{
if (value.WebPage.Contains(".htm"))
{
WebsiteWebPage pagesinfo = new WebsiteWebPage();
pagesinfo.WebPage = value.WebPage;
pagesinfo.WebsiteId = websiteid;
db.WebsiteWebPages.Add(pagesinfo);
}
}
db.SaveChanges();
I want to add only distinct values to database in above code. Kindly help me how to do it as I am not able to find any solution.
IEnumerable<WebsiteWebPage> data = GetWebPages();
foreach (var value in data)
{
if (value.WebPage.Contains(".htm"))
{
var a = db.WebsiteWebPages.Where(i => i.WebPage == value.WebPage.ToString()).ToList();
if (a.Count == 0)
{
WebsiteWebPage pagesinfo = new WebsiteWebPage();
pagesinfo.WebPage = value.WebPage;
pagesinfo.WebsiteId = websiteid;
db.WebsiteWebPages.Add(pagesinfo);
db.SaveChanges();
}
}
}
This is the code that I used to add distinct data.I hope it helps
In addition to the code sample Furkan Öztürk supplied, Make sure your DB has a constraint so that you cannot enter duplicate values in the column. Belt and braces approach.
I assume that by "distinct values" you mean "distinct value.WebPage values":
// get existing values (if you ever need this)
var existingWebPages = db.WebsiteWebPages.Select(v => v.WebPage);
// get your pages
var webPages = GetWebPages().Where(v => v.WebPage.Contains(".htm"));
// get distinct WebPage values except existing ones
var distinctWebPages = webPages.Select(v => v.WebPage).Distinct().Except(existingWebPages);
// create WebsiteWebPage objects
var websiteWebPages = distinctWebPages.Select(v =>
new WebsiteWebPage { WebPage = v, WebsiteId = websiteid});
// save all at once
db.WebsiteWebPages.AddRange(websiteWebPages);
db.SaveChanges();
Assuming that you need them to be unique by WebPage and WebSiteId
IEnumerable<WebsiteWebPage> data = GetWebPages();
foreach (var value in data)
{
if (value.WebPage.Contains(".htm"))
{
WebsiteWebPage pagesinfo = new WebsiteWebPage();
if (db.WebsiteWebPages.All(c=>c.WebPage != value.WebPage|| c.WebsiteId != websiteid))
{
pagesinfo.WebPage = value.WebPage;
pagesinfo.WebsiteId = websiteid;
db.WebsiteWebPages.Add(pagesinfo);
}
}
}
db.SaveChanges();
UPDATE
To optimize this (given that your table contains much more data than your current list), override your equals in WebsiteWebPage class to define your uniqueness criteria then:
var myWebsiteWebPages = data.select(x=> new WebsiteWebPage { WebPage = x.WebPage, WebsiteId = websiteid}).Distinct();
var duplicates = db.WebsiteWebPages.Where(x=> myWebsiteWebPage.Contains(x));
db.WebsiteWebPages.AddRange(myWebsiteWebPages.Where(x=> !duplicates.Contains(x)));
this is a one database query to retrieve ONLY duplicates and then removing them from the list
You can use the following code,
IEnumerable<WebsiteWebPage> data = GetWebPages();
var templist = new List<WebsiteWebPage>();
foreach (var value in data)
{
if (value.WebPage.Contains(".htm"))
{
WebsiteWebPage pagesinfo = new WebsiteWebPage();
pagesinfo.WebPage = value.WebPage;
pagesinfo.WebsiteId = websiteid;
templist.Add(pagesinfo);
}
}
var distinctList = templist.GroupBy(x => x.WebsiteId).Select(group => group.First()).ToList();
db.WebsiteWebPages.AddRange(distinctList);
db.SaveChanges();
Or you can use MoreLINQ here to filter distinct the list by parameter like,
var res = tempList.Distinct(x=>x.WebsiteId).ToList();
db.WebsiteWebPages.AddRange(res);
db.SaveChanges();
When I am Trying to save current list of data into database, I need to get already existing data from database, and need to compare with current list of data.
I have two lists one is PreviousList(existing data from DB) and other is CurrentList(Modified data)
public class SoftClose
{
public int ID = -1;
public int AID = -1;
public int WFID = -1;
public string PREFIX;
public DateTime SCDATE;
public string STATUS;
}
In CurrentList I modified Prefix to D2 where ID=1 and added new row(Id=4)...
My req is
When I am trying to save CurrentList to Db,
If there is any new Prefix in CurrentList that is not there in PreviousList I need to insert that new row and need to change Status to ADD for that row.
I changed Prefix to D2 where Id = 1 in CurrentList. D1 is there is DB and but not in CurrentList so i need to delete it. So i need to change the status to DELETE for that record. I should not insert D2 record where id=1 becuase D2 is already there. If I changed to D5 where Id = 1 then I need to insert it because D5 is not there in DB So i need to change the status to UPDATE.
How to do this? What is the best approach to compare lists
here is a solution you could try:
List<SoftClose> previousList = new List<SoftClose>(){
new SoftClose(){ID=1, Status = "NO_CHANGE",AID="19", Prefix = "D1"},
new SoftClose(){ID=2, Status = "NO_CHANGE",AID="20", Prefix = "D2"},
new SoftClose(){ID=3, Status = "NO_CHANGE",AID="21", Prefix = "D3"}
};
List<SoftClose> currentList = new List<SoftClose>(){
new SoftClose(){ID=1, Status = "NO_CHANGE",AID="19", Prefix = "D2"},
new SoftClose(){ID=2, Status = "NO_CHANGE",AID="20", Prefix = "D2"},
new SoftClose(){ID=3, Status = "NO_CHANGE",AID="21", Prefix = "D6"},
new SoftClose(){ID=4, Status = "NO_CHANGE",AID="22", Prefix = "D4"},
new SoftClose(){ID=5, Status = "NO_CHANGE",AID="22", Prefix = "D5"}
};
var addlist = currentList.Where(c => previousList.All(p => !p.ID.Equals(c.ID) && !p.Prefix.Equals(c.Prefix)));
foreach(var n in addlist)
{
var index = currentList.FindIndex(p => p.Prefix.Equals(n.Prefix));
currentList[index].Status = "ADD";
}
var updateORdeletelist = currentList.Where(c => c.Status.Equals("NO_CHANGE") && previousList.Exists(p => p.ID.Equals(c.ID) && !p.Prefix.Equals(c.Prefix)));
foreach (var n in updateORdeletelist)
{
var index = currentList.FindIndex(p => p.Prefix.Equals(n.Prefix));
if (previousList.FindIndex(p => p.Prefix.Equals(n.Prefix)) < 0)
currentList[index].Status = "UPDATE";
else
currentList[index].Status = "DELETE";
}
foreach (var item in currentList)
{
Console.WriteLine($"Id:{item.ID}, Desc1:{item.Prefix}, Status:{item.Status}");
}
output
Id:1, Desc1:D2, Status:DELETE
Id:2, Desc1:D2, Status:NO_CHANGE
Id:3, Desc1:D6, Status:UPDATE
Id:4, Desc1:D4, Status:ADD
Id:5, Desc1:D5, Status:ADD
There is a tool called Side by Side SQL Comparer in C# at https://www.codeproject.com/Articles/27122/Side-by-Side-SQL-Comparer-in-C.
basic use of the component:
using (TextReader tr = new StreamReader(#"c:\1.sql"))
{
sideBySideRichTextBox1.LeftText = tr.ReadToEnd();
}
using (TextReader tr = new StreamReader(#"c:\2.sql"))
{
sideBySideRichTextBox1.RightText = tr.ReadToEnd();
}
sideBySideRichTextBox1.CompareText();
You load the left and right sides to their respective variables sideBySideRichTextBox1.LeftText and sideBySideRichTextBox1.RightText and compare them with sideBySideRichTextBox1.CompareText();
In your case the 1.sql and 2.sql would be your PreviousList and CurrentList -database files.
There is more detailed documentation at the project-site.
I have this query:
var smallExchangeReport = from ex in exchangeProgReport
where !string.IsNullOrEmpty(ex.comment)
group ex by new { ex.siteName } into g
select new SummuryReportTraffic
{
siteName = g.Key.siteName,
exchangeCounter = g.Where(x => x.Prog1ToProg2Check == 1).Count(),
descriptions = (from t in g
group t by new { t.comment, t.siteName } into grp
select new Description
{
title = grp.Key.comment,
numbers = grp.Select(x => x.comment).Count()
})
};
At some point I put it to the dataTable using foreach loop:
foreach (var item in smallExchangeReport)
{
dr = smrTable.NewRow();
foreach (var d in item.descriptions)
{
dr[d.title] = d.numbers;
}
smrTable.Rows.Add(dr);
}
But I need to put the LINQ result to dataTable without using foreach loop.
So I made some changes to my code above according to this link:
DataTable dt = new DataTable();
DataRow dr = dt.NewRow();
IEnumerable<DataRow> smallExchangeReport = from ex in exchangeProgReport.AsEnumerable()
where !string.IsNullOrEmpty(ex.comment)
group ex by new { ex.siteName } into g
select new
{
siteName = g.Key.siteName,
exchangeCounter = g.Where(x => x.Prog1ToProg2Check == 1).Count(),
descriptions = (from t in g.AsEnumerable()
group t by new { t.comment, t.siteName } into grp
select new
{
title = grp.Key.comment,
numbers = grp.Select(x => x.comment).Count()
})
};
// Create a table from the query.
DataTable boundTable = smallExchangeReport.CopyToDataTable<DataRow>();
But on changed LINQ query I get this error:
Cannot implicitly convert type:'System.Collections.Generic.IEnumerable<<anonymous type: string siteName, int exchangeCounter>>' to
'System.Collections.Generic.IEnumerable<System.Data.DataRow>'. An explicit conversion exists (are you missing a cast?)
My question is how to cast the query to make it work?I tryed to cast to(DataRow) the result of the LINQ but it didn't worked.
In your LINQ query, you are trying to get IEnumerable<DataRow> as the result, but actually you select new objects of an anonymous type: select new { siteName = .... }. This cannot work because your anonymous type cannot be cast to DataRow.
What you need to do is use a function that would populate a DataRow like this:
DataRow PopulateDataRow(
DataTable table,
string siteName,
int exchangeCounter,
IEnumerable<Description> descriptions
{
var dr = table.NewRow();
// populate siteName and exchangeCounter
// (not sure how your data row is structured, so I leave it to you)
foreach (var d in descriptions)
{
dr[d.title] = d.numbers;
}
return dr;
}
then in your LINQ query, use it as follows:
IEnumerable<DataRow> smallExchangeReport =
from ex in exchangeProgReport.AsEnumerable()
where !string.IsNullOrEmpty(ex.comment)
group ex by new { ex.siteName } into g
select PopulateDataRow(
smrTable,
siteName: g.Key.siteName,
exchangeCounter: g.Where(x => x.Prog1ToProg2Check == 1).Count(),
descriptions: (from t in g.AsEnumerable()
group t by new { t.comment, t.siteName } into grp
select new Description {
title = grp.Key.comment,
numbers = grp.Select(x => x.comment).Count()
}
)
);
This solution gets rid of one foreach (on rows) and leaves the other one (on descriptions).
If removing the second foreach is important... I would still leave it inside PopulateDataRow. I don't see an elegant way to remove it. You can call a method from LINQ query which reads like a deterministic function, but actually creates the side effect of setting a column value on a data row, but it doesn't feel right to me.
this is can help you.
defining table structure.
DataTable tbl = new DataTable();
tbl.Columns.Add("Id");
tbl.Columns.Add("Name");
and we need to create datarow from anonymous type.
Func<object, DataRow> createRow = (object data) =>
{
var row = tbl.NewRow();
row.ItemArray = data.GetType().GetProperties().Select(a => a.GetValue(data)).ToArray();
return row;
};
test with fake query:
var enumarate = Enumerable.Range(0, 10);
var rows = from i in enumarate
select createRow( new { Id = i, Name = Guid.NewGuid().ToString() });
var dataTable = rows.CopyToDataTable<DataRow>();
You can use this method:
private DataTable ListToDataTable<T>(List<T> objs, string tableName) {
var table = new DataTable(tableName);
var lists = new List<List<object>>();
// init columns
var propertyInfos = new List<PropertyInfo>();
foreach (var propertyInfo in typeof(T).GetProperties()) {
propertyInfos.Add(propertyInfo);
if(propertyInfo.PropertyType.IsEnum || propertyInfo.PropertyType.IsNullableEnum()) {
table.Columns.Add(propertyInfo.Name, typeof(int));
} else {
table.Columns.Add(propertyInfo.Name, Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType);
}
table.Columns[table.Columns.Count - 1].AllowDBNull = true;
}
// fill rows
foreach(var obj in objs) {
var list = new List<object>();
foreach(var propertyInfo in propertyInfos) {
object currentValue;
if(propertyInfo.PropertyType.IsEnum || propertyInfo.PropertyType.IsNullableEnum()) {
var val = propertyInfo.GetValue(obj);
if(val == null) {
currentValue = DBNull.Value;
} else {
currentValue = (int)propertyInfo.GetValue(obj);
}
} else {
var val = propertyInfo.GetValue(obj);
currentValue = val ?? DBNull.Value;
}
list.Add(currentValue);
}
lists.Add(list);
}
lists.ForEach(x => table.Rows.Add(x.ToArray()));
return table;
}
Edit:
this extension method is used:
public static bool IsNullableEnum(this Type t) {
var u = Nullable.GetUnderlyingType(t);
return u != null && u.IsEnum;
}
I get a list from stored procedure with its child date I want to avoid repeating when I add items to the the list
Here is my code:
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = "[dbo].[T]";
db.Database.Connection.Open();
var reader1 = cmd.ExecuteReader();
var objectContext1 = ((IObjectContextAdapter)db).ObjectContext;
var usersLi = objectContext1.Translate<GetAllNewsForUser>(reader1).ToList();
List<GetAllNewsForUser> lll = new List<GetAllNewsForUser>();
foreach (var item in usersLi)
{
NewsInfo newsInfo = new NewsInfo();
GetAllNewsForUser g = new GetAllNewsForUser();
g.UserName = item.UserName;
NewsInfo nnn = new NewsInfo();
nnn.NewsTitle = item.NewsTitle;
nnn.NewsId = item.NewsId;
g.NewsInfos.Add(nnn);
lll.Add(g);
}
are there any unique identifiers for g, for example a user Id. in which case you could easily use the following:
if (!lll.Any(x=>x.UserId ==g.UserId)) //include system.linq
{
lll.Add(g);
}
You could also look at modifying your stored procedure to not return duplicates.
EDIT ----
Just twigged as to what you actually after, sorry.....
Its just a rough draft but i think this is what you require.
List<GetAllNewsForUser> lll = new List<GetAllNewsForUser>();
foreach (var item in usersLi)
{
NewsInfo nnn = new NewsInfo();
nnn.NewsTitle = item.NewsTitle;
nnn.NewsId = item.NewsId;
if (!lll.Any(x=> x.UserName == item.UserName)
{
GetAllNewsForUser g = new GetAllNewsForUser();
g.UserName = item.UserName;
g.NewsInfos.Add(nnn);
lll.Add(g);
}
else
{
lll.Where(x=>x.UserName == item.Username).FirstOrDefault().NewsInfos.Add(nnn)
}
}
From what I understand so far, you are trying to avoid adding same rows in the list(based on value of some column).
LINQ might help here.
usersLi.GroupBy(e => new {
UserID = e.UserUD
}).Select(g => g.First());