C#/Entity Framework: locally add script to model - c#

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);
}

Related

Is it possible to create a parallel entry in Linked Table per Type In EF6.x using code first

Consider the following database structure (created using EF Code First)
Using simple code as illustrated below I can easily add either new Customers or Suppliers;
private static void InsertCustomers()
{
var customer1 = new Customer
{
FirstName = "Mickey",
LastName = "Mouse",
DateStarted = DateTime.Now
};
var customer2 = new Customer
{
FirstName = "Fred",
LastName = "Flintstone",
DateStarted = DateTime.Now
};
using (var context = new ContactsContext())
{
context.Database.Log = Console.Write; //purely for logging
context.Customers.Add(customer1);
context.Customers.Add(customer2);
context.SaveChanges();
}
}
My question is simple. Fred Flintstone could at some point in the future become a supplier as well as remaining a Customer. From within SSMS I can easily achieve this but I would obviously like to do this from within the application itself. If I use the syntax
var sup = new Supplier();
then the underlying logic will create a new Id (which is perfectly sensible, but undesired as I want to use the existing Id assigned to Fred Flinstone as a Customer which is 2) so how do I in effect add an existing Contacts.Id into the Suppliers table so that it becomes a Primary / foreign key using code in my application.
Well it would transpire that this is not as intuitive in Code First as one might have thought so I have reverted to using the following which does work as intended;
static void CreateSupplier(int id)
{
var contactId = id; //the id being passed in would have been collected from a list of Customers, as we want Fred Flintstone it would have been set to 2
var date = DateTime.UtcNow;
using (var context = new ContactsContext())
{
context.Database.Log = Console.Write; //purely for logging
context.Database.ExecuteSqlCommand(
"INSERT INTO Contacts.Suppliers(Id, DateStarted) VALUES({0}, {1})", contactId, date);
context.SaveChanges();
}
}
It's possible that there is a way to do this with a lambda but at present I haven't found one.

Automatically Filter DataTable

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.

C#: Cannot implicitly convert type

I am trying to access a stored procedure that retrieves a page using an id.
But I am getting an error:
Error 1 Cannot implicitly convert type
'System.Data.Entity.Core.Objects.ObjectResult<StorePageCMS.Models.mn_StorePage_Select_One_Result>' to 'StorePageCMS.Models.StorePage'
I am not sure how to fix this. The stored precedure that comes from dbEntities from SQL Server, does take an int parameter.
Any help is much appreciated.
public StorePage Get(int StorePageID)
{
using (dbEntities db = new dbEntities())
{
StorePage storepage = db.mn_StorePage_Select_One(StorePageID);
if (storepage == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return storepage;
}
}
UPDATE
I rewrote the method this way:
public List<StorePage> Get(int StorePageID)
{
List<StorePage> storepagelist = new List<StorePage>();
using (dbEntities db = new dbEntities())
{
var results = db.mn_StorePage_Select_One(StorePageID).ToList();
foreach (var result in results)
{
var storepage = new StorePage()
{
StorePageID = result.StorePageID,
SPPreambleID = result.SPPreambleID,
Title = result.Title,
SEOTitle = result.SEOTitle,
ParentStorePageID = result.ParentStorePageID ?? -1,
Meta = result.Meta,
Image = result.Image,
ImageLink = result.ImageLink,
Blurb = result.Blurb,
RegionID = result.RegionID,
Footer = result.Footer
};
storepagelist.Add(storepage);
}
return storepagelist;
}
}
Does this looks more correct?
2 UPDATE
Does this looks correct?
If you're not using the Code First model of Entity Framework
Since StorePageCMS.Models.mn_StorePage_Select_One_Result has no conversion to StorePage, I'm assuming it's a stored procedure result. If that's a stored procedure result (of mn_StorePage_Select_One), you need to map it's result to the StorePage model instead in the EDMX designer.
Here, you'd need to say it returns a collection of StorePageCMS.Models.StorePage Entities.

Can't access related data after disposal of DataContext in Linq to SQL

Restated my Question, old Text Below
As I am still hoping for an answer I would like to restate my question. Image I have a GUI with two lists, one that shows a List of all entries to a database tblOrders and another one that shows the items in each order.
I can use Linq2sql or EF to get all orders from the database, like so:
using (DataClasses1DataContext DataContext = new DataClasses1DataContext())
ListOfOrders = DataContext.tblOrder.ToList();
I can display these order in a list or datagridview. Then on a selection of one of the jobs I want to access the collection of entities from the table tblItems. I can do this like so:
ListOfOrders.First().tblItems.toList();
Except I can not, because I need the DataContext which has been disposed. This makes sense in a way, since I can not guarantee that there is not a new items that has been added to that order since I retrieved the ListOfOrders. So ideally I would like to check if there has been an addition to a tblOrder.tblItems collection and only newly retrieve that collection from the server if required.
The background is, that my model is a bit more complex: It consists of Orders, which consist of Parts, which consist of Tasks. So to assess the progress of each order I have to retrieve all parts that belong to an order and for each of them I have to see how many of the tasks have been completed. In a database with 200 job, each with 1 to 10 parts this simply makes my program to slow in terms of responsiveness ...
Can anyone help me?
Original Question
I found a lot of questions concerning DataContext, but I have not found a solution to my problem yet.
If I do the following:
using (DataClasses1DataContext DataContext = new DataClasses1DataContext())
ListOfOrders = DataContext.tblOrder.ToList();
This gives me a list of the entities in the tblOrder table.
But now I want to do this:
DataTable Orders = new DataTable();
Orders.Columns.Add("Order-ID", typeof(int));
Orders.Columns.Add("Order-Name", typeof(string));
Orders.Columns.Add("Order-Items", typeof(string));
dataGridView1.DataSource = Orders;
foreach (tblOrder Order in ListOfOrders)
{
var newRow = Orders.NewRow();
newRow["Order-ID"] = Order.orderID;
newRow["Order-Name"] = Order.orderName;
newRow["Order-Items"] = string.Join(", ", Order.tblItem.Select(item=> item.itemName).ToList());
// System.ObjectDisposedException
(dataGridView1.DataSource as DataTable).Rows.Add(newRow);
}
And I can not because accessing all entities in the tblItem table that are related to orders by foreign key don't seem to be stored.
What which is working:
DataClasses1DataContext DataContext = new DataClasses1DataContext();
ListOfOrders = DataContext.tblOrder.ToList();
DataTable Orders = new DataTable();
Orders.Columns.Add("Order-ID", typeof(int));
Orders.Columns.Add("Order-Name", typeof(string));
Orders.Columns.Add("Order-Items", typeof(string));
dataGridView1.DataSource = Orders;
foreach (tblOrder Order in ListOfOrders)
{
var newRow = Orders.NewRow();
newRow["Order-ID"] = Order.orderID;
newRow["Order-Name"] = Order.orderName;
newRow["Order-Items"] = string.Join(", ", Order.tblItem.Select(item=> item.itemName).ToList());
(dataGridView1.DataSource as DataTable).Rows.Add(newRow);
}
DataContext.Dispose();
But as I understand this is not desirable.
EDIT
I extended my example into a Controller-View-Pattern.
using System.Collections.Generic;
using System.Linq;
namespace TestApplication
{
class Controller
{
private List<tblOrder> _orders;
public IList<tblOrder> Orders
{
get
{
return _orders;
}
}
public Controller()
{
using (var DataContext = new DataClasses1DataContext())
{
_orders = DataContext.tblOrder.ToList();
}
}
}
}
And the view now retrieves the Orders from the controller:
using System.Data;
using System.Linq;
using System.Windows.Forms;
namespace TestApplication
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Controller controller = new Controller();
DataTable Orders = new DataTable();
Orders.Columns.Add("Order-ID", typeof(int));
Orders.Columns.Add("Order-Name", typeof(string));
Orders.Columns.Add("Order-Items", typeof(string));
dataGridView1.DataSource = Orders;
foreach (tblOrder Order in controller.Orders)
{
var newRow = Orders.NewRow();
newRow["Order-ID"] = Order.orderID;
newRow["Order-Name"] = Order.orderName;
newRow["Order-Items"] = string.Join(", ", Order.tblItem.Select(item=> item.itemName).ToList());
(dataGridView1.DataSource as DataTable).Rows.Add(newRow);
}
}
}
}
Sadly the problem remains the same ...
Entity Framework lazy-loads object data, meaning it loads the minimum amount of data it has to as late as possible. Take your query:
ListOfOrders = context.tblOrder.ToList();
Here you are requesting all of the records in the tblOrder table. Entity Framework doesn't read ahead in your program and understand that you will be looking at the tblItem table after the context has been disposed, so it assumes it can load the tblItem data later. Being lazy, it loads the bare minimum requested: The list of records in tblOrder.
There are two ways is a way around this:
Disable lazy loading
using (var context = new DataClasses1DataContext())
{
data.Configuration.LazyLoadingEnabled = false;
_orders = context.tblOrder.ToList();
}
With LazyLoadingEnabled=false Entity Framework will select the entire contents of the tblOrder table and all tables connected with it via a foreign key. This may take a while and use a lot of memory, depending on the size and number of related tables.
(Edit: My mistake, disabling LazyLoading does not enable eager loading, and there is no default configuration for eager loading. Apologies for the misinformation. The .Include command below looks like the only way to go.)
Include additional tables
using (var context = new DataClasses1DataContext())
{
data.Configuration.LazyLoadingEnabled = false;
_orders = context.tblOrder.Include("tblItems").ToList();
}
This tells Entity Framework to load all related data from tblItems up front while it's loading the tblOrders table data. EF still doesn't load any data from other related tables, so that other data will be unavailable after the context is disposed.
However, this does not solve the problem of stale data -- that is, over time the records in dataGridView1 will no longer be up-to-date. You could have a button or timer that triggers a refresh. The simplest method of refreshing would be to do the entire process over again -- reload _orders, then selectively repopulate dataGridView1.
I advice you to create new result class, that contains your data:
class Result
{
int ID {get;set;}
string OrderName {get;set;}
IEnumerable<string> ItemNames {get;set;}
}
Select needed data:
class Controller
{
private List<Result> _orders;
public IList<Result> Orders
{
get
{
return _orders;
}
}
public Controller()
{
using (var DataContext = new DataClasses1DataContext())
{
_orders = DataContext.tblOrder.Select(o=>
new Result
{
ID = o.orderID,
OrderName = o.orderName,
ItemNames = o.tblItem.Select(item=> item.itemName)
}).ToList();
}
}
}
And after that bind this collection do grid view. So, you'll get all your data before disposing context and you have no more dependencies.
Just an answer to the first question.
Like EF says, you disposed the context after the [unit of] work (a must in ASP, a good practice in WinForm/WPF).
using (DataClasses1DataContext DataContext = new DataClasses1DataContext())
ListOfOrders = DataContext.tblOrder.ToList();
after this, if you try to run this statement
ListOfOrders.First().tblItems.toList();
EF works in this way:
- get the first element of ListOfOrders that is a proxy to your object.
- try to get the tblItems related with LazyLoad.
At this point, to retrieve the tblItems needs a database access with a context that is retrieved from the proxy of the ListOfOrder first element BUT the context is disposed.
So you can't use this approach.
There are 2 solutions and some variations on them:
- You can read all the tblItems before disposing the context (you can see it in another answer) but is a not scalable approach.
- When you need to access to tblItems you retrieve the Order from a new context and do what you need before disposing it.
In your case
using (DataClasses1DataContext DataContext = new DataClasses1DataContext())
{
int Id = ListOfOrders.First().Id;
var myListOftblItems = DataContext.tblOrder.Find(Id).tblItems.ToList();
}
A variation on this approach is to read only tblItems. You can do this if tblItems has exposed also the foreign key field and not only the navigation property (usually I don't).
using (DataClasses1DataContext DataContext = new DataClasses1DataContext())
{
int Id = ListOfOrders.First().Id;
var myListOftblItems = DataContext.tblItems.Where(t => t.IdOrder == Id).ToList();
}
The default behavior of EF is to Lazy Load entities.
In general this is a good idea and I think you should not disable it if you don't have very good reasons.
EF also provides methods for Eager Loading specified entities:
// Add this!
using System.Data.Entity;
Then you can use the Include method:
public static IList<Order> GetOrdersAndItems()
{
List<Order> orders;
using (var context = new ShopDC())
{
context.Database.Log = Console.WriteLine;
Console.WriteLine("Orders: " + context.Orders.Count());
orders = context.Orders
.Where(o => o.OrderID > 0)
// Tells EF to eager load all the items
.Include(o => o.Items)
.ToList();
}
return orders;
}
Now you can use GetOrdersAndItems to load a list with all your orders and each order will contain all the Items:
public static void Run()
{
IList<Order> disconnectedOrders = GetOrdersAndItems();
foreach (var order in disconnectedOrders)
{
Console.WriteLine(order.Name);
foreach (var item in order.Items)
{
Console.WriteLine("--" + item.Name);
}
}
}
For more examples and multiple level includes take a look here
Note: using a helper or a controller or a repository is not important in the understanding of this mechanism, so I simply used a static method.

How to Update an array with Dapper Extension?

I get from DB my entities' list, change some properties and try to Update in in DB.
using (var cn = new SqlConnection(ConnectionString))
{
cn.Open();
var dataPredicate = Predicates.Field<Data>(f => f.Id, Operator.Eq, new [] {1, 2, 3}); // of course it's for an example
var data = cn.GetList<Data>(dataPredicate);
foreach (var element in data)
{
element.Status = StatusEnum.Pending;
element.LastChange = DateTime.Now;
}
foreach (var activeRequest in data)
{
cn.Update(activeRequest);
}
cn.Close();
}
I tried also:
var updated = data.Select(s => new Data
{
Id = s.Id,
CreateDate = s.CreateDate,
ForeignId = s.ForeignId,
LastChange = DateTime.Now,
Status = RequestStatus.Pending
});
And I get InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
I don't have any problem with another operations.
How can I Update it correctly?
cn.GetList<Data>(dataPredicate) returns IEnumerable and every time you use it with foreach actual sql query gets executed and DataReader is used to provide IEnumerable with data.
So in your case you are executing DataReader twice, and second time you are trying to do Update query on the same connection with DataReader still open.
If this is desirable behaviour (too much data and you do want to load it one by one with DataReader) you should use separate connection for Update queries.
Another option will be to load all the data to list and then iterate trough it, like this:
var data = cn.GetList<Data>(dataPredicate).ToList();

Categories

Resources