Using OSMSharp I am having trouble to open a stream for a file (which I can provide on demand)
The error occurs in PBFReader (line 104)
using (var tmp = new LimitedStream(_stream, length))
{
header = _runtimeTypeModel.Deserialize(tmp, null, _blockHeaderType) as BlobHeader;
}
and states: "ProtoBuf.ProtoException: 'Invalid field in source data: 0'" which might mean different things as I have read in this SO question.
The file opens and is visualized with QGis so is not corrupt in my opinion.
Can it be that the contracts do not match? Is OsmSharp/core updated to the latest .proto files for OSM from here (although not sure if this is the real original source for the definition files).
And what might make more sense, can it be that the file I attached is generated for v2 of OSM PBF specification?
In the code at the line of the exception I see the following comment which makes me wonder:
// TODO: remove some of the v1 specific code.
// TODO: this means also to use the built-in capped streams.
// code borrowed from: http://stackoverflow.com/questions/4663298/protobuf-net-deserialize-open-street-maps
// I'm just being lazy and re-using something "close enough" here
// note that v2 has a big-endian option, but Fixed32 assumes little-endian - we
// actually need the other way around (network byte order):
// length = IntLittleEndianToBigEndian((uint)length);
BlobHeader header;
// again, v2 has capped-streams built in, but I'm deliberately
// limiting myself to v1 features
So this makes me wonder if OSM Sharp is (still) up-to-date.
My sandbox code looks like this:
using OsmSharp.Streams;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OsmSharp.Tags;
namespace OsmSharp
{
class Program
{
private const string Path = #"C:\Users\Bernoulli IT\Documents\Applications\Argaleo\Test\";
private const string FileNameAntarctica = "antarctica-latest.osm";
private const string FileNameOSPbf = "OSPbf";
private const Boolean useRegisterSource = false;
private static KeyValuePair<string, string> KeyValuePair = new KeyValuePair<string, string>("joep", "monita");
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
//string fileName = $#"{Path}\{FileNameAntarctica}.pbf";
string fileName = $#"{Path}\{FileNameOSPbf}.pbf";
string newFileName = $"{fileName.Replace(".pbf", string.Empty)}-{Guid.NewGuid().ToString().Substring(0, 4)}.pbf";
Console.WriteLine("*** Complete");
string fileNameOutput = CompleteFlow(fileName, newFileName);
Console.WriteLine("");
Console.WriteLine("*** Display");
DisplayFlow(fileNameOutput);
Console.ReadLine();
}
private static string CompleteFlow(string fileName, string newFileName)
{
// 1. Open file and convert to bytes
byte[] fileBytes = FileToBytes(fileName);
// 2. Bytes to OSM stream source (pbf)
PBFOsmStreamSource osmStreamSource;
osmStreamSource = BytesToOsmStreamSource(fileBytes);
osmStreamSource.MoveNext();
if (osmStreamSource.Current() == null)
{
osmStreamSource = FileToOsmStreamSource(fileName);
osmStreamSource.MoveNext();
if (osmStreamSource.Current() == null)
{
throw new Exception("No current in stream.");
}
}
// 3. Add custom tag
AddTag(osmStreamSource);
// 4. OSM stream source to bytes
//byte[] osmStreamSourceBytes = OsmStreamSourceToBytes(osmStreamSource);
// 5. Bytes to file
//string fileNameOutput = BytesToFile(osmStreamSourceBytes, newFileName);
OsmStreamSourceToFile(osmStreamSource, newFileName);
Console.WriteLine(newFileName);
return newFileName;
}
private static void DisplayFlow(string fileName)
{
// 1. Open file and convert to bytes
byte[] fileBytes = FileToBytes(fileName);
// 2. Bytes to OSM stream source (pbf)
BytesToOsmStreamSource(fileBytes);
}
private static byte[] FileToBytes(string fileName)
{
Console.WriteLine(fileName);
return File.ReadAllBytes(fileName);
}
private static PBFOsmStreamSource BytesToOsmStreamSource(byte[] bytes)
{
MemoryStream memoryStream = new MemoryStream(bytes);
memoryStream.Position = 0;
PBFOsmStreamSource osmStreamSource = new PBFOsmStreamSource(memoryStream);
foreach (OsmGeo element in osmStreamSource.Where(osmGeo => osmGeo.Tags.Any(tag => tag.Key.StartsWith(KeyValuePair.Key))))
{
foreach (Tag elementTag in element.Tags.Where(tag => tag.Key.StartsWith(KeyValuePair.Key)))
{
Console.WriteLine("!!!!!!!!!!!!!! Tag found while reading !!!!!!!!!!!!!!!!!!".ToUpper());
}
}
return osmStreamSource;
}
private static PBFOsmStreamSource FileToOsmStreamSource(string fileName)
{
using (FileStream fileStream = new FileInfo(fileName).OpenRead())
{
PBFOsmStreamSource osmStreamSource = new PBFOsmStreamSource(fileStream);
return osmStreamSource;
}
}
private static void AddTag(PBFOsmStreamSource osmStreamSource)
{
osmStreamSource.Reset();
OsmGeo osmGeo = null;
while (osmGeo == null)
{
osmStreamSource.MoveNext();
osmGeo = osmStreamSource.Current();
if(osmGeo?.Tags == null)
{
osmGeo = null;
}
}
osmGeo.Tags.Add("joep", "monita");
Console.WriteLine($"{osmGeo.Tags.FirstOrDefault(tag => tag.Key.StartsWith(KeyValuePair.Key)).Key} - {osmGeo.Tags.FirstOrDefault(tag => tag.Key.StartsWith(KeyValuePair.Key)).Value}");
}
private static byte[] OsmStreamSourceToBytes(PBFOsmStreamSource osmStreamSource)
{
MemoryStream memoryStream = new MemoryStream();
PBFOsmStreamTarget target = new PBFOsmStreamTarget(memoryStream, true);
osmStreamSource.Reset();
target.Initialize();
UpdateTarget(osmStreamSource, target);
target.Flush();
target.Close();
return memoryStream.ToArray();
}
private static string BytesToFile(byte[] bytes, string fileName)
{
using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
fs.Write(bytes, 0, bytes.Length);
}
return fileName;
}
private static void OsmStreamSourceToFile(PBFOsmStreamSource osmStreamSource, string fileName)
{
using (FileStream fileStream = new FileInfo(fileName).OpenWrite())
{
PBFOsmStreamTarget target = new PBFOsmStreamTarget(fileStream, true);
osmStreamSource.Reset();
target.Initialize();
UpdateTarget(osmStreamSource, target);
target.Flush();
target.Close();
}
}
private static void UpdateTarget(OsmStreamSource osmStreamSource, OsmStreamTarget osmStreamTarget)
{
if (useRegisterSource)
{
osmStreamTarget.RegisterSource(osmStreamSource, osmGeo => true);
osmStreamTarget.Pull();
}
else
{
bool isFirst = true;
foreach (OsmGeo osmGeo in osmStreamSource)
{
Tag? tag = osmGeo.Tags?.FirstOrDefault(t => t.Key == KeyValuePair.Key);
switch (osmGeo.Type)
{
case OsmGeoType.Node:
if (isFirst)
{
for (int indexer = 0; indexer < 1; indexer++)
{
(osmGeo as Node).Tags.Add(new Tag(KeyValuePair.Key + Guid.NewGuid(), KeyValuePair.Value));
}
isFirst = false;
}
osmStreamTarget.AddNode(osmGeo as Node);
break;
case OsmGeoType.Way:
osmStreamTarget.AddWay(osmGeo as Way);
break;
case OsmGeoType.Relation:
osmStreamTarget.AddRelation(osmGeo as Relation);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
}
}
Already I posted this question on the GITHube page of OSMSharp as is linked here. Any help would be very appreciated.
I am a decades-old C programmer. Unity is my first foray into modern C and I find myself pleasantly surprised - but some of the subtleties of the language have escaped me.
First a technical question: What is the right way to store a Unity Object in a generic class? In the example below I get the following error:
Assets/scripts/MediaTest.cs(49,44): error CS0030: Cannot convert type `UnityEngine.Texture2D' to `T'
Second the real question: What is a better approach to loading a set of textures?
Thanks in advance for your help
/*
* MediaTest.cs
*
* Pared down generic class test
*
*/
using System.Collections; // Load IEnumerable
using System.Collections.Generic; // Load Dictionary
using System.Text.RegularExpressions; // Load Regex
using UnityEngine; // Load UnityEngine.Object
public class MediaTest<T> : IEnumerable where T : UnityEngine.Object {
private Dictionary<string, MediaContent> _dict =
new Dictionary<string, MediaContent>();
private class MediaContent {
public string path { get; set; }
public T item { get; set; }
public MediaContent(string path, T item) {
this.path = path;
this.item = item;
}
}
// Indexer to return a UnityEngine.Object by filename
public T this[string name] {
get { return (T)_dict[name].item; }
}
// Convert a path to just the filename
public string Basename(string path) {
return new Regex(#"^.*/").Replace(path, "");
}
// Iterate through the filenames (keys) to the stored objects
public IEnumerator GetEnumerator() {
foreach (string name in _dict.Keys) {
yield return name;
}
}
// Read in the Resource at the specified path into a UnityEngine.Object
public void Load(string path, bool load=false) {
string name = Basename(path);
if (this.GetType() == typeof(Media<Texture2D>) && IsStill(name)) {
T item = (load) ? (T)Resources.Load<Texture2D>(path) : null;
_dict[name] = new MediaContent(path, item);
return;
}
if (this.GetType() == typeof(Media<AudioClip>) && IsAudio(name)) {
T item = (load) ? (T)Resources.Load<AudioClip>(path) : null;
_dict[name] = new MediaContent(path, item);
return;
}
}
// The real code uses Regex.Match on the file extension for supported types
public bool IsStill(string name) { return true; }
public bool IsAudio(string name) { return true; }
}
Here is the working code with updates from the comments. Thanks derHugo!
public void Load(string path, bool load=false) {
// Translate the filesystem path to a Resources relative path by removing both
// The */Resources prefix and the filename extention
Regex re_path = new Regex(#".*/Resources/");
Regex re_ext = new Regex(#"\.\w+$");
string relpath = re_ext.Replace(re_path.Replace(path, ""), "");
// Create an asset name from the path
string name = System.IO.Path.GetFileName(relpath);
// Skip this file if it doesn't match our type
if ( (typeof(T) == typeof(Texture2D) && IsStill(path)) ||
// (typeof(T) == typeof(Video) && IsVideo(path)) ||
(typeof(T) == typeof(AudioClip) && IsAudio(name))) {
T item = (load) ? Resources.Load(relpath) as T : null;
_dict[name] = new MediaContent(path, item);
}
}
I have a unit test, calling a service that makes use of Autorest generated code to call my Api.
I want my unit test to display the error that my Api is throwing, but there seems to be an error in the service's error handling.
I am using the following command to generate code to consume my api.
autorest --input-file=https://mywebsite.com.au:4433/myapi/api-docs/v1/swagger.json --output-folder=generated --csharp --namespace=MyConnector
The generated "client code" contains
/// <param name='request'>
/// </param>
/// <param name='customHeaders'>
/// Headers that will be added to request.
/// </param>
/// <param name='cancellationToken'>
/// The cancellation token.
/// </param>
/// <exception cref="HttpOperationException">
/// Thrown when the operation returned an invalid status code
/// </exception>
/// <exception cref="SerializationException">
/// Thrown when unable to deserialize the response
/// </exception>
/// <return>
/// A response object containing the response body and response headers.
/// </return>
public async Task<HttpOperationResponse<GetAvailableCarriersResponse>> GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(GetAvailableCarriersRequest request = default(GetAvailableCarriersRequest), Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
{
// Tracing
bool _shouldTrace = ServiceClientTracing.IsEnabled;
string _invocationId = null;
if (_shouldTrace)
{
_invocationId = ServiceClientTracing.NextInvocationId.ToString();
Dictionary<string, object> tracingParameters = new Dictionary<string, object>();
tracingParameters.Add("request", request);
tracingParameters.Add("cancellationToken", cancellationToken);
ServiceClientTracing.Enter(_invocationId, this, "GetAvailableCarriersByJobHeaderId", tracingParameters);
}
// Construct URL
var _baseUrl = BaseUri.AbsoluteUri;
var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "api/shipping-management/Get-Available-Carriers").ToString();
// Create HTTP transport objects
var _httpRequest = new HttpRequestMessage();
HttpResponseMessage _httpResponse = null;
_httpRequest.Method = new HttpMethod("POST");
_httpRequest.RequestUri = new System.Uri(_url);
// Set Headers
if (customHeaders != null)
{
foreach(var _header in customHeaders)
{
if (_httpRequest.Headers.Contains(_header.Key))
{
_httpRequest.Headers.Remove(_header.Key);
}
_httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value);
}
}
// Serialize Request
string _requestContent = null;
if(request != null)
{
_requestContent = SafeJsonConvert.SerializeObject(request, SerializationSettings);
_httpRequest.Content = new StringContent(_requestContent, System.Text.Encoding.UTF8);
_httpRequest.Content.Headers.ContentType =System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json-patch+json; charset=utf-8");
}
// Send Request
if (_shouldTrace)
{
ServiceClientTracing.SendRequest(_invocationId, _httpRequest);
}
cancellationToken.ThrowIfCancellationRequested();
_httpResponse = await HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false);
if (_shouldTrace)
{
ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse);
}
HttpStatusCode _statusCode = _httpResponse.StatusCode;
cancellationToken.ThrowIfCancellationRequested();
string _responseContent = null;
if ((int)_statusCode != 200)
{
var ex = new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", _statusCode));
if (_httpResponse.Content != null) {
_responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
}
else {
_responseContent = string.Empty;
}
ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent);
ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent);
if (_shouldTrace)
{
ServiceClientTracing.Error(_invocationId, ex);
}
_httpRequest.Dispose();
if (_httpResponse != null)
{
_httpResponse.Dispose();
}
throw ex;
}
// Create Result
var _result = new HttpOperationResponse<GetAvailableCarriersResponse>();
_result.Request = _httpRequest;
_result.Response = _httpResponse;
// Deserialize Response
if ((int)_statusCode == 200)
{
_responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
_result.Body = SafeJsonConvert.DeserializeObject<GetAvailableCarriersResponse>(_responseContent, DeserializationSettings);
}
catch (JsonException ex)
{
_httpRequest.Dispose();
if (_httpResponse != null)
{
_httpResponse.Dispose();
}
throw new SerializationException("Unable to deserialize the response.", _responseContent, ex);
}
}
if (_shouldTrace)
{
ServiceClientTracing.Exit(_invocationId, _result);
}
return _result;
}
I have a unit test to call the generated code using
var api = MakeApi();
var task=api.GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(req);
var carriers = task.Result.Body.Carriers;
where
private static MyApiService MakeApi()
{
var setting = new MyAPISettings(false);
var api = new MyApiService(setting);
return api;
}
and MyApiService contains (with altered namespaces)
public Task<HttpOperationResponse<GetAvailableCarriersResponse>> GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(
GetAvailableCarriersRequest request = default(GetAvailableCarriersRequest), Dictionary<string, List<string>> customHeaders = null,
CancellationToken cancellationToken = default(CancellationToken))
{
return ApiCaller.ExecuteAsync(
async headers => await API.GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(request, headers, cancellationToken),
async () => await GetTokenHeadersAsync(customHeaders));
}
where apicaller is
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyServices
{
public static class ApiCaller
{
private static Dictionary<string, List<string>> Headers { get; set; }
private static string GetHeadersMessage()
{
string ret = "";
if (Headers != null)
{
foreach (string key in Headers.Keys)
{
if (Headers[key] != null)
{
foreach (string value in Headers[key])
{
ret = $"{key}-{value}\n";
}
}
}
}
return ret;
}
public async static Task<T> ExecuteAsync<T>(Func<Dictionary<string, List<string>>, Task<T>> f,
Func<Task<Dictionary<string, List<string>>>> getHeaders)
{
T ret = default(T);
try
{
try
{
if (getHeaders != null && Headers == null)
{
Headers = await getHeaders();
}
ret = await f(Headers);
}
catch (Microsoft.Rest.HttpOperationException ex1)
{
if (ex1.Response?.StatusCode == System.Net.HttpStatusCode.Unauthorized && getHeaders != null)
{
Headers = await getHeaders();
ret = await f(Headers);
}
else
{
throw;
}
}
}
catch (Exception ex)
{
//Log.Error(ex, $"... API CALL ERROR ...\nHEADERS:{GetHeadersMessage()}");
throw new Exception($"Error calling the API. {ex.Message}", ex);
}
return ret;
}
}
}
My Api throws an InternalServerError
However when I run the unit test, I get an error in the client code.
The error occurs at
// Create Result
var _result = new HttpOperationResponse<GetAvailableCarriersResponse>();
And is
System.Exception: Error calling the API. Operation returned an invalid status code 'InternalServerError' ---> Microsoft.Rest.HttpOperationException: Operation returned an invalid status code 'InternalServerError'
at MyConnector.MyApi.<GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync>d__49.MoveNext()
How can I work around this?
I note that the code for HttpOperationResponse is
namespace Microsoft.Rest
{
/// <summary>
/// Represents the base return type of all ServiceClient REST operations.
/// </summary>
public class HttpOperationResponse<T> : HttpOperationResponse, IHttpOperationResponse<T>, IHttpOperationResponse
{
/// <summary>Gets or sets the response object.</summary>
public T Body { get; set; }
}
}
Here is the structure for GetAvailableCarriersResponse
using Newtonsoft.Json;
using System.Collections.Generic;
public partial class GetAvailableCarriersResponse
{
public GetAvailableCarriersResponse()
{
CustomInit();
}
public GetAvailableCarriersResponse(IList<DeliverBy> carriers = default(IList<DeliverBy>))
{
Carriers = carriers;
CustomInit();
}
partial void CustomInit();
[JsonProperty(PropertyName = "carriers")]
public IList<DeliverBy> Carriers { get; set; }
}
[Update]
In ApiCaller ExecuteAsync the following executes.
throw;
If I catch the error at this point, it's (edited) ToString() returns
"Microsoft.Rest.HttpOperationException: Operation returned an invalid status code 'InternalServerError' at MyAPI.
<GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync>d__49.MoveNext() in
MyConnector\\generated\\MyAPI.cs:line 4018
End of stack trace from previous location where exception was thrown at
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at
System.Runtime.CompilerServices.TaskAwaiter.
HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at MyApiService.<>c__DisplayClass39_0.<<GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync>b__0>d.MoveNext()
in MyApiService.cs:line 339
End of stack trace from previous location where exception was thrown
at System.Runtime.ExceptionServices.ExceptionDispatch
Info.Throw() at System.Runtime.CompilerServices.TaskAwaiter.
HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at MyServices.ApiCaller.<ExecuteAsync>d__5`1.MoveNext()
in ApiCaller.cs:line 50"
I edited some of the names in the above code to simplify and obfuscate.
[Update]
The problem seems to be to do with the getHeaders parameter to ApiCaller.ExecuteAsync
[Update]
If I examine ex1 thrown in ExecuteAsync, I can get my Api Error type using
ex1.Response.StatusCode
but how do I get the error description?
What I did to get error description is to cast it to one of the error types generated by Autorest.
if (myRawResponseObject is My422Response my422Response)
{
// Response has HTTP Status Code 422
Console.WriteLine($"Error Response Type: {my422Response.ToString()}");
}
If you OpenAPI document defines error properties for a 422 response, then you will find them on the My422Response object.
I am using this Code in dataGridView1_SelectionChanged and dataGridView1_CellEndEdit event. It works properly but when I press the enter Key firstly the edit control focus jumps on new row of selected column and then automatically goes back to the upper row and select the next cell. But I want to transfer directly to the next cell of the selected row.
For more details I attached a picture.
Please help me. I am trying a lot of logic for this.
Here my code
try
{
if (MouseButtons != 0) return;
if (celWasEndEdit != null && dataGridView1.CurrentCell != null)
{
// if we are currently in the next line of last edit cell
if (dataGridView1.CurrentCell.RowIndex == celWasEndEdit.RowIndex + 1 &&
dataGridView1.CurrentCell.ColumnIndex == celWasEndEdit.ColumnIndex)
{
int iColNew;
int iRowNew = 0;
if (celWasEndEdit.ColumnIndex >= dataGridView1.ColumnCount - 1)
{
iColNew = 0;
iRowNew = dataGridView1.CurrentCell.RowIndex;
}
//if we Edit the cell and press Enter the focus on the next cell
else
{
iColNew = celWasEndEdit.ColumnIndex + 1;
iRowNew = celWasEndEdit.RowIndex;
}
dataGridView1.CurrentCell = dataGridView1[iColNew, iRowNew];
}
}
celWasEndEdit = null;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
Create an extended control derived from the DataGridView and override the ProcessDialogKey() method.
For additional information see here.
Test sources
User control:
using System.Windows.Forms;
namespace DGNextEdit
{
public class UpdatedDataGridView : DataGridView
{
protected override bool ProcessDialogKey(Keys keyData)
{
if (keyData == Keys.Enter)
{
var col = CurrentCell.ColumnIndex;
var row = CurrentCell.RowIndex;
if (col >= ColumnCount - 1 && row < RowCount - 1)
row++;
CurrentCell = this[col < ColumnCount - 1 ? col + 1 : 0, row];
return true;
}
return base.ProcessDialogKey(keyData);
}
}
}
Test form:
using System.Windows.Forms;
namespace DGNextEdit
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}
Test form code-behind:
namespace DGNextEdit
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.dataGridView1 = new DGNextEdit.UpdatedDataGridView();
this.Column1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Column2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Column3 = new System.Windows.Forms.DataGridViewTextBoxColumn();
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
this.SuspendLayout();
//
// dataGridView1
//
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.Column1,
this.Column2,
this.Column3});
this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill;
this.dataGridView1.Location = new System.Drawing.Point(0, 0);
this.dataGridView1.Name = "dataGridView1";
this.dataGridView1.Size = new System.Drawing.Size(465, 261);
this.dataGridView1.TabIndex = 0;
//
// Column1
//
this.Column1.HeaderText = "Column1";
this.Column1.Name = "Column1";
//
// Column2
//
this.Column2.HeaderText = "Column2";
this.Column2.Name = "Column2";
//
// Column3
//
this.Column3.HeaderText = "Column3";
this.Column3.Name = "Column3";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(465, 261);
this.Controls.Add(this.dataGridView1);
this.Name = "Form1";
this.Text = "Form1";
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
this.ResumeLayout(false);
}
#endregion
private UpdatedDataGridView dataGridView1;
private System.Windows.Forms.DataGridViewTextBoxColumn Column1;
private System.Windows.Forms.DataGridViewTextBoxColumn Column2;
private System.Windows.Forms.DataGridViewTextBoxColumn Column3;
}
}
The database I'm working against has table names such as "table_name". That's fine, but I'd like to generate classes in the format "TableName" to work with in C#, Pascal style.
Is this possible?
Update: For use with EF6, see additional answer on this page.
Thanks to Alex's answer, I've now extended this code to a full working solution that solves this problem. Since it's taken me most of the day, I'm posting here to help others facing the same challenge. This includes a complete class for manipulating the edmx file (no points for pretty code here) which passes fairly verbose unit tests on varying input strings:
A few examples:
some_class > SomeClass
_some_class_ > SomeClass
some_1_said > Some1Said
I had some additional issues to deal with:
Firstly optionally not replacing underscores while still changing the string to pascal case (due to column names like "test_string" and "teststring", which both resolved to "TestString" otherwise, causing collisions).
Secondly, my code here takes an input parameter that modifies the object context class name that is generated.
Lastly, I wasn't initially sure how to get the designer file to update after modifiying the edmx, so I've included exact steps. I would recommend creating a pre build step in Visual Studio, which saves some of the remaining effort in updating from a modified database schema.
Updating an EDMX File to Reflect Database Changes
Double click the edmx file to show the design surface.
Right click the design surface and select "update model from database".
Select "yes include sensitive information in connection string".
Uncheck "save entity connection settings in App Config" (we already have these).
Select the appropriate database. In Add screen select Tables, Views etc.
Leave pluralise and foreign key options as checked. If creating the edmx file from fresh, you
are given the option to enter a model name. You can change or leave this.
Click finish.
Run the code below on the edmx file.
Depending on the state of the application, you may see errors here until the next step.
Right click the edmx file that you are updating, and select "Run custom tool" in Visual Studio. This will cuse
the designer.cs (C#) file to be updated.
Run build to check there are no compiler errors.
Run tests to ensure application is functioning correctly.
Any application issues following this should be expected according to the application
changes that have been made.
Replacing an edmx file in it's entirety.
Delete the edmx file, taking the designer file with it.
Right click the entities folder
From the create file dialogue, select ADO.NET Entity Data Model. Name it accroding to the class name you would like for your object context(IMPORTANT).
This value is referenced in the connection string, so take a look at your app config in case of issues.
In choose model contents, select generate from database.
Follow above instructions from step 3.
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace EdmxStringFormatter
{
public class Program
{
static void Main(string[] args)
{
if (args.Length < 1) return;
string filePath = args[0];
string entityName = null;
// Optionally do not replace underscores which
// helps with naming collisions with siimilarly named
// columns on some database tables.
bool replaceUnderscores = true;
// Allow for the replacement of the object context class name, which is useful
// where multiple databases have edmx files.
bool doEntityNameReplace = false;
if (args.Length > 1)
{
entityName = args[1];
doEntityNameReplace = true;
}
if (args.Length > 2)
{
replaceUnderscores = args[2] != "0";
}
if (!File.Exists(filePath))
{
StopWithMessage("Could not find specified file.");
return;
}
if (Path.GetExtension(filePath) != ".edmx")
{
StopWithMessage("This works only on EDMX files.");
return;
}
// Processing:
Console.WriteLine("Creating backup: " + Path.ChangeExtension(filePath, ".bak"));
File.Copy(filePath, Path.ChangeExtension(filePath, ".bak"), true);
Console.WriteLine("Reading target document...");
XDocument xdoc = XDocument.Load(filePath);
const string CSDLNamespace = "http://schemas.microsoft.com/ado/2008/09/edm";
const string MSLNamespace = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2008/10/edmx";
const string CSNameSpace = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
XElement csdl = xdoc.Descendants(XName.Get("Schema", CSDLNamespace)).First();
XElement msl = xdoc.Descendants(XName.Get("Mapping", MSLNamespace)).First();
XElement designerDiagram = xdoc.Descendants(XName.Get("Diagram", DiagramNamespace)).First();
//modifications for renaming everything, not just table names:
#region CSDL2
Console.WriteLine("Modifying CSDL...");
Console.WriteLine(" - modifying entity sets...");
foreach (var entitySet in csdl.Element(XName.Get("EntityContainer", CSDLNamespace)).Elements(XName.Get("EntitySet", CSDLNamespace)))
{
entitySet.Attribute("Name").Value = FormatString(entitySet.Attribute("Name").Value, replaceUnderscores);
entitySet.Attribute("EntityType").Value = FormatString(entitySet.Attribute("EntityType").Value, replaceUnderscores);
}
Console.WriteLine(" - modifying association sets...");
foreach (var associationSet in csdl.Element(XName.Get("EntityContainer", CSDLNamespace)).Elements(XName.Get("AssociationSet", CSDLNamespace)))
{
foreach (var end in associationSet.Elements(XName.Get("End", CSDLNamespace)))
{
end.Attribute("EntitySet").Value = FormatString(end.Attribute("EntitySet").Value, replaceUnderscores);
}
}
Console.WriteLine(" - modifying entity types...");
foreach (var entityType in csdl.Elements(XName.Get("EntityType", CSDLNamespace)))
{
entityType.Attribute("Name").Value = FormatString(entityType.Attribute("Name").Value, replaceUnderscores);
foreach (var key in entityType.Elements(XName.Get("Key", CSDLNamespace)))
{
foreach (var propertyRef in key.Elements(XName.Get("PropertyRef", CSDLNamespace)))
{
propertyRef.Attribute("Name").Value = FormatString(propertyRef.Attribute("Name").Value, replaceUnderscores);
}
}
foreach (var property in entityType.Elements(XName.Get("Property", CSDLNamespace)))
{
property.Attribute("Name").Value = FormatString(property.Attribute("Name").Value, replaceUnderscores);
}
foreach (var navigationProperty in entityType.Elements(XName.Get("NavigationProperty", CSDLNamespace)))
{
navigationProperty.Attribute("Name").Value = FormatString(navigationProperty.Attribute("Name").Value, replaceUnderscores);
}
}
Console.WriteLine(" - modifying associations...");
foreach (var association in csdl.Elements(XName.Get("Association", CSDLNamespace)))
{
foreach (var end in association.Elements(XName.Get("End", CSDLNamespace)))
{
end.Attribute("Type").Value = FormatString(end.Attribute("Type").Value, replaceUnderscores);
}
foreach (var propref in association.Descendants(XName.Get("PropertyRef", CSDLNamespace)))
{
//propertyrefs are contained in constraints
propref.Attribute("Name").Value = FormatString(propref.Attribute("Name").Value, replaceUnderscores);
}
}
#endregion
#region MSL2
Console.WriteLine("Modifying MSL...");
Console.WriteLine(" - modifying entity set mappings...");
foreach (var entitySetMapping in msl.Element(XName.Get("EntityContainerMapping", MSLNamespace)).Elements(XName.Get("EntitySetMapping", MSLNamespace)))
{
entitySetMapping.Attribute("Name").Value = FormatString(entitySetMapping.Attribute("Name").Value, replaceUnderscores);
foreach (var entityTypeMapping in entitySetMapping.Elements(XName.Get("EntityTypeMapping", MSLNamespace)))
{
entityTypeMapping.Attribute("TypeName").Value = FormatString(entityTypeMapping.Attribute("TypeName").Value, replaceUnderscores);
foreach
(var scalarProperty in
(entityTypeMapping.Element(XName.Get("MappingFragment", MSLNamespace))).Elements(XName.Get("ScalarProperty", MSLNamespace))
)
{
scalarProperty.Attribute("Name").Value = FormatString(scalarProperty.Attribute("Name").Value, replaceUnderscores);
}
}
}
Console.WriteLine(" - modifying association set mappings...");
foreach (var associationSetMapping in msl.Element(XName.Get("EntityContainerMapping", MSLNamespace)).Elements(XName.Get("AssociationSetMapping", MSLNamespace)))
{
foreach (var endProperty in associationSetMapping.Elements(XName.Get("EndProperty", MSLNamespace)))
{
foreach (var scalarProperty in endProperty.Elements(XName.Get("ScalarProperty", MSLNamespace)))
{
scalarProperty.Attribute("Name").Value = FormatString(scalarProperty.Attribute("Name").Value, replaceUnderscores);
}
}
}
#endregion
#region Designer
Console.WriteLine("Modifying designer content...");
foreach (var item in designerDiagram.Elements(XName.Get("EntityTypeShape", DiagramNamespace)))
{
item.Attribute("EntityType").Value = FormatString(item.Attribute("EntityType").Value, replaceUnderscores);
}
#endregion
// Optionally replace the entity name in case the default of "Entity" is not
// sufficient for your needs.
if (doEntityNameReplace)
{
Console.WriteLine("Modifying entity name refs...");
// CSDL
xdoc.Descendants(XName.Get("EntityContainer", CSDLNamespace)).First().Attribute("Name").Value = entityName;
// Diagram
xdoc.Descendants(XName.Get("Diagram", DiagramNamespace)).First().Attribute("Name").Value = entityName;
// Diagram
xdoc.Descendants(XName.Get("EntityContainerMapping", CSNameSpace)).First().Attribute("CdmEntityContainer").Value = entityName;
}
Console.WriteLine("Writing result...");
using (XmlTextWriter writer = new XmlTextWriter(filePath, Encoding.Default))
{
writer.Formatting = Formatting.Indented;
xdoc.WriteTo(writer);
}
}
/// <summary>
/// Formats the string to pascal case, additionally checking for a period
/// in the string (in which case it skips past the period, which indicates
/// the use of namespace in a string.
/// </summary>
/// <param name="str"></param>
/// <param name="replaceUnderscores"></param>
/// <returns></returns>
private static string FormatString(string str, bool replaceUnderscores = true)
{
char[] chars = str.ToCharArray();
var sb = new StringBuilder();
bool previousCharWasUpper = false;
bool lastOperationWasToLower = false;
int startPos = 0;
if (str.Contains("."))
{
if (str.IndexOf(".") < (str.Length - 1))
{
startPos = str.IndexOf(".") + 1;
}
sb.Append(str.Substring(0, startPos));
}
for (int i = startPos; i < chars.Length; i++)
{
char character = chars[i];
if (Char.IsLetter(character))
{
if (Char.IsLower(character))
{
bool toUpper = false;
if (i > 0)
{
// Look at the previous char to see if not a letter
if (!Char.IsLetter(chars[i - 1]))
{
toUpper = true;
}
}
if (i == 0 || toUpper)
{
character = Char.ToUpper(character);
lastOperationWasToLower = false;
}
}
else // IsUpper = true
{
if (previousCharWasUpper || lastOperationWasToLower)
{
character = Char.ToLower(character);
lastOperationWasToLower = true;
}
}
previousCharWasUpper = Char.IsUpper(character);
sb.Append(character);
}
else
{
if (Char.IsDigit(character))
{
sb.Append(character);
previousCharWasUpper = false;
lastOperationWasToLower = false;
}
else if(!replaceUnderscores)
{
if(character == '_')
{
sb.Append(character);
}
}
}
}
return sb.ToString();
}
private static void StopWithMessage(string str)
{
Console.WriteLine(str);
Console.ReadLine();
throw new InvalidOperationException("Cannot continue.");
}
}
}
Edit by Chris
Adapting to Visual Studio 2013 & EF6
The code namespaces need a little tweak in order to make it work with EF6:
const string CSDLNamespace = "http://schemas.microsoft.com/ado/2009/11/edm";
const string MSLNamespace = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2009/11/edmx";
const string CSNameSpace = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
plus you need to take care of designerDiagram(in my case, it wasn't found, so just replaced First() with FirstOrDefault() and added simple null check).
Update based on Chris' Edit To Original Answer
This is the full C# .edmx modification code for use in an Entity Framework 6 context (original answer was for EF4).
(Additional answer as opposed to edit due to character limit per answer)
Thank you Chris for your input. I have recently revisited this as a project using this tool has been upgraded to EF6. The full code for use with EF6 is copied below.
Note that this program code now operates on two files - the .edmx and the .edmx.diagram file. Visual Studio 2013 splits the diagram out into a separate file, and this needs editing otherwise the table / entity representations will not show up on the .edmx designer surface. The file path for the designer file is accepted as the second argument.
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace EdmxStringFormatter
{
public class Program
{
static void Main(string[] args)
{
if (args.Length < 1) return;
string filePath = args[0];
string designerFilePath = null;
string entityName = null;
// Optionally do not replace underscores which
// helps with naming collisions with siimilarly named
// columns on some database tables.
bool replaceUnderscores = true;
// Allow for the replacement of the object context class name, which is useful
// where multiple databases have edmx files.
bool doEntityNameReplace = false;
if (args.Length > 1)
{
designerFilePath = args[1];
}
if (args.Length > 2)
{
entityName = args[2];
doEntityNameReplace = true;
}
if (args.Length > 3)
{
replaceUnderscores = args[3] != "0";
}
if (!File.Exists(filePath))
{
StopWithMessage("Could not find specified file.");
return;
}
if (Path.GetExtension(filePath) != ".edmx")
{
StopWithMessage("This works only on EDMX files.");
return;
}
TransformEdmx(filePath, replaceUnderscores, doEntityNameReplace, entityName);
TransformEdmxDiagram(designerFilePath, replaceUnderscores);
}
private static void TransformEdmx(string filePath, bool replaceUnderscores, bool doEntityNameReplace, string entityName)
{
// Processing:
Console.WriteLine("Creating backup: " + Path.ChangeExtension(filePath, ".bak"));
File.Copy(filePath, Path.ChangeExtension(filePath, ".bak"), true);
Console.WriteLine("Reading target document...");
XDocument xdoc = XDocument.Load(filePath);
//const string CSDLNamespace = "http://schemas.microsoft.com/ado/2008/09/edm";
//const string MSLNamespace = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
//const string DiagramNamespace = "http://schemas.microsoft.com/ado/2008/10/edmx";
//const string CSNameSpace = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
const string CSDLNamespace = "http://schemas.microsoft.com/ado/2009/11/edm";
const string MSLNamespace = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2009/11/edmx";
const string CSNameSpace = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
XElement csdl = xdoc.Descendants(XName.Get("Schema", CSDLNamespace)).First();
XElement msl = xdoc.Descendants(XName.Get("Mapping", MSLNamespace)).First();
//modifications for renaming everything, not just table names:
#region CSDL2
Console.WriteLine("Modifying CSDL...");
Console.WriteLine(" - modifying entity sets...");
foreach (
var entitySet in
csdl.Element(XName.Get("EntityContainer", CSDLNamespace)).Elements(XName.Get("EntitySet", CSDLNamespace)))
{
entitySet.Attribute("Name").Value = FormatString(entitySet.Attribute("Name").Value, replaceUnderscores);
entitySet.Attribute("EntityType").Value = FormatString(entitySet.Attribute("EntityType").Value,
replaceUnderscores);
}
Console.WriteLine(" - modifying association sets...");
foreach (
var associationSet in
csdl.Element(XName.Get("EntityContainer", CSDLNamespace))
.Elements(XName.Get("AssociationSet", CSDLNamespace)))
{
foreach (var end in associationSet.Elements(XName.Get("End", CSDLNamespace)))
{
end.Attribute("EntitySet").Value = FormatString(end.Attribute("EntitySet").Value, replaceUnderscores);
}
}
Console.WriteLine(" - modifying entity types...");
foreach (var entityType in csdl.Elements(XName.Get("EntityType", CSDLNamespace)))
{
entityType.Attribute("Name").Value = FormatString(entityType.Attribute("Name").Value, replaceUnderscores);
foreach (var key in entityType.Elements(XName.Get("Key", CSDLNamespace)))
{
foreach (var propertyRef in key.Elements(XName.Get("PropertyRef", CSDLNamespace)))
{
propertyRef.Attribute("Name").Value = FormatString(propertyRef.Attribute("Name").Value,
replaceUnderscores);
}
}
foreach (var property in entityType.Elements(XName.Get("Property", CSDLNamespace)))
{
property.Attribute("Name").Value = FormatString(property.Attribute("Name").Value, replaceUnderscores);
}
foreach (var navigationProperty in entityType.Elements(XName.Get("NavigationProperty", CSDLNamespace)))
{
navigationProperty.Attribute("Name").Value = FormatString(navigationProperty.Attribute("Name").Value,
replaceUnderscores);
}
}
Console.WriteLine(" - modifying associations...");
foreach (var association in csdl.Elements(XName.Get("Association", CSDLNamespace)))
{
foreach (var end in association.Elements(XName.Get("End", CSDLNamespace)))
{
end.Attribute("Type").Value = FormatString(end.Attribute("Type").Value, replaceUnderscores);
}
foreach (var propref in association.Descendants(XName.Get("PropertyRef", CSDLNamespace)))
{
//propertyrefs are contained in constraints
propref.Attribute("Name").Value = FormatString(propref.Attribute("Name").Value, replaceUnderscores);
}
}
#endregion
#region MSL2
Console.WriteLine("Modifying MSL...");
Console.WriteLine(" - modifying entity set mappings...");
foreach (
var entitySetMapping in
msl.Element(XName.Get("EntityContainerMapping", MSLNamespace))
.Elements(XName.Get("EntitySetMapping", MSLNamespace)))
{
entitySetMapping.Attribute("Name").Value = FormatString(entitySetMapping.Attribute("Name").Value,
replaceUnderscores);
foreach (var entityTypeMapping in entitySetMapping.Elements(XName.Get("EntityTypeMapping", MSLNamespace)))
{
entityTypeMapping.Attribute("TypeName").Value = FormatString(entityTypeMapping.Attribute("TypeName").Value,
replaceUnderscores);
foreach
(var scalarProperty in
(entityTypeMapping.Element(XName.Get("MappingFragment", MSLNamespace))).Elements(
XName.Get("ScalarProperty", MSLNamespace))
)
{
scalarProperty.Attribute("Name").Value = FormatString(scalarProperty.Attribute("Name").Value,
replaceUnderscores);
}
}
}
Console.WriteLine(" - modifying association set mappings...");
foreach (
var associationSetMapping in
msl.Element(XName.Get("EntityContainerMapping", MSLNamespace))
.Elements(XName.Get("AssociationSetMapping", MSLNamespace)))
{
foreach (var endProperty in associationSetMapping.Elements(XName.Get("EndProperty", MSLNamespace)))
{
foreach (var scalarProperty in endProperty.Elements(XName.Get("ScalarProperty", MSLNamespace)))
{
scalarProperty.Attribute("Name").Value = FormatString(scalarProperty.Attribute("Name").Value,
replaceUnderscores);
}
}
}
#endregion
// Optionally replace the entity name in case the default of "Entity" is not
// sufficient for your needs.
if (doEntityNameReplace)
{
Console.WriteLine("Modifying entity name refs...");
// CSDL
xdoc.Descendants(XName.Get("EntityContainer", CSDLNamespace)).First().Attribute("Name").Value = entityName;
// Diagram
var diagramDescendants = xdoc.Descendants(XName.Get("Diagram", DiagramNamespace)).FirstOrDefault();
if (diagramDescendants != null)
{
diagramDescendants.Attribute("Name").Value = entityName;
}
// Diagram
xdoc.Descendants(XName.Get("EntityContainerMapping", CSNameSpace)).First().Attribute("CdmEntityContainer").Value
= entityName;
}
Console.WriteLine("Writing result...");
using (XmlTextWriter writer = new XmlTextWriter(filePath, Encoding.Default))
{
writer.Formatting = Formatting.Indented;
xdoc.WriteTo(writer);
}
}
private static void TransformEdmxDiagram(string filePath, bool replaceUnderscores)
{
// Processing:
Console.WriteLine("Creating backup: " + Path.ChangeExtension(filePath, ".bak"));
File.Copy(filePath, Path.ChangeExtension(filePath, ".bak"), true);
Console.WriteLine("Reading target document...");
XDocument xdoc = XDocument.Load(filePath);
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2009/11/edmx";
XElement designerDiagram = xdoc.Descendants(XName.Get("Diagram", DiagramNamespace)).FirstOrDefault();
#region Designer
Console.WriteLine("Modifying designer content...");
if (designerDiagram != null)
{
foreach (var item in designerDiagram.Elements(XName.Get("EntityTypeShape", DiagramNamespace)))
{
item.Attribute("EntityType").Value = FormatString(item.Attribute("EntityType").Value, replaceUnderscores);
}
}
#endregion
Console.WriteLine("Writing result...");
using (XmlTextWriter writer = new XmlTextWriter(filePath, Encoding.Default))
{
writer.Formatting = Formatting.Indented;
xdoc.WriteTo(writer);
}
}
/// <summary>
/// Formats the string to pascal case, additionally checking for a period
/// in the string (in which case it skips past the period, which indicates
/// the use of namespace in a string.
/// </summary>
/// <param name="str"></param>
/// <param name="replaceUnderscores"></param>
/// <returns></returns>
private static string FormatString(string str, bool replaceUnderscores = true)
{
char[] chars = str.ToCharArray();
var sb = new StringBuilder();
bool previousCharWasUpper = false;
bool lastOperationWasToLower = false;
int startPos = 0;
if (str.Contains("."))
{
if (str.IndexOf(".") < (str.Length - 1))
{
startPos = str.IndexOf(".") + 1;
}
sb.Append(str.Substring(0, startPos));
}
for (int i = startPos; i < chars.Length; i++)
{
char character = chars[i];
if (Char.IsLetter(character))
{
if (Char.IsLower(character))
{
bool toUpper = false;
if (i > 0)
{
// Look at the previous char to see if not a letter
if (!Char.IsLetter(chars[i - 1]))
{
toUpper = true;
}
}
if (i == 0 || toUpper)
{
character = Char.ToUpper(character);
lastOperationWasToLower = false;
}
}
else // IsUpper = true
{
if (previousCharWasUpper || lastOperationWasToLower)
{
character = Char.ToLower(character);
lastOperationWasToLower = true;
}
}
previousCharWasUpper = Char.IsUpper(character);
sb.Append(character);
}
else
{
if (Char.IsDigit(character))
{
sb.Append(character);
previousCharWasUpper = false;
lastOperationWasToLower = false;
}
else if (!replaceUnderscores)
{
if (character == '_')
{
sb.Append(character);
}
}
}
}
return sb.ToString();
}
private static void StopWithMessage(string str)
{
Console.WriteLine(str);
Console.ReadLine();
throw new InvalidOperationException("Cannot continue.");
}
}
}
some_class > SomeClass
didn't work for me, instead I got:
some_class > Someclass
To fix it I moved
previousCharWasUpper = false;
a few lines up. It's near the end of the old code block.
else
{
if (Char.IsDigit(character))
{
sb.Append(character);
previousCharWasUpper = false;
lastOperationWasToLower = false;
}
else if(!replaceUnderscores)
{
if(character == '_')
{
sb.Append(character);
}
}
}
Changed to:
else
{
previousCharWasUpper = false;
if (Char.IsDigit(character))
{
sb.Append(character);
lastOperationWasToLower = false;
}
else if(!replaceUnderscores)
{
if(character == '_')
{
sb.Append(character);
}
}
}