Map a column with string representing a list to a List<> object using Dapper - sql-server

I have the following model:
public class Model {
public string Name { get; set; }
public List<int> Numbers { get; set; }
}
And an SQL query that returns the following dataset containing two nvarchar columns:
Name
Numbers
foo
1,2,3,4
bar
4,17
Is there a simple way to auto-assign the results of the query to a List<Model> using Dapper?
I know I could use multi-mapping and make the splitting myself in C# code, but I would rather get a simpler solution.

I'm not sure if you can call this "simpler", but something like this is an option:
public class Result
{
public string Name { get; set; }
public List<int> Numbers { get; set; }
}
public class DapperTests
{
[Test]
public void Test()
{
var conn = new SqlConnection(#"Data Source=.\sqlexpress; Integrated Security=true; Initial Catalog=mydb");
conn.Open();
var result = conn.Query<string, string, Result>(
"select Name = 'Foo', Numbers = '1,2,3' union all select Name = 'Bar', Numbers = '4,5,6'", (a, b) => new Result
{
Name = a,
Numbers = b.Split(',').Select(Int32.Parse).ToList()
}, splitOn: "*").ToList();
Assert.That(result.Count, Is.EqualTo(2));
Assert.That(result.FirstOrDefault(x => x.Name == "Foo").Numbers.Count, Is.GreaterThan(0));
Assert.That(result.FirstOrDefault(x => x.Name == "Bar").Numbers.Count, Is.GreaterThan(0));
}
}

An alternative option with multimapping... pretty ugly
public class Result
{
public string Name { get; set; }
public List<int> NumberList { get; set; }
public string Numbers { set { NumberList = value.Split(',').Select(Int32.Parse).ToList(); } }
}
public class DapperTests
{
[Test]
public void Test()
{
var conn = new SqlConnection(#"Data Source=.\sqlexpress; Integrated Security=true; Initial Catalog=mydb");
conn.Open();
var sql = #"
select Name = 'Foo', Numbers = '1,2,3';
select Name = 'Bar', Numbers = '4,5,6';";
var expectedResults = 2;
var results = new List<Result>();
using (var multi = conn.QueryMultiple(sql))
{
for (int i = 0; i < expectedResults; i++)
{
results.Add(multi.Read<Result>().Single());
}
}
Assert.That(results.Count, Is.EqualTo(2));
Assert.That(results.FirstOrDefault(x => x.Name == "Foo").NumberList.Count, Is.GreaterThan(0));
Assert.That(results.FirstOrDefault(x => x.Name == "Bar").NumberList.Count, Is.GreaterThan(0));
}
}

Related

'There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'InternID'.'

i'm new to ASP.NET MVC
so i've been trying for the last week to create a dropdown list from sql server to view a list of the interns Names
I've tried various & different ways to solve it, i checked most of the stackoverflow solutions related to the same issue, but all is leading to is the same error 'There is no ViewData item of type 'IEnumerable' that has the key 'InternID'.'
So my question is, What am i missing?
this is my main code
Controller
public ActionResult AddTask(Add_TaskModel Add_Task, HttpPostedFileBase postedFile) {
var InternList = entities.InternsTbls.ToList();
ViewBag.internList = new SelectList(InternList, "InternID","Name");
return View();}
View
#Html.DropDownListFor(m => m.InternID, ViewBag.internList as SelectList, "--Select The Intern", new { #class = "form-cotrnol"})
Model
public class Add_TaskModel
{
[Key]
public int TaskID { get; set; }
public string TaskTitle { get; set; }
public string TaskType { get; set; }
public string TaskDes { get; set; }
public string DueDate { get; set; }
public int SupID { get; set; }
public string TaskStatus { get; set; }
public int FileID { get; set; }
public int InternID { get; set; }
public string Name { get; set; }
}
Note- this one of many ways i tried, but same error.
Code 2
//var myList = new List<SelectListItem>();
//var list = entities.InternsTbls.ToList();
//var items = from g in list
// select new SelectListItem
// {
// Value = g.InternID.ToString(),
// Text = g.Name.ToString()
// };
//foreach (var item in items)
// myList.Add(item);
//ViewBag["In_List"] = myList;
Code 3
//string mainconn = ConfigurationManager.ConnectionStrings["TaskTrackerDBEntities"].ConnectionString;
//if (mainconn.ToLower().StartsWith("metadata="))
//{
// System.Data.Entity.Core.EntityClient.EntityConnectionStringBuilder efBuilder = new System.Data.Entity.Core.EntityClient.EntityConnectionStringBuilder(mainconn);
// mainconn = efBuilder.ProviderConnectionString;
//}
//using (SqlConnection sqlconn = new SqlConnection(mainconn))
//{
// string sqlquery = "select * from [dbo].[InternsTbl]";
// using (SqlCommand sqlcomm = new SqlCommand(sqlquery, sqlconn))
// {
// sqlconn.Open();
// SqlDataAdapter sda = new SqlDataAdapter(sqlcomm);
// DataSet ds = new DataSet();
// sda.Fill(ds);
// ViewBag.internname = ds.Tables[0];
// List<SelectListItem> getinternname = new List<SelectListItem>();
// foreach (System.Data.DataRow dr in ViewBag.internname.Rows)
// {
// getinternname.Add(new SelectListItem { Text = #dr["Name"].ToString(), Value = #dr["Name"].ToString() });
// }
// ViewData ["Interns"] = getinternname;
// //new SelectList(getinternname, "Name", "Name");
// sqlconn.Close();
// }
//}
Code 4
//TaskTrackerDBEntities entities1 = new TaskTrackerDBEntities();
//var getInternName = entities1.InternsTbls.ToList();
//SelectList list = new SelectList(getInternName, "InternID", "Name");
// ViewBag["InternListName"] = list;
//ViewBag.SubNames = new SelectList(entities1.InternsNames, "InternID", "Name");

Multiple return sets from stored procedure .NET Core & Entity Framework

I want to load 3 different models from a SQL Server stored procedure which returns 3 different tables, like:
select A.id, A.Name from tableA A
select B.id, B.Age from tableB B
select C.Test, C.Param from tableC C
Usually, I would handle a single result stored procedure with Entity Framework Core like this:
Context:
public virtual DbQuery<StoredProcedureModel> spModel{ get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Query<StoredProcedureModel>(entity =>
{
entity.Property(e => e.Id)
.HasColumnName("Id");
entity.Property(e => e.Name)
.HasColumnName("Name");
}
}
Repository:
return _context.StoredProcedureModel
.FromSql<StoredProcedureModel>(
"GET_ID_NAME #ID,#NAME",
new SqlParameter("#ID", ID),
new SqlParameter("#NAME", Name))
.ToList();
This is just a dummy example, but I wanted to know if there's a way to load all 3 tables into 3 different models (some of the tables returned have the same column names like "id").
Currently it is not possible with Entitiy-Framework but there is a FeatureRequest.
As a workaround, according to this blog post there is a solution with ADO.Net:
First code your classes:
public class TableA
{
public int Id { get; set; }
public string Name { get; set; }
}
public class TableB
{
public int Id { get; set; }
public int Age { get; set; }
}
public class TableC
{
public string Test { get; set; }
public string Param { get; set; }
}
public class StoredProcedureResult
{
public List<TableA> TableAEntries { get; set; }
public List<TableB> TableBEntries { get; set; }
public List<TableC> TableCEntries { get; set; }
}
Afterwards code the following function into your CustomDbContext class:
public async Task<StoredProcedureResult> GetStoredProcedureResult(int id, string name)
{
var connection = Database.GetDbConnection();
await connection.OpenAsync();
var command = connection.CreateCommand();
command.CommandText = "GET_ID_NAME #ID, #NAME";
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add(new Microsoft.Data.SqlClient.SqlParameter("#ID", id));
command.Parameters.Add(new Microsoft.Data.SqlClient.SqlParameter("#NAME", name));
var reader = await command.ExecuteReaderAsync();
var tableAEntries = new List<TableA>();
var tableBEntries = new List<TableB>();
var tableCEntries = new List<TableC>();
while (await reader.ReadAsync())
{
tableAEntries.Add(new TableA
{
Id = reader.GetInt32("id"),
Name = reader.GetString("name"),
});
}
await reader.NextResultAsync();
while (await reader.ReadAsync())
{
tableBEntries.Add(new TableB
{
Id = reader.GetInt32("id"),
Age = reader.GetInt32("Age"),
});
}
await reader.NextResultAsync();
while (await reader.ReadAsync())
{
tableCEntries.Add(new TableC
{
Test = reader.GetString("Test"),
Param = reader.GetString("Param"),
});
}
var storedProcedureResult = new StoredProcedureResult();
storedProcedureResult.TableAEntries = tableAEntries;
storedProcedureResult.TableBEntries = tableBEntries;
storedProcedureResult.TableCEntries = tableCEntries;
await reader.CloseAsync();
return storedProcedureResult;
}

BindingList<> (master) with a composed BindingList<> (child) reference

I have a situation where a BindingList<> represents a collection of POCOs that have sub-collections of similar nature, Here is a sample code of two such POCOs and their respective lists:
The DirectoryTypePoco
public class DirectoryTypePoco : IBasePoco
{
public DirectoryTypePoco()
{
}
public DirectoryTypePoco(Int16 directoryTypeId, String directoryImplementation, String directoryDescription, DirectoryDefinitionPocoList directoryDefinition)
{
DirectoryTypeId = directoryTypeId;
DirectoryImplementation = directoryImplementation;
DirectoryDescription = directoryDescription;
DirectoryDefinition = directoryDefinition;
}
public Int16 DirectoryTypeId { get; set; }
public String DirectoryImplementation { get; set; }
public String DirectoryDescription { get; set; }
public DirectoryDefinitionPocoList DirectoryDefinition { get; set; }
public object GenerateEntity(GenericRepository repository, params object[] parameters)
{
var lastMaxEntityId = repository.GetQuery<DirectoryType>().Select(select => #select.DirectoryTypeId).DefaultIfEmpty().Max();
var newEntity = new DirectoryType
{
DirectoryTypeId = (short)(lastMaxEntityId + 1),
DirectoryImplementation = this.DirectoryImplementation,
DirectoryDescription = this.DirectoryDescription
};
return newEntity;
}
}
And the BindingList<DirectoryTypePoco>:
public class DirectoryTypePocoList : BindingList<DirectoryTypePoco>
{
public DirectoryTypePocoList()
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryType>();
foreach (var r in query)
{
Add(new DirectoryTypePoco(r.DirectoryTypeId, r.DirectoryImplementation, r.DirectoryDescription, new DirectoryDefinitionPocoList(r.DirectoryTypeId)));
}
}
}
public DirectoryTypePocoList(short directoryTypeId)
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryType>(where => where.DirectoryTypeId == directoryTypeId);
foreach (var r in query)
{
Add(new DirectoryTypePoco(r.DirectoryTypeId, r.DirectoryImplementation, r.DirectoryDescription, new DirectoryDefinitionPocoList(r.DirectoryTypeId)));
}
}
}
}
The second object: DirectoryDefinitionPoco
public class DirectoryDefinitionPoco : IBasePoco
{
public DirectoryDefinitionPoco()
{
}
public DirectoryDefinitionPoco(Int16 directoryTypeId, Byte parameterId, String parameterName, String parameterValidation, Boolean encryptionRequired, PocoChangeType changeType = PocoChangeType.None)
{
DirectoryTypeId = directoryTypeId;
ParameterId = parameterId;
ParameterName = parameterName;
ParameterDescription = parameterName;
ParameterRequired = false;
ParameterValidation = parameterValidation;
EncryptionRequired = encryptionRequired;
}
public Int16 DirectoryTypeId { get; set; }
public Byte ParameterId { get; set; }
public String ParameterName { get; set; }
public String ParameterDescription { get; set; }
public String ParameterValidation { get; set; }
public Boolean ParameterRequired { get; set; }
public Boolean EncryptionRequired { get; set; }
public object GenerateEntity(GenericRepository repository, params object[] parameters)
{
var masterId = (short) parameters[0];
var lastMaxEntityId = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == masterId).Select(select => #select.ParameterId).DefaultIfEmpty().Max();
var newEntity = new DirectoryDefinition
{
DirectoryTypeId = (short)parameters[0],
ParameterId = (byte)(lastMaxEntityId + 1),
ParameterName = this.ParameterName,
ParameterDescription = this.ParameterDescription,
ParameterValidation = this.ParameterValidation,
ParameterRequired = this.ParameterRequired,
EncryptionRequired = this.EncryptionRequired
};
return newEntity;
}
}
And BindingList<DirectoryDefinitionPoco>:
public class DirectoryDefinitionPocoList : BindingList<DirectoryDefinitionPoco>
{
public DirectoryDefinitionPocoList(short directoryTypeId)
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == directoryTypeId);
foreach (var r in query)
{
Add(new DirectoryDefinitionPoco(r.DirectoryTypeId, r.ParameterId, r.ParameterName, r.ParameterValidation, r.EncryptionRequired));
}
}
}
public List<DirectoryDefinition> GetSourceQuery()
{
List<DirectoryDefinition> result;
using (var repository = new GenericRepository(new PWRDbContext()))
{
result = repository.GetQuery<DirectoryDefinition>().ToList();
}
return result;
}
public List<DirectoryDefinition> GetSourceQuery(short directoryTypeId)
{
List<DirectoryDefinition> result;
using (var repository = new GenericRepository(new PWRDbContext()))
{
result = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == directoryTypeId).ToList();
}
return result;
}
}
On the form, I load the data into the grid through a BindingSource component. The child rows are added properly and the data is valid.
Here is the issue: I'm able to add new DirectoryTypePoco but when try to add a DirectoryDefinitionPoco, in the code, the the DirectoryDefinitionPocoobject that I get has a zero for it's parent object. In the above picture, the Test5.dll234 is a DirectoryTypePoco with DirectoryTypeId = 8 and all child under it are ok except the new one I create. What am I suppose to do to make sure I have Master-Child relation in this case?
Ok. It seems that there are two thing I should have noticed in my design.
The individual child Poco needs to know the parent Poco through a reference.
The DevExpress Grid has methods that allow for retrieving the attached data to a parent row while in the child view' particular row.
The first part is straightforwards: add a new property in the child poco of parent poco type.
This however, in my case, doesn't solve my issue as when I visually add a new row on the grid, the default constructor is invoked and it takes no parameters and hence the parent poco reference will remain NULL and the Ids (numeric) will be defaulted to 0
The second point helped fix my issue completely. I was able to conjure up an extension method for the XtraGrid's GridView as follows:
public static class DevExpressGridHelper
{
public static IBasePoco GetPocoFromSelectedRow(this BaseView view)
{
return (IBasePoco)view.GetRow(((GridView)view).FocusedRowHandle);
}
public static IBasePoco GetParentPocoFromSelectedRow(this GridView view)
{
if (view.ParentView !=null)
{
// return (IBasePoco)(view.ParentView).GetRow(((GridView)(view.ParentView)).FocusedRowHandle);
return (IBasePoco)((GridView)view.ParentView).GetFocusedRow();
}
return null;
}
}
And used it as follows:
private void GridMain_Level_1_RowUpdated(object sender, RowObjectEventArgs e)
{
var view = sender as GridView;
if (view == null)
{
return;
}
var pocoObject = e.Row as DirectoryDefinitionPoco;
if (pocoObject == null)
{
return;
}
var parentPocoObject = view.GetParentPocoFromSelectedRow();
if (parentPocoObject == null)
{
return;
}
if (view.IsNewItemRow(e.RowHandle))
{
Create(pocoObject, parentPocoObject);
}
else
{
Update(pocoObject);
}
}

Dapper Multi-map next level

I'm using multiple mapping for a current query and now I need to map another object on the initial query.
For example:
public class Part {
public int Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address {
public int Id { get; set; }
public string Street { get; set; }
public SiteOu Ou { get; set; }
}
public class SiteOu
public int Id { get; set; }
public string Name { get; set; }
}
Dapper:
connection.Query<Part, Address, Part>(sql, (part, address) => {
part.Address = address;
});
How do I get the Address class to have the SiteOu information?
This example isn't what I'm actually doing because I've actually got
Query<T1,T2,T3,T4,T5,TResult>();
I'm doing 1 select and 5 joins in my query. So hopefully I don't need more overloads of Query.
Dapper allows you to map a single row to multiple objects, so you can just map SiteOu as part of the same query.
[Test]
public void TestSplitOn()
{
var conn = new SqlConnection(#"Data Source=.\SQLEXPRESS;Integrated Security=true;Initial Catalog=db");
conn.Open();
const string sql = "select Id = 1, Name = 'My Part', " +
"Id = 2, Street = 'My Street', " +
"Id = 3, Name = 'My Site'";
var result = conn.Query<Part, Address, SiteOu, Part>(sql, (part, address, siteOu) =>
{
part.Address = address;
address.Ou = siteOu;
return part;
},
commandType: CommandType.Text
).FirstOrDefault();
Assert.That(result, Is.Not.Null);
Assert.That(result.Address, Is.Not.Null);
Assert.That(result.Address.Ou, Is.Not.Null);
}
Important Note: Dapper assumes your Id columns are named "Id" or "id", if your primary key is different or you would like to split the wide row at point other than "Id", use the optional 'splitOn' parameter.
If you have more that 5 types to map, another out of the box option is to use QueryMultiple extension. Here is an example from the Dapper docs.
var sql =
#"
select * from Customers where CustomerId = #id
select * from Orders where CustomerId = #id
select * from Returns where CustomerId = #id";
using (var multi = connection.QueryMultiple(sql, new {id=selectedId}))
{
var customer = multi.Read<Customer>().Single();
var orders = multi.Read<Order>().ToList();
var returns = multi.Read<Return>().ToList();
...
}
Also check out this thread.

Fill data in ObservableCollection using LINQ

I have a structure as below.
public class CategoryClass
{
public decimal Category_ID { get; set; }
public string Category_Name { get; set; }
//public System.Nullable<char> _Category_Type;
public ObservableCollection<DAL.SubCategoryClass> SubCat { get; set; }
}
public class SubCategoryClass
{
public decimal Sub_Category_ID { get; set; }
public string Sub_Category_Name { get; set; }
public decimal Category_ID { get; set; }
}
I need to fill data using LINQ.
I have some code here, please correct me to solve it.
public ObservableCollection<DAL.CategoryClass> GetCategoryandSubCategory()
{
var cat = from c in dbc.Categories
select new DAL.CategoryClass
{
Category_ID = c.Category_ID,
Category_Name = c.Category_Name,
SubCat = from d in dbc.Sub_Categories
where d.Category_ID == c.Category_ID
select new DAL.SubCategoryClass
{
Sub_Category_ID = d.Sub_Category_ID,
Sub_Category_Name = d.Sub_Category_Name,
Category_ID = d.Category_ID
}
};
}
Also suggest me some examples of validation tech in WPF.
One option is to just return an IEnumerable.
That may serve your needs.
If you need an ObservableCollection then you need to new it.
For List just .ToList()
As for validation one question at time.
Start with searching MSDB for validation.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
System.Diagnostics.Debug.WriteLine(OCint.Count.ToString());
}
List<SimpleClass> baseList = new List<SimpleClass> { new SimpleClass(1), new SimpleClass(2), new SimpleClass(3) };
public IEnumerable<SimpleClass> iEint
{
get { return baseList.Where(x => x.ID < 3).Select(w=> new SimpleClass(w.ID)); }
}
public List<SimpleClass> Lint
{
get { return iEint.ToList(); ; }
}
public ObservableCollection<SimpleClass> OCint
{
get { return new ObservableCollection<SimpleClass>(iEint); }
}
}
public class SimpleClass
{
public Int32 ID { get; private set; }
public SimpleClass ( Int32 id) { ID = id; }
}
public IEnumerable<DAL.CategoryClass> GetCategoryandSubCategory()
{
ObservableCollection<DAL.SubCategoryClass> s = new ObservableCollection<DAL.SubCategoryClass>();
var cat = from c in dbc.Categories
select new DAL.CategoryClass
{
Category_ID = c.Category_ID,
Category_Name = c.Category_Name,
SubCat =s.Add( from d in dbc.Sub_Categories
where d.Category_ID == c.Category_ID
select new DAL.SubCategoryClass
{
Sub_Category_ID = d.Sub_Category_ID,
Sub_Category_Name = d.Sub_Category_Name,
Category_ID = d.Category_ID
}
};).ToList()
return cat ;
}
hope it work with u
If you want GetCategoryandSubCategory() returns ObservableCollection that is refreshed automatically when dbc.Categories changes or CategoryClass (SubCotegory) properties change, you can use my ObservableComputations library. Using that library you can code:
public ObservableCollection<DAL.CategoryClass> GetCategoryandSubCategory()
{
var cat = dbc.Categories.Selecting(c =>
new DAL.CategoryClass
{
Category_ID = c.Category_ID,
Category_Name = c.Category_Name,
SubCat = dbc.Sub_Categories
.Filtering(d => d.Category_ID == c.Category_ID)
.Selecting(d => new DAL.SubCategoryClass
{
Sub_Category_ID = d.Sub_Category_ID,
Sub_Category_Name = d.Sub_Category_Name,
Category_ID = d.Category_ID
})
});
}
To make code above working dbc.Categories and dbc.Sub_Categories must be of type ObservableCollection and all the classes mentioned in the code must implement INotifyPropertyChanged.

Resources