I'm working on this for more than a week and quit stressed currently,
Hope you guys can put me out of my misery.
I welcome If you can suggest overall different approach too. Okay here we go,
I'm on a learning curve and creating a small chat app using SignalR, MVC, JSON, jquery.
I have Chatter class which contain list of ChatMsg class (Msgs). As GetData() method shows below, I'm getting my classes populated from database to a list. As you can see list of Chatter contain some variables including list of ChatMsg. This will get any changes to Table ( new chat messages).
Up to here, this is working fine. [Add part]
[Serializable]
public class Chatter
{
public string Name { get; set; }
public bool Open { get; set; }
public DateTime LastMsg { get; set; }
public IEnumerable<ChatMsg> Msgs { get; set; }
}
[Serializable]
public class ChatMsg
{
public DateTime MsgCreated { get; set; }
public string MsgType { get; set; }
public string MsgBody { get; set; }
}
public List<Chatter> GetData()
{
Dictionary<string, List<ChatMsg>> dcm = new Dictionary<string, List<ChatMsg>>();
List<Chatter> lcm = new List<Chatter>();
using (var connection = new SqlConnection(_connString))
{
connection.Open();
using (var command = new SqlCommand(#"SELECT [Sender], [Receiver], [Body], [MessageCreated] FROM [dbo].[Chat] WHERE [Receiver] = #Name AND [Seen] = #Seen", connection))
{
command.Parameters.Add(new SqlParameter("#Name", "Fan"));//Test val
command.Parameters.Add(new SqlParameter("#Seen", "0"));//Test val
command.Notification = null;
var dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
var reader = command.ExecuteReader();
while (reader.Read())
{
List<ChatMsg> cm = new List<ChatMsg>();
cm.Add(item: new ChatMsg { MsgCreated = Convert.ToDateTime(reader["MessageCreated"]), MsgType = "from", MsgBody = (string)reader["Body"] });
if (dcm.ContainsKey((string)reader["Sender"]))
{ dcm[(string)reader["Sender"]].Add(item: new ChatMsg { MsgCreated = Convert.ToDateTime(reader["MessageCreated"]), MsgType = "from", MsgBody = (string)reader["Body"] }); }
else { dcm.Add((string)reader["Sender"], cm); }
}
}
}
foreach (KeyValuePair<string, List<ChatMsg>> pair in dcm)
{
lcm.Add(item: new Chatter { Name = pair.Key, Open = true, LastMsg = DateTime.UtcNow, Msgs = pair.Value });
}
// Updateting [Seen] = 1 here
return lcm;
}
Now if this is a new instance I'm putting this list of Chatters to Session.
Each time when getData() gets new data I'd like to check my Session["ChatHistory"] and if Parent.Name exist I'd like to update Parent and Addrange to Msgs, if not ad new parent from getData() session list.
I'm strugling on following code.
public string receiveMessages()
{
if (Session["ChatHistory"] == null) Session["ChatHistory"] = new List<Chatter>();
List<Chatter> lc = (List<Chatter>)Session["ChatHistory"];
ChatRepository chatRepository = new ChatRepository();
List<Chatter> c = (List<Chatter>)chatRepository.getData();
//havent tested below
foreach (Chatter e in c)
{
var temp_lc = lc.Find(n => n.Name == e.Name);// Can we avoid linq?
if (temp_lc == null)
{
lc.Add(e);
}
else
{
// How to Addrange to Msgs?
}
}
var serializer = new JavaScriptSerializer();
var t = serializer.Serialize(lc);
return t;
}
How to Update list of class in list of class?
How to remove an item from list of class?
Thank you so much!
Consider using variable names like chatters and chatterHistory instead of c and lc. It makes it much easier to read.
Try rewriting your foreach in receiveMessages() like so:
foreach (Chatter e in c)
{
var temp_lc = lc.Where(x => x.Name == e.Name).SingleOrDefault();
if (temp_lc == null)
{
lc.Add(e);
}
else
{
temp_lc.Msgs = temp_lc.Msgs.Concat(e.Msgs).ToList();
}
}
If temp_lc exists, temp_lc.Msgs.Concat(e.Msgs).ToList() will concatenate the Msgs property with e.Msgs. ToList() converts it into a List<ChatMsg>, and then we can assign the whole thing back to temp_lc.Msgs.
That last step is important because Concat() does not mutate (change) the object it is called on - instead, it returns a new object that we then can assign back to temp_lc.Msgs.
Related
As the title says my intention is to find all tables participating in either INSERT/UPDATE/DELETE statements and produce a structured format. So far this is what I've come up with -
void Main()
{
string DBName = "Blah";
string ServerName = #"(localdb)\MSSQLLocalDB";
Server s = new Server(ServerName);
Database db = s.Databases[DBName];
ConcurrentDictionary<string, SPAudit> list = new ConcurrentDictionary<string, SPAudit>();
var sps = db.StoredProcedures.Cast<StoredProcedure>()
.Where(x => x.ImplementationType == ImplementationType.TransactSql && x.Schema == "dbo")
.Select(x => new
{
x.Name,
Body = x.TextBody
}).ToList();
Parallel.ForEach(sps, item =>
{
try
{
ParseResult p = Parser.Parse(item.Body);
IEnumerable<SqlInsertStatement> insStats = null;
IEnumerable<SqlUpdateStatement> updStats = null;
IEnumerable<SqlDeleteStatement> delStats = null;
var listTask = new List<Task>();
listTask.Add(Task.Run(() =>
{
insStats = FindBatchCollection<SqlInsertStatement>(p.Script.Batches);
}));
listTask.Add(Task.Run(() =>
{
updStats = FindBatchCollection<SqlUpdateStatement>(p.Script.Batches);
}));
listTask.Add(Task.Run(() =>
{
delStats = FindBatchCollection<SqlDeleteStatement>(p.Script.Batches);
}));
Task.WaitAll(listTask.ToArray());
foreach (var ins in insStats)
{
var table = ins?.InsertSpecification?.Children?.FirstOrDefault();
if (table != null)
{
var tableName = table.Sql.Replace("dbo.", "").Replace("[", "").Replace("]", "");
if (!tableName.StartsWith("#"))
{
var ll = list.ContainsKey(item.Name) ? list[item.Name] : null;
if (ll == null)
{
ll = new SPAudit();
}
ll.InsertTable.Add(tableName);
list.AddOrUpdate(item.Name, ll, (key, old) => ll);
}
}
}
foreach (var ins in updStats)
{
var table = ins?.UpdateSpecification?.Children?.FirstOrDefault();
if (table != null)
{
var tableName = table.Sql.Replace("dbo.", "").Replace("[", "").Replace("]", "");
if (!tableName.StartsWith("#"))
{
var ll = list.ContainsKey(item.Name) ? list[item.Name] : null;
if (ll == null)
{
ll = new SPAudit();
}
ll.UpdateTable.Add(tableName);
list.AddOrUpdate(item.Name, ll, (key, old) => ll);
}
}
}
foreach (var ins in delStats)
{
var table = ins?.DeleteSpecification?.Children?.FirstOrDefault();
if (table != null)
{
var tableName = table.Sql.Replace("dbo.", "").Replace("[", "").Replace("]", "");
if (!tableName.StartsWith("#"))
{
var ll = list.ContainsKey(item.Name) ? list[item.Name] : null;
if (ll == null)
{
ll = new SPAudit();
}
ll.DeleteTable.Add(tableName);
list.AddOrUpdate(item.Name, ll, (key, old) => ll);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
});
}
IEnumerable<T> FindBatchCollection<T>(SqlBatchCollection coll) where T : SqlStatement
{
List<T> sts = new List<T>();
foreach (var item in coll)
{
sts.AddRange(FindStatement<T>(item.Children));
}
return sts;
}
IEnumerable<T> FindStatement<T>(IEnumerable<SqlCodeObject> objs) where T : SqlStatement
{
List<T> sts = new List<T>();
foreach (var item in objs)
{
if (item.GetType() == typeof(T))
{
sts.Add(item as T);
}
else
{
foreach (var sub in item.Children)
{
sts.AddRange(FindStatement<T>(item.Children));
}
}
}
return sts;
}
public class SPAudit
{
public HashSet<string> InsertTable { get; set; }
public HashSet<string> UpdateTable { get; set; }
public HashSet<string> DeleteTable { get; set; }
public SPAudit()
{
InsertTable = new HashSet<string>();
UpdateTable = new HashSet<string>();
DeleteTable = new HashSet<string>();
}
}
Now I'm facing two problems
First, its is taking hell lot of a time to complete, given that there are around 841 stored procedures in the database.
Second, if there are statements like the following the table name is not being captured properly, meaning that the table is being captured as w instead of SomeTable_1 or SomeTable_2.
CREATE PROCEDURE [dbo].[sp_blah]
#t SomeTableType READONLY
AS
DELETE w
FROM SomeTable_2 w
INNER JOIN (Select * from #t) t
ON w.SomeID = t.SomeID
DELETE w
FROM SomeTable_1 w
INNER JOIN (Select * from #t) t
ON w.SomeID = t.SomeID
RETURN 0
Any help would be greatly appreciated.
Edit
Using the following dll from this location C:\Program Files (x86)\Microsoft SQL Server\140\DTS\Tasks-
Microsoft.SqlServer.ConnectionInfo.dll
Microsoft.SqlServer.Management.SqlParser.dll
Microsoft.SqlServer.Smo.dll
Microsoft.SqlServer.SqlEnum.dll
Finally I got it to work like I wanted the output to look like using #dlatikay answer. I'm posting this here more for documentation purposes than anything else.
I'm using the following nuget packages -
https://www.nuget.org/packages/Microsoft.SqlServer.SqlManagementObjects/
https://www.nuget.org/packages/Microsoft.SqlServer.TransactSql.ScriptDom/
and removed all other local dependencies. I hope this helps someone out there.
void Main()
{
string DatabaseName = "Blah";
string ServerIP = #"(localdb)\MSSQLLocalDB";
List<string> ExcludeList = new List<string>()
{
"sp_upgraddiagrams",
"sp_helpdiagrams",
"sp_helpdiagramdefinition",
"sp_creatediagram",
"sp_renamediagram",
"sp_alterdiagram",
"sp_dropdiagram"
};
List<string> StringDataTypes = new List<string>()
{
"nvarchar",
"varchar",
"nchar",
"char",
};
Server s = new Server(ServerIP);
s.SetDefaultInitFields(typeof(StoredProcedure), "IsSystemObject");
Database db = s.Databases[DatabaseName];
Dictionary<string, SPAudit> AuditList = new Dictionary<string, SPAudit>();
var sps = db.StoredProcedures.Cast<StoredProcedure>()
.Where(x => x.ImplementationType == ImplementationType.TransactSql && x.Schema == "dbo" && !x.IsSystemObject)
.Select(x => new
{
x.Name,
Body = x.TextBody,
Parameters = x.Parameters.Cast<StoredProcedureParameter>().Select(t =>
new SPParam()
{
Name = t.Name,
DefaultValue = t.DefaultValue,
DataType = $"{t.DataType.Name}{(StringDataTypes.Contains(t.DataType.Name) ? $"({(t.DataType.MaximumLength > 0 ? Convert.ToString(t.DataType.MaximumLength) : "MAX")})" : "")}"
})
}).ToList();
foreach (var item in sps)
{
try
{
TSqlParser parser = new TSql140Parser(true, SqlEngineType.Standalone);
IList<ParseError> parseErrors;
TSqlFragment sqlFragment = parser.Parse(new StringReader(item.Body), out parseErrors);
sqlFragment.Accept(new OwnVisitor(ref AuditList, item.Name, item.Parameters));
}
catch (Exception ex)
{
//Handle exception
}
}
}
public class OwnVisitor : TSqlFragmentVisitor
{
private string spname;
private IEnumerable<SPParam> parameters;
private Dictionary<string, SPAudit> list;
public OwnVisitor(ref Dictionary<string, SPAudit> _list, string _name, IEnumerable<SPParam> _parameters)
{
list = _list;
spname = _name;
parameters = _parameters;
}
public override void ExplicitVisit(InsertStatement node)
{
NamedTableReference namedTableReference = node?.InsertSpecification?.Target as NamedTableReference;
if (namedTableReference != null)
{
string table = namedTableReference?.SchemaObject.BaseIdentifier?.Value;
if (!string.IsNullOrWhiteSpace(table) && !table.StartsWith("#"))
{
if (!list.ContainsKey(spname))
{
SPAudit ll = new SPAudit();
ll.InsertTable.Add(table);
ll.Parameters.AddRange(parameters);
list.Add(spname, ll);
}
else
{
SPAudit ll = list[spname];
ll.InsertTable.Add(table);
}
}
}
base.ExplicitVisit(node);
}
public override void ExplicitVisit(UpdateStatement node)
{
NamedTableReference namedTableReference;
if (node?.UpdateSpecification?.FromClause != null)
{
namedTableReference = node?.UpdateSpecification?.FromClause?.TableReferences[0] as NamedTableReference;
}
else
{
namedTableReference = node?.UpdateSpecification?.Target as NamedTableReference;
}
string table = namedTableReference?.SchemaObject.BaseIdentifier?.Value;
if (!string.IsNullOrWhiteSpace(table) && !table.StartsWith("#"))
{
if (!list.ContainsKey(spname))
{
SPAudit ll = new SPAudit();
ll.UpdateTable.Add(table);
ll.Parameters.AddRange(parameters);
list.Add(spname, ll);
}
else
{
SPAudit ll = list[spname];
ll.UpdateTable.Add(table);
}
}
base.ExplicitVisit(node);
}
public override void ExplicitVisit(DeleteStatement node)
{
NamedTableReference namedTableReference;
if (node?.DeleteSpecification?.FromClause != null)
{
namedTableReference = node?.DeleteSpecification?.FromClause?.TableReferences[0] as NamedTableReference;
}
else
{
namedTableReference = node?.DeleteSpecification?.Target as NamedTableReference;
}
if (namedTableReference != null)
{
string table = namedTableReference?.SchemaObject.BaseIdentifier?.Value;
if (!string.IsNullOrWhiteSpace(table) && !table.StartsWith("#"))
{
if (!list.ContainsKey(spname))
{
SPAudit ll = new SPAudit();
ll.DeleteTable.Add(table);
ll.Parameters.AddRange(parameters);
list.Add(spname, ll);
}
else
{
SPAudit ll = list[spname];
ll.DeleteTable.Add(table);
}
}
}
base.ExplicitVisit(node);
}
}
public class SPAudit
{
public HashSet<string> InsertTable { get; set; }
public HashSet<string> UpdateTable { get; set; }
public HashSet<string> DeleteTable { get; set; }
public List<SPParam> Parameters { get; set; }
public SPAudit()
{
InsertTable = new HashSet<string>();
UpdateTable = new HashSet<string>();
DeleteTable = new HashSet<string>();
Parameters = new List<SPParam>();
}
}
public class SPParam
{
public string Name { get; set; }
public string DefaultValue { get; set; }
public string DataType { get; set; }
}
The SMO model exposes elements of the syntax tree. So instead of assuming a token by position, as in
UpdateSpecification?.Children?.FirstOrDefault();
look up the corresponding property in the documentation. For the update clause, the target table (or updatable view) can occur in different positions. Take this syntax:
UPDATE tablename SET column=value WHERE conditions
which is represented as
var targettable = ins?.UpdateSpecification?.Target?.ScriptTokenStream?.FirstOrDefault()?.Text;
in the SMO model. Whereas, a syntax unique to tsql,
UPDATE t SET t.columnname=value FROM tablename t WHERE conditions
will have its list of tables in the FROM clause.
Regarding the other two DML statements you mentioned: DELETE is the same because they share a common base class, DeleteInsertSpecification (Target).
For INSERT, there is the Target as well, and if its InsertSource is of type SelectInsertSource, this may be based on any number of tables and views too.
You can use following SQL Query:
SELECT *
FROM sys.dm_sql_referenced_entities ('dbo.APSP_MySP', 'OBJECT');
It gives you all the tables, views, SPs impacted in the stored procedure.
is_selected or is_select_all are set to 1 for selected references
is_updated is set to 1 for updated references
As query is reading from pre-defined system tables, it runs fast
If you need information about the referred object use the referenced_id column value to find details
You can use it in 2 ways:
Call the above query in parallel for each stored procedure
Create another query/SP which will loop and run it for every stored procedure
Change Proc_1 to your procedure name
Refine PATINDEX matching to cater for the different possibilites
Modify to look at all procedures
Does not cater for tables in dynamic sql or passed as parameters
Look out for any issues with dm_sql_referenced_entities
SELECT
e.TableName,
p.name,
PATINDEX('%DELETE '+e.TableName+'%', p.definition) AS is_delete,
PATINDEX('%INSERT INTO '+e.TableName+'%', p.definition) AS is_insert,
PATINDEX('%UPDATE '+e.TableName+'%', p.definition) AS is_update
FROM
(
SELECT distinct referenced_entity_name AS TableName
FROM sys.dm_sql_referenced_entities ('dbo.Proc_1', 'OBJECT')
) e,
(
SELECT o.name, m.object_id, definition
FROM sys.objects o, sys.sql_modules m
WHERE o.name = 'Proc_1'
AND o.type='P'
AND m.object_id = o.object_id
) p
I would recommend you querying the syscomments SQL view. The performance will be much better.
select text from sys.syscomments where text like '%DELETE%'
You can work with the results in the SQL Query or fetch all the results and filter the data in C#.
I am building a parser for a custom pipe delimited file format and I am finding my code to be very bulky, could someone suggest better methods of parsing this data?
The file's data is broken down by a line delimited by a pipe (|), each line starts with a record type, followed by an ID, followed by different number of columns after.
Ex:
CDI|11111|OTHERDATA|somemore|other
CEX001|123131|DATA|data
CCC|123131|DATA|data1|data2|data3|data4|data5|data6
. I am splitting by pipe, then grabbing the first two columns, and then using a switch checking the first line and calling a function that will parse the remaining into an object purpose built for that record type. I would really like a more elegant method.
public Dictionary<string, DataRecord> Parse()
{
var data = new Dictionary<string, DataRecord>();
var rawDataDict = new Dictionary<string, List<List<string>>>();
foreach (var line in File.ReadLines(_path))
{
var split = line.Split('|');
var Id = split[1];
if (!rawDataDict.ContainsKey(Id))
{
rawDataDict.Add(Id, new List<List<string>> {split.ToList()});
}
else
{
rawDataDict[Id].Add(split.ToList());
}
}
rawDataDict.ToList().ForEach(pair =>
{
var key = pair.Key.ToString();
var values = pair.Value;
foreach (var value in values)
{
var recordType = value[0];
switch (recordType)
{
case "CDI":
var cdiRecord = ParseCdi(value);
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key, CdiRecords = new List<CdiRecord>() { cdiRecord }
});
}
else
{
data[key].CdiRecords.Add(cdiRecord);
}
break;
case "CEX015":
var cexRecord = ParseCex(value);
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key,
CexRecords = new List<Cex015Record>() { cexRecord }
});
}
else
{
data[key].CexRecords.Add(cexRecord);
}
break;
case "CPH":
CphRecord cphRecord = ParseCph(value);
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key,
CphRecords = new List<CphRecord>() { cphRecord }
});
}
else
{
data[key].CphRecords.Add(cphRecord);
}
break;
}
}
});
return data;
}
Try out FileHelper, here is your exact example - http://www.filehelpers.net/example/QuickStart/ReadFileDelimited/
Given you're data of
CDI|11111|OTHERDATA|Datas
CEX001|123131|DATA
CCC|123131
You could create a class to model this to allow FileHelpers to parse the delimited file:
[DelimitedRecord("|")]
public class Record
{
public string Type { get; set; }
public string[] Fields { get; set; }
}
Then we could allow FileHelpers to parse in to this object type:
var engine = new FileHelperEngine<Record>();
var records = engine.ReadFile("Input.txt");
After we've got all the records loaded in to Record objects we can use a bit of linq to pull them in to their given types
var cdis = records.Where(x => x.Type == "CDI")
.Select(x => new Cdi(x.Fields[0], x.Fields[1], x.Fields[2])
.ToArray();
var cexs = records.Where(x => x.Type == "CEX001")
.Select(x => new Cex(x.Fields[0], x.Fields[1)
.ToArray();
var cccs = records.Where(x => x.Type == "CCC")
.Select(x => new Ccc(x.Fields[0])
.ToArray();
You could also simplify the above using something like AutoMapper - http://automapper.org/
Alternatively you could use ConditionalRecord attributes which will only parse certain lines if they match a given criteria. This will however be slower the more record types you have but you're code will be cleaner and FileHelpers will be doing most of the heavy lifting:
[DelimitedRecord("|")]
[ConditionalRecord(RecordCondition.IncludeIfMatchRegex, "^CDI")]
public class Cdi
{
public string Type { get; set; }
public int Number { get; set; }
public string Data1 { get; set; }
public string Data2 { get; set; }
public string Data3 { get; set; }
}
[DelimitedRecord("|")]
[ConditionalRecord(RecordCondition.IncludeIfMatchRegex, "^CEX001")]
public class Cex001
{
public string Type { get; set; }
public int Number { get; set; }
public string Data1 { get; set; }
}
[DelimitedRecord("|")]
[ConditionalRecord(RecordCondition.IncludeIfMatchRegex, "^CCC")]
public class Ccc
{
public string Type { get; set; }
public int Number { get; set; }
}
var input =
#"CDI|11111|Data1|Data2|Data3
CEX001|123131|Data1
CCC|123131";
var CdiEngine = new FileHelperEngine<Cdi>();
var cdis = CdiEngine.ReadString(input);
var cexEngine = new FileHelperEngine<Cex001>();
var cexs = cexEngine.ReadString(input);
var cccEngine = new FileHelperEngine<Ccc>();
var cccs = cccEngine.ReadString(input);
Your first loop isn't really doing anything other than organizing your data differently. You should be able to eliminate it and use the data as it is from the file. Something like this should give you what you want:
foreach (var line in File.ReadLines(_path))
{
var split = line.Split('|');
var key = split[1];
var value = split;
var recordType = value[0];
switch (recordType)
{
case "CDI":
var cdiRecord = ParseCdi(value.ToList());
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key, CdiRecords = new List<CdiRecord>() { cdiRecord }
});
}
else
{
data[key].CdiRecords.Add(cdiRecord);
}
break;
case "CEX015":
var cexRecord = ParseCex(value.ToList());
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key,
CexRecords = new List<Cex015Record>() { cexRecord }
});
}
else
{
data[key].CexRecords.Add(cexRecord);
}
break;
case "CPH":
CphRecord cphRecord = ParseCph(value.ToList());
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key,
CphRecords = new List<CphRecord>() { cphRecord }
});
}
else
{
data[key].CphRecords.Add(cphRecord);
}
break;
}
};
Caveat: This is just put together here and hasn't been properly checked for syntax.
I'm a fairly junior C# developer so please excuse me if this is very easy, but I am getting this error "invalid initializer member declarator" at this line of the code below:
foreach (DataRow p in data.Rows)
{
DBTrack top = new DBTrack()
/* ===> Error starts here */ {
track.TrackID = SQLDataHelper.GetGuid(dataReader, "TrackID");
track.TrackName = SQLDataHelper.GetString(dataReader, "TrackName");
track.ArtistName = SQLDataHelper.GetString(dataReader, "ArtistName");
track.AddedDate = SQLDataHelper.GetDateTime(dataReader, "AddedDate");
};
DBTrackData.Add(top);
}
Can someone explain what this means and how do I work around and achieve it to display the data?
Here's the full method for your inspection:
public static List<DBTrack> GetAllTracksFromReaderDB(IDataReader dataReader)
{
if (DBTrackData == null)
{
DBTrack track = new DBTrack();
System.Data.DataTable data = new System.Data.DataTable();
List<DBTrack> daa = new List<DBTrack>();
DBTrackData = new List<DBTrack>();
foreach (DataRow p in data.Rows)
{
DBTrack top = new DBTrack()
/* ===> Error starts here */ {
track.TrackID = SQLDataHelper.GetGuid(dataReader, "TrackID");
track.TrackName = SQLDataHelper.GetString(dataReader, "TrackName");
track.ArtistName = SQLDataHelper.GetString(dataReader, "ArtistName");
track.AddedDate = SQLDataHelper.GetDateTime(dataReader, "AddedDate");
};
DBTrackData.Add(top);
}
}
return DBTrackData;
}
EDIT:
public Guid TrackID { get; set; }
public string TrackName { get; set; }
public string ArtistName { get; set; }
public DateTime AddedDate { get; set; }
Any help would be great :) Thanks
Your syntax is invalid for member initialization. You cant instansiate top and try to fill track properties.
It should look like this:
DBTrack top = new DBTrack
{
TrackID = SQLDataHelper.GetGuid(dataReader, "TrackID"),
TrackName = SQLDataHelper.GetString(dataReader, "TrackName"),
ArtistName = SQLDataHelper.GetString(dataReader, "ArtistName"),
AddedDate = SQLDataHelper.GetDateTime(dataReader, "AddedDate")
};
i have populated data reader from db table and i have class like
public class CandidateApplication
{
public string EmailID { get; set; }
public string Name { get; set; }
public string PhoneNo { get; set; }
public string CurrentLocation { get; set; }
public string PreferredWorkLocation { get; set; }
public int RoleApplingFor { get; set; }
public string CurrentJobTitle { get; set; }
public int EducationLevel { get; set; }
public decimal SalaryExpected { get; set; }
public string AvailableTime { get; set; }
public int AdvertID { get; set; }
public bool SignForAlert { get; set; }
public string CVInText { get; set; }
public string CVFileName { get; set; }
public bool IsDownloaded { get; set; }
public string specialization { get; set; }
public bool isallocated { get; set; }
public int id { get; set; }
public string AdvertAdditionalInfo { get; set; }
}
i can populate the above class in loop. we can iterate in data reader and populate class but i want to know is there any short cut way to populate class from data reader.
if data deserialization is possible from data reader to class then also tell me if few fields are there in class which are not there in data reader then how to handle the situation.
You don't need to use a Data Reader, You could just Populate the Data into a DataTable, and use the below method to create a List of your CandidateApplication Class.
The Call :-
List<CandidateApplication> CandidateList = GetCandidateInformation();
The Method that generates the list :-
public List<CandidateApplication> GetCandidateInformation()
{
DataTable dt = new DataTable();
using (OleDbConnection con = new OleDbConnection(ConfigurationManager.AppSettings["con"]))
{
using (OleDbCommand cmd = new OleDbCommand("SELECT * FROM [TableName]", con))
{
var adapter = new OleDbDataAdapter();
adapter.SelectCommand = cmd;
con.Open();
adapter.Fill(dt);
var CandApp = (from row in dt.AsEnumerable()
select new CandidateApplication
{
EmailID = row.Field<string>("EmailID"),
Name = row.Field<string>("Name"),
PhoneNo = row.Field<string>("PhoneNo"),
CurrentLocation = row.Field<string>("CurrentLocation"),
PreferredWorkLocation = row.Field<string>("PreferredWorkLocation"),
RoleApplingFor = row.Field<int>("RoleApplingFor"),
CurrentJobTitle = row.Field<string>("CurrentJobTitle"),
EducationLevel = row.Field<int>("EducationLevel "),
SalaryExpected = row.Field<decimal>("SalaryExpected"),
AvailableTime = row.Field<string>("AvailableTime"),
AdvertID = row.Field<int>("AdvertID"),
SignForAlert = row.Field<bool>("SignForAlert"),
CVInText = row.Field<string>("CVInText"),
CVFileName = row.Field<string>("CVFileName"),
IsDownloaded = row.Field<bool>("IsDownloaded"),
Specialization = row.Field<string>("Specialization"),
Isallocated = row.Field<bool>("Isallocated"),
Id = row.Field<int>("Id"),
AdvertAdditionalInfo = row.Field<string>("AdvertAdditionalInfo")
}).ToList();
return CandApp;
}
}
}
Although not an answer to your question, I would suggest you to consider the following workaround, which uses a SqlDataAdapter instead of a data reader:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Xml.Serialization;
class Program
{
static void Main(string[] args)
{
var cs = "YourConnectionString";
var xml = "";
using (var con = new SqlConnection(cs))
using (var c = new SqlCommand("SELECT * FROM CandidateApplication", con))
{
con.Open();
using (var adapter = new SqlDataAdapter(c))
{
var ds = new DataSet("CandidateApplications");
ds.Tables.Add("CandidateApplication");
adapter.Fill(ds, ds.Tables[0].TableName);
xml = ds.GetXml();
}
}
// We need to specify the root element
var rootAttribute = new XmlRootAttribute();
// The class to use as the XML root element (should match the name of
// the DataTable in the DataSet above)
rootAttribute.ElementName = "CandidateApplications";
// Initializes a new instance of the XmlSerializer class that can
// serialize objects of the specified type into XML documents, and
// deserialize an XML document into object of the specified type.
// It also specifies the class to use as the XML root element.
// I chose List<CandidateApplication> as the type because I find it
// easier to work with (but CandidateApplication[] will also work)
var xs = new XmlSerializer(typeof(List<CandidateApplication>), rootAttribute);
// Deserialize the XML document contained by the specified TextReader,
// in our case, a StringReader instance constructed with xml as a parameter.
List<CandidateApplication> results = xs.Deserialize(new StringReader(xml));
}
}
For those properties that are missing in the retrieved data, you could declare a private field with a default value:
string _advertAdditionalInfo = "default";
public string AdvertAdditionalInfo
{
get
{
return _advertAdditionalInfo;
}
set
{
_advertAdditionalInfo = value;
}
}
If you would like to enforce that the retrieved data will not fill in a specific property, use:
[XmlIgnoreAttribute]
public string AdvertAdditionalInfo { get; set; }
I made a generic function for converting the SELECT result from an OleDbCommand to a list of classes.
Let's say that I have a class that looks like this, which maps to the columns in the database:
internal class EconEstate
{
[Column(Name = "basemasterdata_id")]
public Guid BaseMasterDataId { get; set; }
[Column(Name = "basemasterdata_realestate")]
public Guid? BaseMasterDataRealEstate { get; set; }
[Column(Name = "business_area")]
public string BusinessArea { get; set; }
[Column(Name = "profit_centre")]
public int ProfitCentre { get; set; }
[Column(Name = "rentable_area")]
public decimal RentableArea { get; set; }
}
Then I can get a list of those EconEstate objects using this code:
public void Main()
{
var connectionString = "my connection string";
var objects = ReadObjects<EconEstate>(connectionString, "EMBLA.EconEstates").ToList();
}
private static IEnumerable<T> ReadObjects<T>(string connectionString, string tableName) where T : new()
{
using (var connection = new OleDbConnection(connectionString))
{
connection.Open();
using (var command = new OleDbCommand($"SELECT * FROM {tableName};", connection))
{
var adapter = new OleDbDataAdapter
{
SelectCommand = command
};
var dataTable = new DataTable();
adapter.Fill(dataTable);
foreach (DataRow row in dataTable.Rows)
{
var obj = new T();
foreach (var propertyInfo in typeof(T).GetProperties())
{
var columnAttribute = propertyInfo.GetCustomAttributes().OfType<ColumnAttribute>().First();
var value = row[columnAttribute.Name];
var convertedValue = ConvertValue(value, propertyInfo.PropertyType);
propertyInfo.SetValue(obj, convertedValue);
}
yield return obj;
}
}
}
}
private static object ConvertValue(object value, Type targetType)
{
if (value == null || value.GetType() == typeof(DBNull))
{
return null;
}
if (value.GetType() == targetType)
{
return value;
}
var underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
if (value is string stringValue)
{
if (underlyingTargetType == typeof(int))
{
return int.Parse(stringValue);
}
else if (underlyingTargetType == typeof(decimal))
{
return decimal.Parse(stringValue);
}
}
var valueType = value.GetType();
var constructor = underlyingTargetType.GetConstructor(new[] { valueType });
var instance = constructor.Invoke(new object[] { value });
return instance;
}
As you can see, the code is generic, making it easy to handle different tables and classes.
I am a fairly new C# programmer and am getting stuck on trying to convert FQL results into a custom class...for example, I am doing the following, but it seems like a lot of steps...I was just returning a datatable, but wanted the result to be strongly typed class collection. I'd appreciate any insights. I'm open to other ways of achieving similar results as well.
Thanks,
Chad
public class FacebookFriends
{
public string FriendID { get; set; }
public string FriendName { get; set; }
public string PicURLSquare { get; set; }
public string ProfileLink { get; set; }
//Gets your FB friends that are NOT currently using this application so you can invite them
public IEnumerable<FacebookFriends> GetFriendsNotUsingApp()
{
string strQuery = "SELECT uid, name, pic_square, link FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=me()) AND NOT is_app_user";
FacebookSDKInterface objFQL = new FacebookSDKInterface();
dynamic objFNU = objFQL.FBFQL(strQuery);
//Construct the new, formated, merged datatable to store the results the way we want them
DataTable dtFriendsNotUsingApp = new DataTable();
dtFriendsNotUsingApp.Columns.Add("FriendID");
dtFriendsNotUsingApp.Columns.Add("FriendName");
dtFriendsNotUsingApp.Columns.Add("PicURLSquare");
dtFriendsNotUsingApp.Columns.Add("Link");
if (objFQL != null)
{
foreach (dynamic row in objFNU.data)
{
//Add New DataRow to new DataTable
DataRow drRow = dtFriendsNotUsingApp.NewRow();
//Get various values from original JSON Friend List returned
drRow["FriendID"] = row.uid;
drRow["FriendName"] = row.name;
drRow["PicURLSquare"] = row.pic_square;
drRow["Link"] = row.link;
//Add New Row to New Resulting Data Table
dtFriendsNotUsingApp.Rows.Add(drRow);
}
dtFriendsNotUsingApp.DefaultView.Sort = "FriendName";
}
IEnumerable<FacebookFriends> objFriendsListCollection = null;
var toLinq = from list in dtFriendsNotUsingApp.AsEnumerable()
select new FacebookFriends
{
FriendID = list["FriendID"].ToString(),
FriendName = list["FriendName"].ToString(),
PicURLSquare = list["PicURLSquare"].ToString(),
ProfileLink = list["ProfileLink"].ToString()
};
objFriendsListCollection = toLinq.OrderByDescending(p => p.FriendName);
return objFriendsListCollection;
} //Get FB Friends not already using this app
I belive this may help.
1st: I've never used the Facebook API, so I'm just using your code as an example.
2nd: As the method is inside the class, I've changed it to static. This way, you can use it by simply calling FacebookFriends.GetFriendsNotUsingApp(), instead of new FacebookFriends().GetFriendsNotUsingApp().
3rd The code:
public class FacebookFriends
{
public string FriendID { get; set; }
public string FriendName { get; set; }
public string PicURLSquare { get; set; }
public string ProfileLink { get; set; }
//Gets your FB friends that are NOT currently using this application so you can invite them
public static IEnumerable<FacebookFriends> GetFriendsNotUsingApp()
{
string strQuery = "SELECT uid, name, pic_square, link FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=me()) AND NOT is_app_user";
FacebookSDKInterface objFQL = new FacebookSDKInterface();
dynamic objFNU = objFQL.FBFQL(strQuery);
List<FacebookFriends> friendsToReturn = new List<FacebookFriends>();
if (objFQL != null)
{
foreach (dynamic row in objFNU.data)
{
friendsToReturn.Add(new FacebookFriends()
{
FriendID = row.uid,
FriendName = row.name,
PicURLSquare = row.pic_square,
ProfileLink = row.link
}
);
}
}
return friendsToReturn;
} //Get FB Friends not already using this app
}
Hope this helps.
Regards
I have no experience with Facebook API or FQL as well, but by looking at your code objFNU.data appears to implement IEnumerable, hence you can use LINQ extension methods directly with it:
public class FacebookFriends
{
public string FriendID { get; set; }
public string FriendName { get; set; }
public string PicURLSquare { get; set; }
public string ProfileLink { get; set; }
//Gets your FB friends that are NOT currently using this application so you can invite them
public static IEnumerable<FacebookFriends> GetFriendsNotUsingApp()
{
string strQuery = "SELECT uid, name, pic_square, link FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=me()) AND NOT is_app_user";
FacebookSDKInterface objFQL = new FacebookSDKInterface();
dynamic objFNU = objFQL.FBFQL(strQuery);
if (objFQL != null) // shouldn't you check objFNU for being null here instead?
{
IEnumerable<dynamic> objFNUdata = (IEnumerable<dynamic>)objFNU.data; // explicit cast might not be necessary
return objFNUdata.Select(row => new FacebookFriends()
{
FriendID = row.uid,
FriendName = row.name,
PicURLSquare = row.pic_square,
ProfileLink = row.link
}).OrderByDescending(p => p.FriendName);
}
else
{
return new List<FacebookFriends>();
}
} //Get FB Friends not already using this app
}
In the end, this worked best for me. Thanks to both, and especially #DarmirArh for all his help in getting this to work.
try
{
FacebookSDKInterface objFQL = new FacebookSDKInterface();
dynamic objFNU = objFQL.FBFQL(strQuery);
if (objFNU != null) // shouldn't you check objFNU for being null here instead?
{
IEnumerable<dynamic> objFNUdata = (IEnumerable<dynamic>)objFNU.data; // explicit cast might not be necessary
IEnumerable<FacebookFriends> objMyFriends =
from row in objFNUdata
select new FacebookFriends()
{
FriendID = row.uid,
FriendName = row.name,
PicURLSquare = row.pic_square,
ProfileLink = row.profile_url
};
objMyFriends = objMyFriends.OrderBy(p => p.FriendName);
return objMyFriends;
}
else
{
return new List<FacebookFriends>();
}
}
catch (Exception ex)
{
return new List<FacebookFriends>();
}