Automatically Filter DataTable - c#

I have a console application connected to a SQL Server database with several tables and views. To get the entire table I so something like:
myAppDataset dsTemp = new myAppDataset();
myAppDataset.AppLogDataTable dtLog = dsTemp.AppLog;
myAppDataset.AppUserDataTable dtUser = dsTemp.AppUser;
Then when I need to filter I create a DataView:
DataView dvLog = dtLog.DefaultView;
dvLog.RowFilter = "DeptID = 1";
DataView dvUser = dtUser.DefaultView;
dvUser.RowFilter = "DeptID = 1";
That all works fine.
What I'd like to know is if there is a way modify the DataSet (or something else) so that I don't need to create the DataViews? In other words, I want every AppLogDataTable, AppUserDataTable, etc that gets created to be filtered for DeptID = 1. Essentially what I want to achieve is to be able to pass a parameter to my data class constructor that will automatically filter all of the data tables so that when they are used I don't have to worry about creating a DataView and filtering the table every time (which also necessitates the passing of the original filtering parameters).
I tried creating a DataView and overwriting the original object, but got an error that the DataTable couldn't be casted or something to that effect:
myAppDataset dsTemp = new myAppDataset();
myAppDataset.AppLogDataTable dtLog = dsTemp.AppLog;
DataView dvLog = dtLog.DefaultView;
dvLog.RowFilter = "DeptID = 1";
dtLog = (myAppDataset.AppLogDataTable)dvLog.ToTable();
Any help is greatly appreciated.

some possible suggestions:
use Linq2Object, and not DataView at all
var filterStep1 = dtUser.Where(x => x.DeptID == 1);
var filterStep2 = filterStep1.Where(x => x.XYZ < 40);
Console.WriteLine(filterStep2);
is equivalent to:
var filter = dtUser.Where(x => x.DeptID == 1 && x => x.XYZ < 40);
Console.WriteLine(filter);
edit the sql query
you can filter in the sql query.
in TypedDataSet case, double-click on myAppDataset in the solution-explorer, click on the Fill, GetData() that appears under the table-box.
in property window (F4), click double-click on Select Command property. the query-designer shown. add the filter to query (if you not want write the 1 but parameter, type ? in the criteria - it create parameter for command automaticaly).
use List for criteria
another solution is create a list of criteria, and join them to RowFilter:
var criteria = new List<string>();
criteria.Add("DeptID = 1");
criteria.Add("XYZ < 40");
dvUser.RowFilter = string.Join(" AND ", criteria);

You really shouldn't be reading data from the database if you don't intend to use it. Filter your SQL query.
select somecolumns from sometable where DeptID = 1
However, let's pretend for a moment that you're reading all the data into memory for caching purposes or something like this. Don't put that into a DataSet, DataTable, or DataView. Those are outdated constructs. They're slow, inefficient, and give you none of the benefits of binding to a strongly typed object.
Instead, create a type to represent your data. Since you don't give much context, I'm going to pretend you're dealing with Students that have a many to one relationship with Departments.
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int DepartmentId { get; set; }
}
Now you have some choices. You can use ADO.NET to get the data.
public class StudentSqlServerRepository
{
private readonly string _connectionString;
public StudentSqlServerRepository(string connectionString)
{
_connectionString = connectionString;
}
public List<Student> GetStudentsByDepartmentId(int departmentId)
{
var students = new List<Student>();
using(var connection = new SqlConnection(_connectionString))
using(var command = new SqlCommand("select Id, Name, DepartmentId from students where DepartmentId = #DepartmentId", connection))
{
command.Parameters.Add(new SqlParameter("DepartmentId", SqlDbType.Int).Value = departmentId);
connection.Open();
using(var reader = command.ExecuteReader())
{
while(reader.Read())
{
var student = new Student();
student.Id = (int)reader["Id"];
student.Name = (string)reader["Name"];
student.DepartmentId = (int)reader["DepartmentId"];
students.Add(student);
}
}
}
return students;
}
}
But that's a lot of yucky code. Fortunately, Dapper, a micro ORM, can make this a lot cleaner.
public class StudentSqlServerRepository
{
private readonly string _connectionString;
public StudentSqlServerRepository(string connectionString)
{
_connectionString = connectionString;
}
public List<Student> GetStudentsByDepartmentId(int departmentId)
{
using(var connection = new SqlConnection(_connectionString))
{
var students = connection.Query<Student>("select Id, Name, DepartmentId from students where DepartmentId = #DepartmentId", new { DepartmentId = departmentId}).AsList();
return students;
}
}
}
Now getting your students is as easy as
var studentRepository = new StudentSqlServerRepository(ConfigurationManager.ConnectionStrings["StudentDatabase"].ConnectionString);
var students = studentRepository.GetStudentsByDepartmentId(1);
//let's pretend this is Web Forms and we want to bind to a control
StudentsGridView.DataSource = students;
StudentsGridView.DataBind();
//let's pretend it's MVC and we want to return a View
return View(students);
Compare the memory usage, amount of network traffic, query execution time, and overall ease of this approach as opposed to using a DataTable and filtering in memory.

Related

C#, Entity Framework, How to Update(delete, add, edit) multiple rows at same time?

Sometimes, we would like to change order details by adding, removing, and editing orders by customer's request or depends on stock quantity.
So now want get some list and update including remove, edit, add rows, then save on database
What's the best efficiently way as C#, EntityFramework?
public class OrderDetail
{
public int Id { get; set; }
public int OrderId {get; set; }
public int Qty{ get; set; }
public string ItemName { get; set; }
}
/// Dummy db, OrderDetail Table
{
{1, 1000, 24,"A"},
{2, 1000, 12,"B"}
}
public void Update()
{
using(var db = new xxEntities())
{
// Get All orders, OrderId==1000, total 2rows
List<OrderDetails> list = db.OrderDetails.Where(x=> x.OrderId==1000).ToList();
// remove some row or rows
var temp1 = list.First(x=> x.Id==1);
list.Remove(temp);
// edit some row or rows
var temp2 = list.First(x=> x.Id==2);
temp2.Qty=100;
// add some row or rows
list.Add(new OrderDetail{ Id=3, OrderId=1000, Qty=2, ItemName="C"});
list.Add(new OrderDetail{ Id=4, OrderId=1000, Qty=2, ItemName="D"});
// Apply all changes
db.SaveChanges();
}
}
Additional Question
public void UpdateOrder(int orderId, List<OrderDetail> newOrders)
{
var result = db.OrderDetails.Where(x=>x.OrderId==orderId).ToList();
result = newOrders;
// it does not work
//db.OrderDetails.Update(result);
db.OrderDetails.RemoveRange(result);
db.OrderDetails.AddRange(newOrders);
db.SaveChange();
}
is it right approach to update multiple rows?
As mentioned in another answer... EF will create individual statements for each of the changes that are detected (i.e., updates, inserts, deletes) and submit them inside a single transaction. Gets the job done but is potentially very "chatty". Benefit is that you don't need to worry about the details of how it's getting done. Pretty easy to just modify the data object and call SaveChanges.
If you can consider not using EF for updates such as this... one way we do this kind of update is by creating a System.Data.DataTable and using that as a table-valued parameter into a stored procedure (if your datastore supports it).
Meta-code:
var dt = new DataTable();
var newRow = dt.NewRow();
newRow["column1"] = newdata;
dt.Rows.Add(newRow);
Then just use dt as your input parameter and let the stored proc determine the insert/update/delete operations.
If you want to Add / Remove / Update rows from your tables in Entity Framework, you have to Add / Remove / Update the items in your DbSet, not in fetched data.
using (var dbContext = new OrderContext())
{
// Add one Order
Order orderToAdd = new Order
{
// fill required properties; don't fill primary key
}
var addedOrder = dbContext.Orders.Add(orderToAdd);
// note: addedOrder has no Id yet.
// Add several Orders
IEnumerable<Order> orders = ...
dbContext.Orders.AddRange(orders);
dbContext.SaveChanges();
// now they've got their id:
Debug.Assert(addedOrder.Id != 0);
Debug.Assert(orders.All(order => order.Id != 0);
}
To Remove, you'll first have to fetch the complete Order
int orderIdToDelete = ...
using (var dbContext = new OrderContext())
{
Order orderToDelete = dbContext.Orders.Find(orderIdToDelete);
dbContext.Orders.Remove(orderToDelete);
var ordersToDelete = dbContext.Orders
.Where(order => order.Date.Year < 2000)
.ToList();
dbContext.Orders.RemoveRange(ordersToDelete);
// the orders are not deleted yet.
dbContext.SaveChanges();
}
To Update, you first have to get the value:
int orderIdToUpdate = ...
Order orderToUpdate = dbContext.Orders.Find(orderIdToUpdate);
orderToUpdate.Date = DateTime.Today;
var today = Datetime.Today;
var dateLimit = today.AddDays(-28);
var nonPaidOrders = dbContext.Orders
.Where(order => !order.Paid && order.Date < dateLimit)
.ToList();
foreach (var order in nonPaidOrders)
{
this.SendReminder(order);
order.ReminderDate = today;
}
dbContext.SaveChanges();
There is no "most efficient" way outside of making all changes then calling SaveChanges. upon which Ef will issue a lot of SQL Statements (one per operation).
There is most efficient way because there is no way to change the way Ef works and there is exactly one way Ef does its updates. They do NOT happen at the same time. Period. They happen in one transaction, one after the other, when you call SaveChanges.

Generating SQL insert statement using classes

I have a query regarding generating SQL insert statement using c# classes.
So I have a class called students.
There is a function which gets list of students and dump that in database.
Student Model
public class Student
{
public string ID { get; set; } = ""; // DB column name is studentID
public string Name { get; set; } = ""; // DB column name is studentName
public string address { get; set; } // DB column name is studentAddress
}
Function to dump Data
public async Task<Error> DumpStudentAsync()
{
List<Student> students = new List<Student>();
StringBuilder query = new StringBuilder();
query = query.Append("INSERT INTO Student(studentID,studentName,studentAddress) VALUES");
string columnList = "(#studentID{0},#studentName{0},#studentAddress{0})";
for (int i = 0; i < students.Count; i++)
{
query.AppendFormat($"{columnList},", i);
}
query = query.Replace(',', ';', query.Length - 1, 1);
SqlCommand cmd = new SqlCommand
{
CommandText = query.ToString(),
};
for (int i = 0; i < students.Count; i++)
{
cmd.Parameters.AddWithValue($"#studentID{i}", students[i].ID);
cmd.Parameters.AddWithValue($"#studentName{i}", students[i].Name);
cmd.Parameters.AddWithValue($"#studentAddress{i}", students[i].address);
}
SQLWrapper db = new SQLWrapper(_ctx, "", DSConfig.SQLConnectionKey);
return await db.ExecuteStatementAsync(cmd, "");
}
So here I want to make this function generic in such a way that if I add a new field in my student object there should be no code change done in my function.
I tried searching the answers but I didn't get anything.
Here firstly I'm appending the insert format in query where I have hard-coded.
Secondly I'm appending variables for command parameters on the basis of students count which is also hard-coded.
Also the class properties and database columns names are different how can I make use of class properties as DB column names?
Can I do something like this ?
StringBuilder studentQuery = new StringBuilder();
string columns = "";
// Add column list on the basis of students properties
foreach (var property in Student.Properties)
{
columns += "property.ID,"; // It should be studentID
}
// Add variables on the basis of students count
// Add command parameters value on the basis of students count
FYI: I'm using ADO.NET code to perform DB activities not Entity framework.
This kind of automatic is not easily done. .NET is strongly typed. Unless you go into stuff like reflection or dynamic code to do it. And I would not advise it. Strong Typisation is your friend. Without it you end up in the JavaScript and PHP examples for this comic. Hint: JS does the wrong thing in both cases.
For me at least, having to do some minor changes on the frontend for changes on the Database is acceptable work. If anything that is the smalest, least dangerous part of the whole process. So I can only advise against trying this.
However for databases and only databases, stuff like Entity Framework might be a good idea. It can generate your classes from the Database.

How to execute RawSql against the context

I currently have a project that I'm working on, which has a database connected to it. In said database I need to query some tables that don't have a relationship. I need to get a specific set of data in order to display it on my user interface. However I need to be able to reference the returned data put it into a list and convert it into json. I have a stored procedure that needs to just be executed against the context because it's retrieving data from many different tables.
I've tried using ExecuteSqlCommand but that doesn't work, because it returns -1 and can't put it into a list.
I've tried using linq to select the columns I want however it's really messy and I cannot retrieve the data as easily.
I've tried using FromSql, however that needs a model to execute against the context which is exactly what I don't want.
public string GetUserSessions(Guid memberId)
{
string sql = $"EXECUTE dbo.GetUserTrackByMemberID #p0";
var session = _context.Database.ExecuteSqlCommand(sql, memberId);
var json = JsonConvert.SerializeObject(session);
return json;
}
This is the ExecuteSqlCommand example, this returns -1 and cannot be put into a list as there will be more than one session.
public string GetUserSessions(Guid memberId)
{
var session = _context.MemberSession.Where(ms => ms.MemberId == memberId).Select(s => new Session() { SessionId =
s.SessionId, EventId = s.Session.EventId, CarCategory = s.Session.CarCategory, AirTemp = s.Session.AirTemp,
TrackTemp = s.Session.TrackTemp, Weather = s.Session.Weather, NumberOfLaps = s.Session.NumberOfLaps, SessionLength = s.Session.SessionLength,
Event = new Event() { EventId = s.Session.Event.EventId, TrackId = s.Session.Event.TrackId, Name = s.Session.Event.Name, NumberOfSessions =
s.Session.Event.NumberOfSessions, DateStart = s.Session.Event.DateStart, DateFinish = s.Session.Event.DateFinish, TyreSet = s.Session.Event.TyreSet,
Track = new Track() { TrackId = s.Session.Event.Track.TrackId, Name = s.Session.Event.Track.Name, Location = s.Session.Event.Track.Location, TrackLength
= s.Session.Event.Track.TrackLength, NumberOfCorners = s.Session.Event.Track.NumberOfCorners} } });
var json = JsonConvert.SerializeObject(session);
return json;
}
This is using Linq, however it's really messy and I feel there's probably a better way to do this, and then when retrieving the data from json it's a lot bigger pain.
public string GetUserSessions(Guid memberId)
{
var session = _context.MemberSession.FromSql($"EXECUTE dbo.GetUserSessionByMemberID {memberId}").ToList();
var json = JsonConvert.SerializeObject(session);
return json;
}
This is the ideal way I would like to do it, however since I'm using the MemberSession model it will only retrieve that data from the stored procedure which is in the MemberSession table, however I want data that is in other tables as well....
public string GetUserSessions(Guid memberId)
{
var session = _context.MemberSession.Where(ms => ms.MemberId == memberId).Include("Session").Include("Event").ToList();
var json = JsonConvert.SerializeObject(session);
return json;
}
I tried this way but because the Event table has no reference / relationship to MemberSession it returns an error.
As I've previously stated in the RawSql example I'm only getting the table data that is in the MemberSession table, no other tables.
There are no error messages.
using (var context = new DBEntities())
{
string query = $"Exec [dbo].[YOUR_SP]";
List<ResponseList> obj = context.Database.SqlQuery<ResponseList>(query).ToList();
string JSONString = JsonConvert.SerializeObject(obj);
}

C#/Entity Framework: locally add script to model

My team have read-access to a database maintained by a different team. We have a number of scripts that only we run, and so they've never been added as sprocs to the database (nor do we want them to be).
In Entity Framework 6, is it possible to include a file in my model which contains a stored procedure, so that we can leverage the code generation in EF?
We'd much rather have our code look like:
using (var db = new DatabaseEntities())
{
var properlyTypedResult = db.GetEntriesThatStartWith(firstName);
}
than:
using (var db = new DatabaseEntities())
{
var rawResult = db.Database.ExecuteSqlCommand("SELECT * FROM dbo.Person WHERE FirstName LIKE '#p0%'", firstName);
var properlyTypedResult = CastAppropriately(rawResult);
}
The functionality appears to be missing, but I thought I'd check regardless, I'd expect it to be in the designer view,
right-click, Add New -> Function Import...
... but this only allows adding sprocs to the model that are already in the database.
I think you're forgetting about LINQ - the second example would be something like...
List<People> people = (List<People>)db.Person.Where(f => f.FirstName.StartsWith(firstname)).ToList();
This should be close to what you're looking for. Linq is your friend.
I couldn't find exactly what I was after. I decided to simply write my own code generation, and leverage as much of Entity Framework as I could.
With query string in hand, execute against the database appropriately, using a SqlDataAdapter, with a DataTable
e.g.,
using (var context = new DbContext())
{
var dataTable = new DataTable();
var connection = (SqlConnection)context.Database.Connection;
if (connection != null && connection.State == ConnectionState.Closed)
connection.Open();
using (var adapter = new SqlDataAdapter(queryString, connection))
adapter.Fill(dataTable);
}
The DataTable contains the resulting column names along with all their types, now all we have to do is generate the code for the object.
i.e.,
var objectBuilder = new StringBuilder();
objectBuilder.AppendLine("public class QueryResult");
objectBuilder.AppendLine("{");
foreach (DataColumn column in dataTable.Columns)
{
objectBuilder.AppendLine(String.Format("public {0} {1} { get; set; }", column.DataType.Name, column.ColumnName));
}
objectBuilder.AppendLine("}");
Finally, create an extension method on the context object:
i.e.,
private static string GetQueryString(string firstName)
{
return String.Format($"SELECT * FROM dbo.Person WHERE FirstName LIKE '{firstName}%'", firstName);
}
public static partial class DbContextExtensions
{
public static List<QueryResult> GetEntriesThatStartWith(this DbContext context, string firstName)
{
return context.Database.SqlQuery<QueryResult>(GetQueryString(firstName)).ToList();
}
}
Now, we can use this as a regular sproc call:
using (var db = new DbContext())
{
var properlyTypedResult = db.GetEntriesThatStartWith(firstName);
}

How to return first 50 characters of text in LINQ call

I have a small winapp that uses LinqToSQL as it's DAL. I am creating a summary view of all the CaseNotes for a given person and one of the fields is a Details box. I need to return only the first 50 characters of that column to my treeview function.
Any hints on how I do that? The below is how my TreeView function gets its data for display and the ContactDetails is the column in question.
public static DataTable GetTreeViewCNotes(int personID)
{
var context = new MATRIXDataContext();
var caseNotesTree = from cn in context.tblCaseNotes
where cn.PersonID == personID
orderby cn.ContactDate
select new { cn.CaseNoteID,cn.ContactDate, cn.ParentNote, cn.IsCaseLog, cn.ContactDetails };
var dataTable = caseNotesTree.CopyLinqToDataTable();
context.Dispose();
return dataTable;
}
ANSWER
I am posting this here in case any future searchers wonder what the solution looks like in the questions context.
public static DataTable GetTreeViewCNotes(int personID)
{
DataTable dataTable;
using (var context = new MATRIXDataContext())
{
var caseNotesTree = from cn in context.tblCaseNotes
where cn.PersonID == personID
orderby cn.ContactDate
select new
{
cn.CaseNoteID,
cn.ContactDate,
cn.ParentNote,
cn.IsCaseLog,
ContactDetailsPreview = cn.ContactDetails.Substring(0,50)
};
dataTable = caseNotesTree.CopyLinqToDataTable();
}
return dataTable;
}
String.Substring:
var caseNotesTree = from cn in context.tblCaseNotes
where cn.PersonID == personID
orderby cn.ContactDate
select new {
cn.CaseNoteID,
cn.ContactDate,
cn.ParentNote,
cn.IsCaseLog,
ContactDetailsClip = cn.ContactDetails.Substring(0, Math.Min(cn.ContactDetails.Length, 50))
};
Also, I would suggest wrapping your use of DataContexts in using blocks.
With LinQ you can also do the following :
new string( myString.Take(50).ToArray() );
cn.ContactDetails.Substring(0, 50);
In the "select new" line. Does that work?
LINQ syntax
string message=new string(myString.Take(50).ToArray());

Categories

Resources