Horrible response time with NHibernate and Oracle11g - c#

My problem is at the same time simple and complex :
I'm working with NHibernate 3.3 and Oracle 11g with ODP drivers.
This piece of code works like a charm:
var query = Session.CreateSQLQuery("SELECT * FROM wip_event_log WHERE track_id='" + trackId + "'");
query.AddEntity("l", typeof(MotIdenWipEventLog));
var results = query.List<MotIdenWipEventLog>();
in a couple of milliseconds I get the result set. (only 5 records from a table with 11.000.000 of records)
In the other hand, this piece of code :
var results = Session.Query<MotIdenWipEventLog>().Where(m => m.TRACK_ID == trackId).ToList();
takes about 4 seconds to get 5 the records!.
I read about an problem with the AnsiString columns in Oracle databases (http://bit.ly/1bbSlB7) and added a custom convention for work with strings on my fluent configuration:
Fluently
.Configure(new Configuration().Configure())
.Database(OracleClientConfiguration
.Oracle10
.ConnectionString(c => c.Is("User ID=XXXX;Password=XXXX;Data Source=(DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 1.1.1.1)(PORT = 1521)))(CONNECT_DATA = (SID = iden01)))"))
)
.Mappings(
cfg => cfg.FluentMappings.LocalAddFromAssemblyOf<MotIdenPackSalesModelsHeaderMap>().Conventions.Add<OracleStringPropertyConvention>()
).BuildConfiguration();
and the custom convention MotIdenPackSalesModelsHeaderMap is:
public class OracleStringPropertyConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
if (instance.Property.PropertyType == typeof(string)) instance.CustomType("AnsiString");
}
}
The entity MotIdenWipEventLog is defined below:
[Serializable]
public class MotIdenWipEventLog
{
public virtual String TRACK_ID { get; set; } // VARCHAR2(16 BYTE) No
public virtual String ASSY_PART_NUM { get; set; } // VARCHAR2(20 BYTE) Yes
public virtual String ASSY_VER_CODE { get; set; } // VARCHAR2(4 BYTE) Yes
public virtual int PROC_ID { get; set; } // NUMBER(9,0) Yes
public virtual String WIP_EVENT_CODE { get; set; } // VARCHAR2(4 BYTE) Yes
public virtual DateTime EVENT_DATETIME { get; set; } // DATE No
public virtual int EVENT_CLKSEQ { get; set; } // NUMBER(12,0) Yes
public virtual String AREA_ID { get; set; } // VARCHAR2(8 BYTE) Yes
public virtual String PERSONNEL_ID { get; set; } // VARCHAR2(11 BYTE) Yes
public virtual String STN_ID { get; set; } // VARCHAR2(20 BYTE) Yes
public virtual int WIP_COUNT { get; set; } // NUMBER(3,0) Yes
public virtual String STN_GROUP { get; set; } // VARCHAR2(8 BYTE) Yes
}
Mapped through the class MotIdenWipEventLogMap:
public class MotIdenWipEventLogMap : ClassMap<MotIdenWipEventLog>
{
public MotIdenWipEventLogMap()
{
Table("WIP_EVENT_LOG");
Id(m => m.TRACK_ID, "TRACK_ID").GeneratedBy.Assigned();
#region Fields
Map(m => m.TRACK_ID).Not.Nullable()
.Length(16).Index("WIP_EVENT_LOG_IDX1"); // VARCHAR2(16 BYTE) No
Map(m=>m.ASSY_PART_NUM).Nullable().Length(20); // VARCHAR2(20 BYTE) Yes
Map(m=>m.ASSY_VER_CODE).Nullable().Length(4); // VARCHAR2(4 BYTE) Yes
Map(m=>m.PROC_ID).Nullable(); // NUMBER(9,0) Yes
Map(m=>m.WIP_EVENT_CODE).Nullable().Length(4); // VARCHAR2(4 BYTE) Yes
Map(m=>m.EVENT_DATETIME).Not.Nullable(); // DATE No
Map(m=>m.EVENT_CLKSEQ).Nullable(); // NUMBER(12,0) Yes
Map(m=>m.AREA_ID).Nullable().Length(8); // VARCHAR2(8 BYTE) Yes
Map(m=>m.PERSONNEL_ID).Nullable().Length(11); // VARCHAR2(11 BYTE) Yes
Map(m=>m.STN_ID).Nullable().Length(20); // VARCHAR2(20 BYTE) Yes
Map(m=>m.WIP_COUNT).Nullable(); // NUMBER(3,0) Yes
Map(m=>m.STN_GROUP).Nullable().Length(8); // VARCHAR2(8 BYTE) Yes
#endregion
}
}
Looking my log file for NHibernate in Debug level of Log4Net:
(...)
2013-11-06 14:24:22,375 DEBUG - Opened IDataReader, open IDataReaders: 1
2013-11-06 14:24:22,376 DEBUG - processing result set
2013-11-06 14:24:26,956 DEBUG - result set row: 0
2013-11-06 14:24:26,959 DEBUG - returning 'F7012B200ZMH' as column: TRACK1_6_
(...)
and seeing in the NHibernate source code of class Loader.cs :
(...)
try
{
HandleEmptyCollections(queryParameters.CollectionKeys, rs, session);
EntityKey[] keys = new EntityKey[entitySpan]; // we can reuse it each time
if (Log.IsDebugEnabled)
{
Log.Debug("processing result set");
}
int count;
for (count = 0; count < maxRows && rs.Read(); count++)
{
if (Log.IsDebugEnabled)
{
Log.Debug("result set row: " + count);
}
object result = GetRowFromResultSet(rs, session, queryParameters, lockModeArray, optionalObjectKey, hydratedObjects, keys, returnProxies);
results.Add(result);
(...)
I cant find where the problem is...
What I doing wrong?
Any idea?

Rather a patch than a solution, but you could create a function-based index to match the type your application is requesting.
E.g.,
create index patch_index on your_table(cast(your_column as nvarchar2(16)));
Illustrating this on Oracle 11g using EXPLAIN PLAN.
Using
create table t(x varchar2(10));
create index idx on t(x);
insert into t values ('a');
The query
select * from t where x = 'a';
gives you the following plan
| Id | Operation | Name | Rows | Bytes | Cost (%CPU) | Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 3 (0) | 00:00:01 |
|* 1 | TABLE ACCESS FULL | T | 1 | 7 | 3 (0) | 00:00:01 |
--------------------------------------------------------------------------
After adding the following index
create index t2 on t(cast(x as nvarchar2(10)))
The same query now gives you the following plan
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 19 | 2 (0) | 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T | 1 | 19 | 2 (0) | 00:00:01 |
|* 2 | INDEX RANGE SCAN | T2 | 1 | | 1 (0) | 00:00:01 |
------------------------------------------------------------------------------------
You can apply this technique if you cannot fix the problem on the application side.

Related

Multiple lines and columns in Specflow

I have a Specflow table that looks like this.
When I Perform POST Operation for "/example/" with body
| answerValue1 | answerValue2 | countryCode | Cash |
| Yes | Yes | AD | 76-100% |
| | | AF | |
The column CountryCode is the only one that can be multiple choices.
What I tried to do was to add the columns to dictionary with a simple tableExtenstions
public class TableExtensions
{
public static Dictionary<string, string> ToDictionary(Table table)
{
var dictionary = new Dictionary<string, string>();
foreach (var row in table.Rows)
{
dictionary.Add(row[0], row[1]);
}
return dictionary;
}
}
and call it from the method.
var dictionary = TableExtensions.ToDictionary(table);
var countryCode = dictionary["countryCode"];
Unfortnally I get error The given key was not present in the dictionary,
since the dictionary only returns two values from the first and the second Key
Ofcourse if I change the keys to row[2], row[3] it gets the right columns.
But I would like to reuse the Table Extension.
Also i tried to increment them, but it only took the first to columns
var i = 0;
foreach (var row in table.Rows)
{
dictionary.Add(row[i], row[i]);
i++;
}
Does anyone has a better solution?
I'm not entirely sure what you want the dictionary to ultimately contain, but as you mention that manually changing the rows it looks for to:
row[2], row[3]
gives the data you want, perhaps this would give you the reusability you're looking for:
public class TableExtensions
{
public static Dictionary<string, string> ToDictionary(Table table, int columnOne, int columnTwo)
{
int i = 0;
var dictionary = new Dictionary<string, string>();
foreach (var row in table.Rows)
{
dictionary.Add(row[columnOne], row[columnTwo]);
}
return dictionary;
}
}
Usage:
var dictionary = TableExtensions.ToDictionary(table, 2, 3);
This produces a dictionary with the following contents:
You could get the country code like this:
foreach (var row in dictionary)
{
var countryCode = row.Key;
var score = row.Value ?? string.empty;
}
Given the simplicity of the country codes I would make them a comma separated list and use a vertical table instead:
When I Perform POST Operation for "/example/"
| Field | Value |
| answerValue1 | ... |
| answerValue2 | ... |
| countryCodes | AD, AF |
| cash | ... |
In your step definition:
var example = table.CreateInstance<ExampleRow>();
// use example.GetCountryCodes();
And the ExampleRow class to parse the table into an object:
public class ExampleRow
{
public string AnswerValue1 { get; set; }
public string AnswerValue2 { get; set; }
private string[] countryCodes;
public string CountryCodes
{
get => string.Join(", ", countryCodes);
set => countryCodes = value.Split(", ");
}
public string[] GetCountryCodes()
{
return countryCodes;
}
}

Replace repeated values in collection with its sum

I have a list of custom class ModeTime, its structure is below:
private class ModeTime
{
public DateTime Date { get; set; }
public string LineName { get; set; }
public string Mode { get; set; }
public TimeSpan Time { get; set; }
}
In this list I have some items, whose LineName and Modeare the same, and they are written in the list one by one. I need to sum Time property of such items and replace it with one item with sum of Time property without changing LineName and Mode, Date should be taken from first of replaced items. I will give an example below:
Original: Modified:
Date | LineName | Mode | Time Date | LineName | Mode | Time
01.09.2018 | Line1 | Auto | 00:30:00 01.09.2018 | Line1 | Auto | 00:30:00
01.09.2018 | Line2 | Auto | 00:10:00 01.09.2018 | Line2 | Auto | 00:15:00
01.09.2018 | Line2 | Auto | 00:05:00 01.09.2018 | Line2 | Manual | 00:02:00
01.09.2018 | Line2 | Manual | 00:02:00 01.09.2018 | Line2 | Auto | 00:08:00
01.09.2018 | Line2 | Auto | 00:08:00 01.09.2018 | Line1 | Manual | 00:25:00
01.09.2018 | Line1 | Manual | 00:25:00 01.09.2018 | Line2 | Auto | 00:24:00
01.09.2018 | Line2 | Auto | 00:05:00 02.09.2018 | Line1 | Auto | 00:05:00
02.09.2018 | Line2 | Auto | 00:12:00
02.09.2018 | Line2 | Auto | 00:07:00
02.09.2018 | Line1 | Auto | 00:05:00
I have tried to write method to do it, it partly works, but some not summarized items still remain.
private static List<ModeTime> MergeTime(List<ModeTime> modeTimes)
{
modeTimes = modeTimes.OrderBy(e => e.Date).ToList();
var mergedModeTimes = new List<ModeTime>();
for (var i = 0; i < modeTimes.Count; i++)
{
if (i - 1 != -1)
{
if (modeTimes[i].LineName == modeTimes[i - 1].LineName &&
modeTimes[i].Mode == modeTimes[i - 1].Mode)
{
mergedModeTimes.Add(new ModeTime
{
Date = modeTimes[i - 1].Date,
LineName = modeTimes[i - 1].LineName,
Mode = modeTimes[i - 1].Mode,
Time = modeTimes[i - 1].Time + modeTimes[i].Time
});
i += 2;
}
else
{
mergedModeTimes.Add(modeTimes[i]);
}
}
else
{
mergedModeTimes.Add(modeTimes[i]);
}
}
return mergedModeTimes;
}
I have also tried to wrap for with do {} while() and reduce source list modeTimes length. Unfortunately it leads to loop and memory leak (I waited till 5GB memory using).
Hope someone can help me. I searched this problem, in some familiar cases people use GroupBy. But I don't think it will work in my case, I must sum item with the same LineName and Mode, only if they are in the list one by one.
Most primitive solution would be something like this.
var items = GetItems();
var sum = TimeSpan.Zero;
for (int index = items.Count - 1; index > 0; index--)
{
var item = items[index];
var nextItem = items[index - 1];
if (item.LineName == nextItem.LineName && item.Mode == nextItem.Mode)
{
sum += item.Time;
items.RemoveAt(index);
}
else
{
item.Time += sum;
sum = TimeSpan.Zero;
}
}
items.First().Time += sum;
Edit: I missed last line, where you have to add leftovers. This only applies if first and second elements of the collection are the same. Without it, it would not assign aggregated time to first element.
You can use LINQ's GroupBy. To group only consecutive elements, this uses a trick. It stores the key values in a tuple together with a group index which is only incremented when LineName or Mode changes.
int i = 0; // Used as group index.
(int Index, string LN, string M) prev = default; // Stores previous key for later comparison.
var modified = original
.GroupBy(mt => {
var ret = (Index: prev.LN == mt.LineName && prev.M == mt.Mode ? i : ++i,
LN: mt.LineName, M: mt.Mode);
prev = (Index: i, LN: mt.LineName, M: mt.Mode);
return ret;
})
.Select(g => new ModeTime {
Date = g.Min(mt => mt.Date),
LineName = g.Key.LN,
Mode = g.Key.M,
Time = new TimeSpan(g.Sum(mt => mt.Time.Ticks))
})
.ToList();
This produces the expected 7 result rows.

How to Compare multi dimentional string array?

I want to compare 2 fields. i.e Machine and Pass.
I want to concatenate 'Color' Depending upon the value of Machine and pass.
--------------|------------------|------------
Color1 | Machine 1 | Pass 1
--------------|------------------|------------
Color2 | Machine 2 | Pass 1
--------------|------------------|------------
Color3 | Machine 1 | Pass 1
--------------|------------------|------------
Color4 | Machine 1 | Pass 2
--------------|------------------|------------
Color5 | Machine 2 | Pass 1
--------------|------------------|------------
Color6 | Machine 2 | Pass 2
--------------|------------------|------------
I want results as follows.
Color1/Color3 for Machine1 & Pass1
Color2/Color5 for Machine2 & Pass2
Color4 for Machine1 & Pass2
Color6 for Machine2 & Pass2
I have no clue how to get this results. tried many ways but not getting it the way I want it
First, what you really have here, conceptually, is a single dimension of a complex object that has both a color, machine, and pass value. Storing that in a 2 dimensional array isn't a good idea. A better representation is a List of some custom type, so let's do that conversion first.
First, we'll define a custom type to represent the input data:
public class MachinePass//TODO consider renaming
{
public string Color { get; set; }
public string Machine { get; set; }
public string Pass { get; set; }
}
then we'll parse our input and put it into the appropriate structure:
var list = new List<MachinePass>();
for (int i = 0; i < data.GetLength(0); i++)
{
var next = new MachinePass();
next.Color = data[i, 0];
next.Machine = data[i, 1];
next.Pass = data[i, 2];
list.Add(next);
}
Now that we have this, we can apply our business logic. In this case what you're wanting to do is group these rows by both machine and pass, and then get the colors for those groups. The GroupBy LINQ operator makes this super easy:
var query = list.GroupBy(row => new { row.Machine, row.Pass }
, row => row.Color);
Then we can just print out the results of this query in the defined format:
foreach(var group in query)
Console.WriteLine("{0} for {1} & {2}",
string.Join("/", group),
group.Key.Machine,
group.Key.Pass);
This is your answer,
private void button1_Click(object sender, EventArgs e)
{
var ary = new[]
{
"Color1 | Machine 1 | Pass 1 ",
"Color2 | Machine 2 | Pass 1 ",
"Color3 | Machine 1 | Pass 1 ",
"Color4 | Machine 1 | Pass 2 ",
"Color5 | Machine 2 | Pass 1 ",
"Color6 | Machine 2 | Pass 2 "
};
var seprated = from x in ary.Select(x => x.Split('|'))
select new
{
key = x[1].Trim() + "&" + x[2].Trim(),
value = x[0]
};
var sb = new StringBuilder();
foreach (var key in seprated.Select(x => x.key).Distinct())
{
var colors = seprated.Where(x => x.key == key).Select(x => x.value.Trim()).ToArray();
sb.AppendLine(string.Format("{0} for {1}", string.Join("/", colors), key));
}
textBox1.Text = sb.ToString();
}
sb.toString() has result:
Color1/Color3 for Machine 1&Pass 1
Color2/Color5 for Machine 2&Pass 1
Color4 for Machine 1&Pass 2
Color6 for Machine 2&Pass 2
You did not provide a class definition so I am going to assume it is the following
class PaintMachineInfo
{
public string ColorName {get; set;}
public string MachineName {get; set;}
public string Pass {get; set;}
}
You can easily get the results you want by using .ToLookup(, what it will allow you to do is to provide a key and it will give you a IEnumerable of results that match that key.
I like to use a custom class for the lookup key instead of a Tuple because it makes it much more obvious what you are looking at and it is not too much code to create the class.
private static void Main(string[] args)
{
List<PaintMachineInfo> info = GenerateInfo();
var filteredResults = info.ToLookup(line => new PaintMachineLookup(line.MachineName, line.Pass), line => line.ColorName);
//Contains a IEnumerable<string> containing the elements "Color1" and "Color3"
var result1 = filteredResults[new PaintMachineLookup("Machine 1", "Pass 1")];
}
private static List<PaintMachineInfo> GenerateInfo()
{
//...
}
class PaintMachineInfo
{
public string ColorName { get; set; }
public string MachineName { get; set; }
public string Pass { get; set; }
}
internal class PaintMachineLookup
{
public PaintMachineLookup(string machineName, string pass)
{
MachineName = machineName;
Pass = pass;
}
public string MachineName { get; private set; }
public string Pass { get; private set; }
public override int GetHashCode()
{
unchecked
{
int x = 27;
x = x * 11 + MachineName.GetHashCode();
x = x * 11 + Pass.GetHashCode();
return x;
}
}
public override bool Equals(object obj)
{
var other = obj as PaintMachineLookup;
if (other == null)
return false;
return MachineName.Equals(other.MachineName) && Pass.Equals(other.Pass);
}
}
var multidimensionalArray = new[,]
{
{"Color1", "Machine 1", "Pass 1"},
{"Color2", "Machine 2", "Pass 1"},
{"Color3", "Machine 1", "Pass 1"},
{"Color4", "Machine 1", "Pass 2"},
{"Color5", "Machine 2", "Pass 1"},
{"Color6", "Machine 2", "Pass 2"}
};
var tuple = new List<Tuple<string, string, string>>();
for (var i = 0; i < multidimensionalArray.Length/3-1; i++)
{
tuple.Add(new Tuple<string, string, string>(multidimensionalArray[i, 0], multidimensionalArray[i, 1], multidimensionalArray[i, 2]));
}
foreach (var el in tuple.GroupBy(x => String.Format("{0} & {1}", x.Item2, x.Item3), y => y.Item1))
{
Console.WriteLine(String.Join("/", el) + " for " + el.Key);
}

C# output is somehow program name and class name

I am trying to do the crazy formatting instructions my teacher gave me. After perusing for probably an hour (This is my first C# program), I came up with this line of code.
`Console.WriteLine(String.Format("{0," + -longestTitle + "} | {1," + -longestAlbumTitle + "} | {2," + -longestArtist + "} | {3:0.00, 8} | {4," + -longestYearAndRating + "} |", songArray[arraySearcher].title, songArray[arraySearcher].albumTitle, songArray[arraySearcher].artist, songArray[arraySearcher].length, songArray[arraySearcher].yearAndRating));`
longestX is an int containing the number of characters of the longestX (where x = title, album, etc).
The output I would like looks something like this:
Stuff | morestuff | extrastuff | 5.92 | 1992:R |
Stuf | est | sfafe | 232.44 | 2001:PG |
S uf | e | sfe | .44 | 2001:G |
(Where all padding is determined dynamically based on the longest title input by the user or file).
The output I get looks like this:
Program_Example.ClassName
Program_Example.ClassName
(or, specifically, Tyler_Music_Go.Song)
I have printed songArray[arraySearcher].title in this same method, and it works fine.
Could someone please help me?
Full relevant code:
class Song {
public string title, albumTitle, yearAndRating, artist;
public float length;
public Song(string titl, string albumTitl, string art, float leng, string yrNRating)
{
title = titl;
albumTitle = albumTitl;
yearAndRating = yrNRating;
length = leng;
artist = art;
}
}
//This class contains a Song array (with all Songs contained within), an array index, a search index, and ints to determine the longest of each category.
class SongList
{
Song[] songArray;
private int arrayKeeper, longestTitle, longestArtist, longestAlbumTitle, longestYearAndRating, checker;
int arraySearcher = 0;
public SongList()
{
songArray = new Song[10000];
arrayKeeper = 0;
longestTitle = 0;
longestArtist = 0;
longestAlbumTitle = 0;
longestYearAndRating = 0;
}
public void AddSong(string title, string albumTitle, string artist, float length, string yearAndRating)
{
songArray[arrayKeeper] = new Song(title, albumTitle, artist, length, yearAndRating);
arrayKeeper++;
checker = 0;
//This section of code is responsible for formatting the output. Since the longest values are already known, the list can be displayed quickly.
//Once a song is deleted, however, previously calculated longest lengths still stand.
foreach (char check in title)
{
checker++;
}
if (checker > longestTitle)
{
longestTitle = checker;
}
foreach (char check in albumTitle)
{
checker++;
}
if (checker > longestAlbumTitle)
{
longestAlbumTitle = checker;
}
foreach (char check in artist)
{
checker++;
}
if (checker > longestArtist)
{
longestArtist = checker;
}
foreach (char check in yearAndRating)
{
checker++;
}
if (checker > longestYearAndRating)
{
longestYearAndRating = checker;
}
}
//public bool RemoveSong(string title)
// {
//}
public void DisplayData()
{
Console.WriteLine("| Title | Album Title | Artist | Length | Year and Rating |");
for (arraySearcher = 0; arraySearcher < arrayKeeper; arraySearcher++)
{
//This line for testing purposes. (works)
Console.WriteLine(songArray[arraySearcher].title);
Console.WriteLine(songArray[arraySearcher].ToString());
}
}
public override string ToString()
{
//This line for testing purposes. (works)
Console.WriteLine(songArray[arraySearcher].title);
return String.Format("{0," + -longestTitle + "} | {1," + -longestAlbumTitle + "} | {2," + -longestArtist + "} | {3:0.00, 8} | {4," + -longestYearAndRating + "} |", songArray[arraySearcher].title, songArray[arraySearcher].albumTitle, songArray[arraySearcher].artist, songArray[arraySearcher].length, songArray[arraySearcher].yearAndRating);
}
}
`
EDIT:
Well, now I feel all manor of stupid. I was overwriting the tostring() method for the SongList, and then calling the tostring method for Song. Guy who answered made me realize it. Thanks to everyone who gave me advice, though.
You have to either access a property directly (songVariable.Title) or override ToString() in your song class to have that output the title.
public class Song
{
public string Title {get; set;}
public override string ToString()
{
return Title;
}
}

Generate Sitemap from URLs in Database

Problem Statement:
URLs are stored in a database, example:
home/page1
gallery/image1
info/IT/contact
home/page2
home/page3
gallery/image2
info/IT/map
and so on.
I would like to arrange the above urls into a tree fashion as shown below (each item will be a url link). The final output would be a simple HTML List (plus any sub list(s))
thus:
home gallery info
page1 image1 IT
page2 image2 contact
page3 map
Programming Language is C# , platform is asp.net
EDIT 1:
In the above example, we end up with Three Lists because in our example there is three main 'groups' eg: home, gallery, info.
Naturally, this can change, the algorithm needs to be able to somehow build the lists recursively..
Well,sorting those strings need a lot of work,I've done something similar to your condition.I wish to share the strategy with you.
First of all,(if you can change design of your tables indeed)
Create a table URL like below
----------------
| URL Table |
----------------
| ID |
| ParentID |
| Page |
|..extra info..|
----------------
It's an implementation of category and sub category in same table.In a similar manner,you can contain insert a lot of page and subpage.For example,
-------------------------------------
| ID | ParentID | Page | ...
------------------------------------
| 0 | null | Home |
| 1 | null | Gallery |
| 2 | null | Info |
| 3 | 0 | Page1 |
| 4 | 0 | Page2 |
| 5 | 0 | Page3 | ...
| 6 | 1 | Image1 |
| 7 | 1 | Image2 |
| 8 | 2 | IT |
| 9 | 8 | contact |
| 1 | 8 | map |
------------------------------------- ...
when ParentID is null then its highest level
when ParentID is and ID then its a sublevel of whatever level is on that ID and so on...
From C# side,you know the top pages where ParentID's are null.
You can bring sub pages of them by selected ID's of top pages.It's some ADO.NET work.
Hope this helps
Myra
ok, did it:
First created a class:
public class Node
{
private string _Parent = string.Empty;
private string _Child = string.Empty;
private bool _IsRoot = false;
public string Parent
{
set { _Parent = value; }
get { return _Parent; }
}
public string Child
{
set { _Child = value; }
get { return _Child; }
}
public Node(string PChild, string PParent)
{
_Parent = PParent;
_Child = PChild;
}
public bool IsRoot
{
set { _IsRoot = value; }
get { return _IsRoot; }
}
}
then generated the SiteMap, by transforming the urls strings directly as follows:
private static string MakeTree()
{
List<Node> __myTree = new List<Node>();
List<string> urlRecords = new List<string>();
urlRecords.Add("home/image1");
urlRecords.Add("home/image2");
urlRecords.Add("IT/contact/map");
urlRecords.Add("IT/contact/address");
urlRecords.Add("IT/jobs");
__myTree = ExtractNode(urlRecords);
List<string> __roots = new List<string>();
foreach(Node itm in __myTree)
{
if (itm.IsRoot)
{
__roots.Add(itm.Child.ToString());
}
}
string __trees = string.Empty;
foreach (string roots in __roots)
{
__trees += GetChildren(roots, __myTree) + "<hr/>";
}
return __trees;
}
private static string GetChildren(string PRoot, List<Node> PList)
{
string __res = string.Empty;
int __Idx = 0;
foreach (Node x in PList)
{
if (x.Parent.Equals(PRoot))
{
__Idx += 1;
}
}
if (__Idx > 0)
{
string RootHeader = string.Empty;
foreach (Node x in PList)
{
if (x.IsRoot & PRoot == x.Child)
{
RootHeader = x.Child;
}
}
__res += RootHeader+ "<ul>\n";
foreach (Node itm in PList)
{
if (itm.Parent.Equals(PRoot))
{
__res += string.Format("<ul><li>{0}{1}</li></ul>\n", itm.Child, GetChildren(itm.Child, PList));
}
}
__res += "</ul>\n";
return __res;
}
return string.Empty;
}
private static List<Node> ExtractNode(List<string> Urls)
{
List<Node> __NodeList = new List<Node>();
foreach (string itm in Urls)
{
string[] __arr = itm.Split('/');
int __idx = -1;
foreach (string node in __arr)
{
__idx += 1;
if (__idx == 0)
{
Node __node = new Node(node, "");
if (!__NodeList.Exists(x => x.Child == __node.Child & x.Parent == __node.Parent))
{
__node.IsRoot = true;
__NodeList.Add(__node);
}
}
else
{
Node __node = new Node(node, __arr[__idx - 1].ToString());
{
if (!__NodeList.Exists (x => x.Child == __node.Child & x.Parent == __node.Parent))
{
__NodeList.Add(__node);
}
}
}
}
}
return __NodeList;
}
anyway it's not optimised, I'm sure I can clean it up a lot..

Categories