Why is NHibernate ignoring FetchMode.Join? - nhibernate

I have an entity called Member. A Member can follow many other Members (according to the domain), thus pertaining a many-to-many relationship. I've created a relationship table (member_follows) in my database. Using Fluent NHibernate I've also dedicated a new entity "MemberFollow" to map this relationship as seen below:
public class MemberMap : MapBase<Member>
{
public MemberMap()
: base()
{
Table("members");
Map(x => x.Id ).Column("id" );
Map(x => x.Fullname).Column("fullname");
}
public class MemberFollowMap : MapBase<MemberFollow>
{
public MemberFollowMap()
: base()
{
Table("members_follows");
Map(x => x.Id).Column("id");
References<Member>(x => x.Follower)
.Column("follower_id")
.Fetch.Join();
References<Member>(x => x.Member)
.Column("member_id");
.Fetch.Join();
}
}
Since the FetchMode for MemberFollow mapping is set to Join, I was expecting this query to fetch the members in one query. However when I look at the logs, I see that NHibernate performs a simple select to find the Ids of each followed member and upon access, loads members one by one.
public IList<Member> ListFollowings(Int32 FollwerId, Int32 Start, Int32 Size, String SortBy, SortOrder OrderBy)
{
DetachedCriteria Filter = DetachedCriteria.For<MemberFollow>();
Filter.Add (Expression.Eq("Follower.Id", FollwerId));
Filter.AddOrder (OrderBy == SortOrder.Asc ? Order.Asc(SortBy) : Order.Desc(SortBy));
Filter.SetProjection (Projections.Property("Member"));
Filter.SetFirstResult(Start);
Filter.SetMaxResults (Size );
return Find<Member>(Filter);
}
So my question is: Why is NHibernate ignoring the FetchMode set by the mapping class?

I think you may take it from the wrong angle. In NHibernate, it is quite unusual to explicitly map a many-to-many relationship as a model object. See below a proposal for changes.
Given the domain object MyMember and its overriden mapping:
public class MyMember : DomainObjectBase
{
public virtual string Name { get; set; }
public virtual IList<MyMember> Follows { get; set; }
}
public class MemberOverride : IAutoMappingOverride<MyMember>
{
public void Override(AutoMapping mapping)
{
mapping.HasManyToMany<MyMember> (x => x.Follows)
.Table("FollowMap")
.ParentKeyColumn("FollowerID")
.ChildKeyColumn("FollowedID")
.Cascade.SaveUpdate(); ;
}
}
The following test pass:
[Test]
public void WhoFollowsWho()
{
var a = new MyMember {Name = "A"};
var b = new MyMember {Name = "B"};
var c = new MyMember {Name = "C"};
var d = new MyMember {Name = "D"};
var e = new MyMember {Name = "E"};
a.Follows = new List<MyMember> { b, c, d, e };
d.Follows = new List<MyMember> { a, c, e };
using (var t = Session.BeginTransaction())
{
Session.Save(a);
Session.Save(b);
Session.Save(c);
Session.Save(d);
Session.Save(e);
t.Commit();
}
using (var t = Session.BeginTransaction())
{
DetachedCriteria followersOfC = DetachedCriteria.For<MyMember>();
followersOfC.CreateCriteria("Follows")
.Add(Expression.Eq("Id", c.Id))
.SetProjection(Projections.Property("Name"));
var results = followersOfC.GetExecutableCriteria(Session).List();
t.Commit();
CollectionAssert.AreEquivalent(new[]{"A", "D"}, results);
}
using (var t = Session.BeginTransaction())
{
DetachedCriteria followedByA = DetachedCriteria.For<MyMember>();
followedByA.CreateAlias("Follows", "f")
.Add(Expression.Eq("Id", a.Id))
.SetProjection(Projections.Property("f.Name"));
var results = followedByA.GetExecutableCriteria(Session).List();
t.Commit();
CollectionAssert.AreEquivalent(new[]{"B", "C", "D", "E"}, results);
}
}
And the produced SQL relies, as expected, on inner joins:
NHibernate:
SELECT this_.Name as y0_ FROM "MyMember" this_
inner join FollowMap follows3_ on this_.Id=follows3_.FollowerID
inner join "MyMember" mymember1_ on follows3_.FollowedID=mymember1_.Id
WHERE mymember1_.Id = #p0
NHibernate:
SELECT f1_.Name as y0_ FROM "MyMember" this_
inner join FollowMap follows3_ on this_.Id=follows3_.FollowerID
inner join "MyMember" f1_ on follows3_.FollowedID=f1_.Id
WHERE this_.Id = #p0
Note: If, instead of only retrieving the "Name" property of each MyMember, you retrieve the full instances of MyMember, the SQL statement keeps the same shape. Only additional projections are added to the SELECT clause. However, you'll have to fix the test to make it pass again ;-)
Note 2: Provided you're willing to deal with a many-to-many relationship which holds properties of its own, this post from Kyle Baley and this one from the Nhibernate blog may provide some help on this subject.
Note 3: I've given it a try :-)
Given the domain objects MySecondMember and MyFollowMap and their overriden mapping:
public class MySecondMember : DomainObjectBase
{
public virtual string Name { get; set; }
public virtual IList<MyFollowMap> Follows { get; set; }
}
public class MyFollowMap : DomainObjectBase
{
public virtual MySecondMember Who { get; set; }
public virtual DateTime StartedToFollowOn { get; set; }
}
public class MemberSecondOverride : IAutoMappingOverride<MySecondMember>
{
public void Override(AutoMapping mapping)
{
mapping.HasMany(x => x.Follows);
}
}
The following test pass:
[Test]
public void WhoFollowsWho2()
{
var a = new MySecondMember { Name = "A" };
var b = new MySecondMember { Name = "B" };
var c = new MySecondMember { Name = "C" };
var d = new MySecondMember { Name = "D" };
var e = new MySecondMember { Name = "E" };
var bfm = new MyFollowMap { Who = b, StartedToFollowOn = DateTime.UtcNow };
var cfm = new MyFollowMap { Who = c, StartedToFollowOn = DateTime.UtcNow };
var dfm = new MyFollowMap { Who = d, StartedToFollowOn = DateTime.UtcNow };
var efm = new MyFollowMap { Who = e, StartedToFollowOn = DateTime.UtcNow };
a.Follows = new List { bfm, cfm, dfm, efm };
var afm = new MyFollowMap { Who = a, StartedToFollowOn = DateTime.UtcNow };
cfm = new MyFollowMap { Who = c, StartedToFollowOn = DateTime.UtcNow };
efm = new MyFollowMap { Who = e, StartedToFollowOn = DateTime.UtcNow };
d.Follows = new List { afm, cfm, efm };
using (var t = Session.BeginTransaction())
{
Session.Save(a);
Session.Save(b);
Session.Save(c);
Session.Save(d);
Session.Save(e);
t.Commit();
}
using (var t = Session.BeginTransaction())
{
DetachedCriteria followersOfC = DetachedCriteria.For<MySecondMember>();
followersOfC.CreateAlias("Follows", "f")
.CreateAlias("f.Who", "w")
.Add(Expression.Eq("w.Id", c.Id))
.SetProjection(Projections.Property("Name"));
var results = followersOfC.GetExecutableCriteria(Session).List();
t.Commit();
CollectionAssert.AreEquivalent(new[] { "A", "D" }, results);
}
using (var t = Session.BeginTransaction())
{
DetachedCriteria followedByA = DetachedCriteria.For<MySecondMember>();
followedByA
.CreateAlias("Follows", "f")
.CreateAlias("f.Who", "w")
.Add(Expression.Eq("Id", a.Id))
.SetProjection(Projections.Property("w.Name"));
var results = followedByA.GetExecutableCriteria(Session).List();
t.Commit();
CollectionAssert.AreEquivalent(new[] { "B", "C", "D", "E" }, results);
}
}
And the produced SQL relies, as expected, on inner joins:
NHibernate:
SELECT this_.Name as y0_ FROM "MySecondMember" this_
inner join "MyFollowMap" f1_ on this_.Id=f1_.MySecondMember_id
inner join "MySecondMember" w2_ on f1_.Who_id=w2_.Id
WHERE w2_.Id = #p0;
NHibernate:
SELECT w2_.Name as y0_ FROM "MySecondMember" this_
inner join "MyFollowMap" f1_ on this_.Id=f1_.MySecondMember_id
inner join "MySecondMember" w2_ on f1_.Who_id=w2_.Id
WHERE this_.Id = #p0

Related

Custom Model as Generic TypeArgument in XAML

I have made a small class, which inherits from DataGrid and takes in classes that derive from a specific interface:
public class RecordDataGrid<T> : DataGrid where T : IRecord
{
public RecordDataGrid()
{
this.AutoGenerateColumns = false;
this.CanUserAddRows = false;
this.CanUserDeleteRows = false;
this.CanUserResizeRows = false;
this.IsReadOnly = true;
this.SelectionMode = DataGridSelectionMode.Single;
this.Margin = new System.Windows.Thickness(0, 10, 0, 0);
var propertyInfos = typeof(T).GetProperties();
var list = new Dictionary<PropertyInfo, DataGridColumnAttribute>();
foreach (var propertyInfo in propertyInfos)
{
var customAttributes = propertyInfo.GetCustomAttributes(true);
foreach (var customAttr in customAttributes)
{
if (customAttr != null && customAttr is DataGridColumnAttribute)
{
list.Add(propertyInfo, (DataGridColumnAttribute)customAttr);
}
}
}
var ordered = (from entry in list orderby entry.Value.OrderIndex ascending select entry).ToDictionary(e => e.Key, e => e.Value);
foreach (var kvp in ordered)
{
var propertyInfo = kvp.Key;
var dgcAttr = kvp.Value;
var column = new DataGridTextColumn();
column.Header = dgcAttr.DisplayName;
column.Binding = new Binding(propertyInfo.Name);
column.Binding.StringFormat = dgcAttr.StringFormat ?? null;
column.Width = dgcAttr.ColumnWidthType == DataGridColumnAttribute.ColumnWidthTypes.Auto ? new DataGridLength(10, DataGridLengthUnitType.Auto) : new DataGridLength(10, DataGridLengthUnitType.Star);
this.Columns.Add(column);
}
}
}
It is very rough at the moment, just testing a few things out. The goal is to make my life easier by letting the DataGrid fill the Columns by itself, based on a custom Attribute:
public class DataGridColumnAttribute : Attribute
{
public string DisplayName { get; private set; }
public string StringFormat { get; private set; }
public ColumnWidthTypes ColumnWidthType { get; private set; }
public int OrderIndex { get; private set; }
public DataGridColumnAttribute(string displayName, int orderIndex, string stringFormat = null, ColumnWidthTypes columnWidthType = ColumnWidthTypes.Auto)
{
DisplayName = displayName;
StringFormat = stringFormat;
OrderIndex = OrderIndex;
ColumnWidthType = columnWidthType;
}
public enum ColumnWidthTypes
{
Auto,
Fill
}
}
Later on, as far as I am concerned, I should be able to use it in xaml like this:
Namespaces:
xmlns:model="clr-namespace:NickX.KswErp.Model.Classes;assembly=NickX.KswErp.Model"
xmlns:ctrl="clr-namespace:NickX.KswErp.ClientApplication.UI.Controls"
Control:
<ctrl:RecordDataGrid x:Name="_gridTransactions" x:TypeArguments="model:TransactionRecord" />
But I get following compilation error:
Only a master tag can specify the "x: TypeArguments" attribute.
(Roughly translated by google translation)
Maybe my approach is completely wrong tho. Should I do it completle in code behind. Or are there better approaches? Please let me know!
Conveniently I just found a thread in a german forum, which answeres my exact question. So people questioning the same in the future:
It is not possible. Easiest thing to do at this point is making a specific class for each model, which again derives from your generic class.
In my case:
public class TransactionDataGrid : RecordDataGrid<TransactionRecord>
{
}
Doesen't seem like a nice solution to me, and probably isn't the best way to do it. But it works.

How to convert DocumentClient to IDocumentClient in gremlin?

I am using cosmos db to store and fetch data. Previously I was using DocumentClient like:
public class ProductRepository : IProductRepository
{
private DocumentClient _documentClient;
private DocumentCollection _graphCollection;
public ProductRepository(DocumentClient documentClient, DocumentCollection graphCollection)
{
_documentClient = documentClient;
_graphCollection = graphCollection;
}
public async Task Create(Product product)
{
var createQuery = CreateQuery(product);
IDocumentQuery<dynamic> query = _documentClient.CreateGremlinQuery<dynamic>(_graphCollection, createQuery);
if(query.HasMoreResults)
{
await query.ExecuteNextAsync();
}
}
public async Task<Product> Get(string id)
{
Product product = null;
var getQuery = #"g.V('" + id + "')";
var query = _documentClient.CreateGremlinQuery<dynamic>(_graphCollection, getQuery);
if (query.HasMoreResults)
{
var result = await query.ExecuteNextAsync();
if (result.Count == 0)
return product;
var productData = (JObject)result.FirstOrDefault();
product = new Product
{
name = productData["name"].ToString()
};
}
return product;
}
}
}
But it is not unit testable so I want to convert it to IDocumentClient but IDocumentClient doesn't contain definition for CreateGremlinQuery. So what is the best possible way to convert my methods so that they will be using IDocumentClient? Do I need to use CreateDocumentQuery? if yes, how can I convert CreateGremlimQuery to CreateDocumentQuery?
There are several ways to get around that. The simplest one would be to simply hard cast your IDocumentClient to DocumentClient.
If you go with that approach your code becomes:
public class ProductRepository : IProductRepository
{
private IDocumentClient _documentClient;
private DocumentCollection _graphCollection;
public ProductRepository(IDocumentClient documentClient, DocumentCollection graphCollection)
{
_documentClient = documentClient;
_graphCollection = graphCollection;
}
public async Task Create(Product product)
{
var createQuery = CreateQuery(product);
IDocumentQuery<dynamic> query = ((DocumentClient)_documentClient).CreateGremlinQuery<dynamic>(_graphCollection, createQuery);
if(query.HasMoreResults)
{
await query.ExecuteNextAsync();
}
}
public async Task<Product> Get(string id)
{
Product product = null;
var getQuery = #"g.V('" + id + "')";
var query = ((DocumentClient)_documentClient).CreateGremlinQuery<dynamic>(_graphCollection, getQuery);
if (query.HasMoreResults)
{
var result = await query.ExecuteNextAsync();
if (result.Count == 0)
return product;
var productData = (JObject)result.FirstOrDefault();
product = new Product
{
name = productData["name"].ToString()
};
}
return product;
}
}
You could also create your own extensions for IDocumentClient.
public static class MoreGraphExtensions
{
public static IDocumentQuery<T> CreateGremlinQuery<T>(this IDocumentClient documentClient, DocumentCollection collection, string gremlinExpression, FeedOptions feedOptions = null, GraphSONMode graphSONMode = GraphSONMode.Compact)
{
return GraphExtensions.CreateGremlinQuery<T>((DocumentClient)documentClient, collection, gremlinExpression, feedOptions, graphSONMode);
}
public static IDocumentQuery<object> CreateGremlinQuery(this IDocumentClient documentClient, DocumentCollection collection, string gremlinExpression, FeedOptions feedOptions = null, GraphSONMode graphSONMode = GraphSONMode.Compact)
{
return GraphExtensions.CreateGremlinQuery<object>((DocumentClient)documentClient, collection, gremlinExpression, feedOptions, graphSONMode);
}
}
It is a pre-release however, so I do think that Microsoft will get around moving the extension methods at the interface level.

EF Core decimal precision for Always Encrypted column

Hello I have SQL server with setting up always encrypted feature, also I setup EF for work with always encrypted columns, but when I try to add/update, for Db manipulation I use DbContext, entry in my Db I get follow error:
Operand type clash: decimal(1,0) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = '****', column_encryption_key_database_name = '****') is incompatible with decimal(6,2) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = '*****', column_encryption_key_database_name = '****')
Model that I use
public class Model
{
/// <summary>
/// Payment method name
/// </summary>
[Column(TypeName = "nvarchar(MAX)")]
public string Name { get; set; }
/// <summary>
/// Payment method description
/// </summary>
[Column(TypeName = "nvarchar(MAX)")]
public string Description { get; set; }
/// <summary>
/// Fee charges for using payment method
/// </summary>
[Column(TypeName = "decimal(6,2)")]
public decimal Fee { get; set; }
}
Also I tried to specify decimal format in OnModelCreating method
builder.Entity<Model>().Property(x => x.Fee).HasColumnType("decimal(6,2)");
What I missed ?
Thanks for any advice
My colleague and I have found a workaround to the problem using the DiagnosticSource.
You must know that:
Entity Framework Core hooks itself into DiagnosticSource.
DiagnosticSource uses the observer pattern to notify its observers.
The idea is to populate the 'Precision' and 'Scale' fields of the command object (created by EFCore), in this way the call made to Sql will contain all the information necessary to correctly execute the query.
First of all, create the listener:
namespace YOUR_NAMESPACE_HERE
{
public class EfGlobalListener : IObserver<DiagnosticListener>
{
private readonly CommandInterceptor _interceptor = new CommandInterceptor();
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(DiagnosticListener value)
{
if (value.Name == DbLoggerCategory.Name)
{
value.Subscribe(_interceptor);
}
}
}
}
Where CommandInterceptor is:
namespace YOUR_NAMESPACE_HERE
{
public class CommandInterceptor : IObserver<KeyValuePair<string, object>>
{
// This snippet of code is only as example, you could maybe use Reflection to retrieve Field mapping instead of using Dictionary
private Dictionary<string, (byte Precision, byte Scale)> _tableMapping = new Dictionary<string, (byte Precision, byte Scale)>
{
{ "Table1.DecimalField1", (18, 2) },
{ "Table2.DecimalField1", (12, 6) },
{ "Table2.DecimalField2", (10, 4) },
};
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(KeyValuePair<string, object> value)
{
if (value.Key == RelationalEventId.CommandExecuting.Name)
{
// After that EF Core generates the command to send to the DB, this method will be called
// Cast command object
var command = ((CommandEventData)value.Value).Command;
// command.CommandText -> contains SQL command string
// command.Parameters -> contains all params used in sql command
// ONLY FOR EXAMPLE PURPOSES
// This code may contain errors.
// It was written only as an example.
string table = null;
string[] columns = null;
string[] parameters = null;
var regex = new Regex(#"^INSERT INTO \[(.+)\] \((.*)\)|^VALUES \((.*)\)|UPDATE \[(.*)\] SET (.*)$", RegexOptions.Multiline);
var matches = regex.Matches(command.CommandText);
foreach (Match match in matches)
{
if(match.Groups[1].Success)
{
// INSERT - TABLE NAME
table = match.Groups[1].Value;
}
if (match.Groups[2].Success)
{
// INSERT - COLS NAMES
columns = match.Groups[2].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Replace("[", string.Empty).Replace("]", string.Empty).Trim()).ToArray();
}
if (match.Groups[3].Success)
{
// INSERT - PARAMS VALUES
parameters = match.Groups[3].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Trim()).ToArray();
}
if (match.Groups[4].Success)
{
// UPDATE - TABLE NAME
table = match.Groups[4].Value;
}
if (match.Groups[5].Success)
{
// UPDATE - COLS/PARAMS NAMES/VALUES
var colParams = match.Groups[5].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(p => p.Replace("[", string.Empty).Replace("]", string.Empty).Trim()).ToArray();
columns = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[0].Trim()).ToArray();
parameters = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[1].Trim()).ToArray();
}
}
// After taking all the necessary information from the sql command
// we can add Precision and Scale to all decimal parameters
foreach (var item in command.Parameters.OfType<SqlParameter>().Where(p => p.DbType == DbType.Decimal))
{
var index = Array.IndexOf<string>(parameters, item.ParameterName);
var columnName = columns.ElementAt(index);
var key = $"{table}.{columnName}";
// Add Precision and Scale, that fix our problems w/ always encrypted columns
item.Precision = _tableMapping[key].Precision;
item.Scale = _tableMapping[key].Scale;
}
}
}
}
}
Finally add in the Startup.cs the following line of code to register the listener:
DiagnosticListener.AllListeners.Subscribe(new EfGlobalListener());
Ecountered the same issue.
Adjusted #SteeBono interceptor to work with commands which contain multiple statements:
public class AlwaysEncryptedDecimalParameterInterceptor : DbCommandInterceptor, IObserver<KeyValuePair<string, object>>
{
private Dictionary<string, (SqlDbType DataType, byte? Precision, byte? Scale)> _decimalColumnSettings =
new Dictionary<string, (SqlDbType DataType, byte? Precision, byte? Scale)>
{
// MyTableDecimal
{ $"{nameof(MyTableDecimal)}.{nameof(MyTableDecimal.MyDecimalColumn)}", (SqlDbType.Decimal, 18, 6) },
// MyTableMoney
{ $"{nameof(MyTableMoney)}.{nameof(MyTableMoney.MyMoneyColumn)}", (SqlDbType.Money, null, null) },
};
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
// After that EF Core generates the command to send to the DB, this method will be called
public void OnNext(KeyValuePair<string, object> value)
{
if (value.Key == RelationalEventId.CommandExecuting.Name)
{
System.Data.Common.DbCommand command = ((CommandEventData)value.Value).Command;
Regex regex = new Regex(#"INSERT INTO \[(.+)\] \((.*)\)(\r\n|\r|\n)+VALUES \(([^;]*)\);|UPDATE \[(.*)\] SET (.*)|MERGE \[(.+)\] USING \((\r\n|\r|\n)+VALUES \(([^A]*)\) AS \w* \((.*)\)");
MatchCollection matches = regex.Matches(command.CommandText);
foreach (Match match in matches)
{
(string TableName, string[] Columns, string[] Params) commandComponents = GetCommandComponents(match);
int countOfColumns = commandComponents.Columns.Length;
// After taking all the necessary information from the sql command
// we can add Precision and Scale to all decimal parameters and set type for Money ones
for (int index = 0; index < commandComponents.Params.Length; index++)
{
SqlParameter decimalSqlParameter = command.Parameters.OfType<SqlParameter>()
.FirstOrDefault(p => commandComponents.Params[index] == p.ParameterName);
if (decimalSqlParameter == null)
{
continue;
}
string columnName = commandComponents.Columns.ElementAt(index % countOfColumns);
string settingKey = $"{commandComponents.TableName}.{columnName}";
if (_decimalColumnSettings.ContainsKey(settingKey))
{
(SqlDbType DataType, byte? Precision, byte? Scale) settings = _decimalColumnSettings[settingKey];
decimalSqlParameter.SqlDbType = settings.DataType;
if (settings.Precision.HasValue)
{
decimalSqlParameter.Precision = settings.Precision.Value;
}
if (settings.Scale.HasValue)
{
decimalSqlParameter.Scale = settings.Scale.Value;
}
}
}
}
}
}
private (string TableName, string[] Columns, string[] Params) GetCommandComponents(Match match)
{
string tableName = null;
string[] columns = null;
string[] parameters = null;
// INSERT
if (match.Groups[1].Success)
{
tableName = match.Groups[1].Value;
columns = match.Groups[2].Value.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Replace("[", string.Empty)
.Replace("]", string.Empty)
.Trim()).ToArray();
parameters = match.Groups[4].Value
.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Trim()
.Replace($"),{Environment.NewLine}(", string.Empty)
.Replace("(", string.Empty)
.Replace(")", string.Empty))
.ToArray();
return (
TableName: tableName,
Columns: columns,
Params: parameters);
}
// UPDATE
if (match.Groups[5].Success)
{
tableName = match.Groups[5].Value;
string[] colParams = match.Groups[6].Value.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Replace("[", string.Empty).Replace("]", string.Empty).Trim())
.ToArray();
columns = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[0].Trim()).ToArray();
parameters = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[1].Trim()).ToArray();
return (
TableName: tableName,
Columns: columns,
Params: parameters);
}
// MERGE
if (match.Groups[7].Success)
{
tableName = match.Groups[7].Value;
parameters = match.Groups[9].Value.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Trim()
.Replace($"),{Environment.NewLine}(", string.Empty)
.Replace("(", string.Empty)
.Replace(")", string.Empty))
.ToArray();
columns = match.Groups[10].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Replace("[", string.Empty).Replace("]", string.Empty).Trim()).ToArray();
return (
TableName: tableName,
Columns: columns,
Params: parameters);
}
throw new Exception($"{nameof(AlwaysEncryptedDecimalParameterInterceptor)} was not able to parse the command");
}
}

Populating Nested List<> in MVC4 C#

I've got a problem populating nested List<>
The object graph looks like this:
Route ⇒ Section ⇒ Co-ordinates
Whenever I try to populate Сoordinates list it just overwrites previous record and at the end gives me only the last Coordinate record. But I want all the Co-ordinates.
Here is my controller code:
List<RequestRouteDataClass> result = new List<RequestRouteDataClass> {
new RequestRouteDataClass() {
RouteRequestId = objRouteManagement.RouteRequestId,
RouteName = objRouteManagement.RouteName,
RouteDescription = objRouteManagement.RouteDescription,
RouteSections = new List<RouteSections> {
new RouteSections() {
Route_Sections_Id = objSections.Route_Sections_Id,
Section_Speed = objSections.Section_Speed,
Section_Description = objSections.Section_Description,
RouteCordinatesSections = new List<SectionCoordinatesRelationData> {
new SectionCoordinatesRelationData() {
SectionCoordinate_Relat_Id = objSectionsCordinates.SectionCoordinate_Relat_Id,
CoordinateLat = objSectionsCordinates.CoordinateLat,
CoordinateLag = objSectionsCordinates.CoordinateLag
}
}
}
}
}
If you want to use Nested List.
Your Model Contains =>
public class MainModelToUse
{
public MainModelToUse()
{
FirstListObject = new List<FirstListClass>();
}
public List<FirstListClass> FirstListObject { get; set; }
}
public class FirstListClass
{
public FirstListClass()
{
SecondListObject = new List<SecondListClass>();
}
public List<SecondListClass> SecondListObject { get; set; }
}
public class SecondListClass
{
public SecondListClass()
{
ThirdListObject = new List<ThirdListClass>();
}
public List<ThirdListClass> ThirdListObject { get; set; }
}
public class ThirdListClass
{
}
Your Code to Nested List =>
FirstListClass vmFirstClassMenu = new FirstListClass();
vmFirstClassMenu.SecondListClass = new List<SecondListClass>();
FirstListClass vmFirstClassCategory = new FirstListClass();
var dataObject1 = //Get Data By Query In Object;
foreach (Model objModel in dataObject1)
{
vmFirstClassCategory = new FirstListClass
{
//Your Items
};
var DataObject2 = //Get Data By Query In Object;
vmFirstClassCategory.SecondListClass = new List<SecondListClass>();
foreach (SecondListClass menuItem in DataObject2)
{
SecondListClass vmFirstClassMenuItem = new SecondListClass
{
//Your Items
};
var DataObject3 = //Get Data By Query In Object;
vmFirstClassMenuItem.ThirdListClass = new List<ThirdListClass>();
foreach (ThirdListClass price in DataObject3)
{
ThirdListClass vmThirdClassobj = new ThirdListClass
{
//Your Items
};
vmFirstClassMenuItem.ThirdListClass.Add(vmThirdClassobj);
}
vmFirstClassCategory.SecondListClass.Add(vmFirstClassMenuItem);
}
}
Hope this is what you are looking for.
First off: spacing helps with readability (edit: but I see you fixed that in your question already):
List<RequestRouteDataClass> result = new List<RequestRouteDataClass>
{
new RequestRouteDataClass()
{
RouteRequestId = objRouteManagement.RouteRequestId,
RouteName = objRouteManagement.RouteName,
RouteDescription = objRouteManagement.RouteDescription,
RouteSections = new List<RouteSections>
{
new RouteSections()
{
Route_Sections_Id = objSections.Route_Sections_Id,
Section_Speed = objSections.Section_Speed,
Section_Description = objSections.Section_Description,
RouteCordinatesSections = new List<SectionCoordinatesRelationData>
{
new SectionCoordinatesRelationData()
{
SectionCoordinate_Relat_Id = objSectionsCordinates.SectionCoordinate_Relat_Id,
CoordinateLat = objSectionsCordinates.CoordinateLat,
CoordinateLag =objSectionsCordinates.CoordinateLag
}
}
}
}
}
};
Next: what you are doing with the above is initiating your lists with a single element in each list. If you want more elements, you have to add them. I recommend using a foreach and the Add() functionality to fill your lists.
From your example it is not clear how your source data is stored, but if you have multiples of something I would expect those too to be in a list or an array of some kind.

How to build object hierarchy from SQL query? (for WPF TreeView)

thanks for taking the time out to read this post.
I'm having trouble trying to build a hierarchial object when getting data from my SQL database.
Please note that I am a little bit of a newbie programmer.
How do you build a hierarchial object that has unknown levels? When I say unknown levels I mean, each node may have varying numbers of child nodes, which in turn may have varying numbers of its own child nodes, so on and so on.
The idea is that I need to create a hierarchial object using my SQL data to bind to WPF TreeView control.
Below I have included the code I have so far.
The first bit of code is my Class made up of Properties. Note that the "Products" class has an ObservableCollection referencing itself. I think this is how you construct the nested nodes. i.e. a list inside a list.
The second piece of code is my Get Method to download the data from the SQL database. Here is where I need to some how sort the downloaded data into a hierarchy.
Products Class (properties)
public class Products : INotifyPropertyChanged, IDataErrorInfo
{
private Int64 m_ID;
private SqlHierarchyId m_Hierarchy;
private string m_Name;
private ObservableCollection<Products> m_ChildProducts;
// Default Constructor
public Products()
{
ChildProducts = new ObservableCollection<Products>();
}
//Properties
public Int64 ID
{
get
{
return m_ID;
}
set
{
m_ID = value;
OnPropertyChanged(new PropertyChangedEventArgs("ID"));
}
}
public SqlHierarchyId Hierarchy
{
get
{
return m_Hierarchy;
}
set
{
m_Hierarchy = value;
OnPropertyChanged(new PropertyChangedEventArgs("Hierarchy"));
}
}
public String Name
{
get
{
return m_Name;
}
set
{
m_Name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
public Int16 Level
{
get
{
return m_Level;
}
set
{
m_Level = value;
OnPropertyChanged(new PropertyChangedEventArgs("Level"));
}
}
public Int64 ParentID
{
get
{
return m_ParentID;
}
set
{
m_ParentID = value;
OnPropertyChanged(new PropertyChangedEventArgs("ParentID"));
}
}
public ObservableCollection<Products> ChildProducts
{
get
{
return m_ChildProducts;
}
set
{
m_ChildProducts = value;
OnPropertyChanged(new PropertyChangedEventArgs("ChildProducts"));
}
}
//INotifyPropertyChanged Event
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
}
Method which gets data from SQL DB:
public static ObservableCollection<Products> GetProductsHierarchy()
{
ObservableCollection<Products> productsHierarchy = new ObservableCollection<Products>();
SqlConnection connection = new SqlConnection(DBConnection.GetConnection().ConnectionString);
string selectStatement = "SELECT ID, Hierarchy, Name, Hierarchy.GetLevel() AS Level, Hierarchy.GetAncestor(1) AS ParentHierarchy, " +
"(SELECT ID " +
"FROM SpecProducts " +
"WHERE (Hierarchy = SpecProducts_1.Hierarchy.GetAncestor(1))) AS ParentID " +
"FROM SpecProducts AS SpecProducts_1 " +
"WHERE (EnableDisable IS NULL) " +
"ORDER BY Hierarchy";
SqlCommand selectCommand = new SqlCommand(selectStatement, connection);
try
{
connection.Open();
SqlDataReader reader = selectCommand.ExecuteReader();
while (reader.Read())
{
Products product = new Products();
product.ID = (Int64)reader["ID"];
product.Name = reader["Name"].ToString();
product.Hierarchy = (SqlHierarchyId)reader["Hierarchy"];
product.Level = (Int16)reader["Level"];
if (reader["ParentID"] != DBNull.Value)
{
product.ParentID = (Int64)reader["ParentID"];
}
else
{
product.ParentID = 0;
}
productsHierarchy.Add(product);
// *** HOW TO BUILD HIERARCHY OBJECT WITH UNKNOWN & VARYING LEVELS?
// *** ADD PRODUCT TO CHILDPRODUCT
}
return productsHierarchy;
}
catch (SqlException ex)
{
throw ex;
}
finally
{
connection.Close();
}
}
Below I have attached an image showing the structure of my SQL Query Data.
Please note that the hierarchy level may go deeper when more products are added in the future. The Hierarchy object I need to create should be flexible enough to expand no matter what the number of node levels are.
Thank you very much for your time, all help is greatly appreciated.
********* EDIT 26/04/2012 14:37 *******************
Please find below a link to download my project code (this only contains treeview code).
Can someone please take a look at it to see why I cannot create nodes beyond 2 levels?
The code was given to me by user HB MAAM. Thank you "HB MAAM" for your help so far!
Click this link to download code
I will create an example for you,
1- first i will create a class that holds the data that comes from the DB
public class SqlDataDto
{
public int? Id { get; set; }
public int? ParentId { get; set; }
public String Name { get; set; }
public String OtherDataRelatedToTheNode { get; set; }
}
2- that data will be converted to hierarchal data and we will use this class to hold it:
public class LocalData : INotifyPropertyChanged
{
private int? _id;
public int? Id
{
get { return _id; }
set { _id = value; OnPropertyChanged("Id"); }
}
private int? _parentId;
public int? ParentId
{
get { return _parentId; }
set { _parentId = value; OnPropertyChanged("ParentId"); }
}
private string _name;
public String Name
{
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
}
private string _otherDataRelatedToTheNode;
public String OtherDataRelatedToTheNode
{
get { return _otherDataRelatedToTheNode; }
set { _otherDataRelatedToTheNode = value; OnPropertyChanged("OtherDataRelatedToTheNode"); }
}
private LocalData _parent;
public LocalData Parent
{
get { return _parent; }
set { _parent = value; OnPropertyChanged("Parent"); }
}
private ObservableCollection<LocalData> _children;
public ObservableCollection<LocalData> Children
{
get { return _children; }
set { _children = value; OnPropertyChanged("Children"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
}
}
}
3- finally we need to change the sql data to hierarchical one:
public List<LocalData> GetHerachy(List<SqlDataDto> sqlData)
{
var sqlParents = sqlData.Where(q => q.ParentId == null).ToList();
var parents = sqlParents.Select(q => new LocalData {Id = q.Id, Name = q.Name}).ToList();
foreach (var parent in parents)
{
var childs = sqlData.Where(q => q.ParentId == parent.Id).Select(q => new LocalData { Id = q.Id, Name = q.Name , Parent = parent});
parent.Children = new ObservableCollection<LocalData>(childs);
}
return parents;
}
4- then you can create a dummy data and convert it and show it in the tree:
var sqlData = new List<SqlDataDto>
{
new SqlDataDto {Id = 1, ParentId = null, Name = "F1"}
, new SqlDataDto {Id = 2, ParentId = null, Name = "F2"}
, new SqlDataDto {Id = 3, ParentId = 1, Name = "S1"}
, new SqlDataDto {Id = 4, ParentId = 2, Name = "S21"}
, new SqlDataDto {Id = 5, ParentId = 2, Name = "S22"}
};
treeView.ItemsSource = GetHerachy(sqlData);
5- the tree should be like:
<TreeView Name="treeView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
You need to use recursion to fill the Child-List of every object. This is necessary for the WPF HierarchicalDataTemplate to work. Otherwise you only get the first level.
There is an alternative using the Linq method ForEach() and passing an Action Argument. The following solution is very straight forward and easy to understand:
public List<Product> Products { get; set; }
public MainViewModel()
{
Products = new List<Product>();
Products.Add(new Product() { Id = 1, Name = "Main Product 1", ParentId = 0 });
Products.Add(new Product() { Id = 3, Name = "Sub Product 1", ParentId = 1 });
Products.Add(new Product() { Id = 4, Name = "Sub Product 2", ParentId = 1 });
Products.Add(new Product() { Id = 5, Name = "Sub Product 3", ParentId = 1 });
Products.Add(new Product() { Id = 6, Name = "Sub Product 3.1", ParentId = 5 });
this.ProcessRootNodes();
}
private void ProcessRootNodes()
{
var rootNodes = Products.Where(x => x.ParentId == 0).ToList();
for (int i = 0; i < rootNodes.Count; i++)
{
rootNodes[i].Children = this.AddChildren(rootNodes[i]);
}
}
private List<Product> AddChildren(Product entry)
{
var children = Products.Where(x => x.ParentId == entry.Id).ToList();
for(int i=0;i<children.Count;i++)
{
children[i].Children = this.AddChildren(children[i]);
}
return children;
}
// *** HOW TO BUILD HIERARCHY OBJECT WITH UNKNOWN & VARYING LEVELS?
Instead of
ObservableCollection<Products> productsHierarchy = new ObservableCollection<Products>();
use Dictionary<Int64, Products> IdToProduct = new ...
As you loop your products; do a IdToProduct[product.ID] = product;
Then, loop the completed IdToProduct collection and do;
if(product.ParentID != 0)
{
IdToProduct[product.ParentID].ChildProducts.Add(product);
}
Now, your Product --> ChildProducts relation is mapped out.
Optionally, add properties to the Products class:
public bool IsCategory { get { return (ChildProducts.Count >= 1); } } // e.g. Oven
public bool IsProduct { get { return !(IsCategory); } } // e.g. Electric (Oven)
Now, you have most of the model for your view defined.
This article is the de facto starting point for using the WPF TreeView.
Hint: a starting point for your HierarchicalDataTemplate
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Products}"
ItemsSource="{Binding ChildProducts}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
You should create a MainViewModel class which has:
public Products RootProduct { get; set; } (notify property changed property)
after you do your SQL parsing and what not; do:
RootProduct = IdToProduct.FirstOrDefault(product => (product.Level == 0));
<TreeView ItemsSource="{Binding RootProduct.ChildProducts}">