High-level DB modify methods implemented (actions, changes, changesets); Thread.incrementViewsCounter() implemented

main
Inga 🏳‍🌈 15 years ago
parent 9ca91905b6
commit c38add376a
  1. 2
      Builder/IISMainHandler/build.txt
  2. 10
      Common/Common.csproj
  3. 11
      Common/ISqlObjectTableSpec.cs
  4. 7
      Common/SqlObject.cs
  5. 39
      Common/actions/AbstractChange.cs
  6. 13
      Common/actions/AbstractFieldValue.cs
  7. 100
      Common/actions/ChangeSet.cs
  8. 27
      Common/actions/IncrementFieldValue.cs
  9. 38
      Common/actions/InsertChange.cs
  10. 68
      Common/actions/InsertOrUpdateChange.cs
  11. 23
      Common/actions/ReferenceFieldValue.cs
  12. 23
      Common/actions/ScalarFieldValue.cs
  13. 40
      Common/actions/UpdateChange.cs
  14. 5
      Common/dataobjects/Board.cs
  15. 5
      Common/dataobjects/Category.cs
  16. 5
      Common/dataobjects/Post.cs
  17. 26
      Common/dataobjects/Thread.cs
  18. 5
      Common/dataobjects/User.cs
  19. 1
      IISMainHandler/handlers/PostHandler.cs
  20. 1
      IISMainHandler/handlers/ThreadHandler.cs
  21. 10
      MySQLConnector/ConditionCompiler.cs
  22. 3
      MySQLConnector/Connection.cs

@ -45,6 +45,15 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="actions\AbstractFieldValue.cs" />
<Compile Include="actions\AbstractChange.cs" />
<Compile Include="actions\ChangeSet.cs" />
<Compile Include="actions\IncrementFieldValue.cs" />
<Compile Include="actions\InsertChange.cs" />
<Compile Include="actions\InsertOrUpdateChange.cs" />
<Compile Include="actions\ReferenceFieldValue.cs" />
<Compile Include="actions\ScalarFieldValue.cs" />
<Compile Include="actions\UpdateChange.cs" />
<Compile Include="Config.cs" /> <Compile Include="Config.cs" />
<Compile Include="dataobjects\Board.cs" /> <Compile Include="dataobjects\Board.cs" />
<Compile Include="dataobjects\Category.cs" /> <Compile Include="dataobjects\Category.cs" />
@ -54,6 +63,7 @@
<Compile Include="dataobjects\Thread.cs" /> <Compile Include="dataobjects\Thread.cs" />
<Compile Include="dataobjects\User.cs" /> <Compile Include="dataobjects\User.cs" />
<Compile Include="IOutputParams.cs" /> <Compile Include="IOutputParams.cs" />
<Compile Include="ISqlObjectTableSpec.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SqlObject.cs" /> <Compile Include="SqlObject.cs" />
<Compile Include="UserContext.cs" /> <Compile Include="UserContext.cs" />

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FLocal.Core.DB;
namespace FLocal.Common {
public interface ISqlObjectTableSpec : ITableSpec {
void refreshSqlObject(int id);
}
}

@ -11,7 +11,7 @@ namespace FLocal.Common {
protected SqlObject() : base() { protected SqlObject() : base() {
} }
abstract protected ITableSpec table { abstract protected ISqlObjectTableSpec table {
get; get;
} }
@ -116,5 +116,10 @@ namespace FLocal.Common {
return res; return res;
} }
protected static void Refresh(int id) {
Dictionary<int, T> objects = LoadByIdsForLoadingFromHash(new List<int>() { id });
objects[id].ReLoad();
}
} }
} }

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FLocal.Core.DB;
namespace FLocal.Common.actions {
abstract class AbstractChange {
private bool isApplied;
abstract public int? getId();
abstract public void Lock(Transaction transaction);
abstract protected void doApply(Transaction transaction);
public void Apply(Transaction transaction) {
if(!this.isApplied) {
this.doApply(transaction);
this.isApplied = true;
}
}
public readonly IEnumerable<AbstractChange> references;
public readonly ISqlObjectTableSpec tableSpec;
protected readonly Dictionary<string, AbstractFieldValue> data;
protected AbstractChange(ISqlObjectTableSpec tableSpec, Dictionary<string, AbstractFieldValue> data) {
this.tableSpec = tableSpec;
this.data = data;
this.references = from kvp in data where kvp.Value is ReferenceFieldValue select ((ReferenceFieldValue)kvp.Value).referenced;
this.isApplied = false;
}
}
}

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FLocal.Common.actions {
abstract class AbstractFieldValue {
abstract public string getStringRepresentation();
abstract public string getStringRepresentation(string oldInfo);
}
}

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FLocal.Core;
using FLocal.Core.DB;
namespace FLocal.Common.actions {
/// <summary>
/// Note that you should create ChangeSet instances outside of transactions they're using!
/// Otherwise, there will be a 100% DB deadlock in Dispose (or data in registry won't update)
/// </summary>
class ChangeSet : IDisposable {
private static readonly object tablesLockOrder_locker = new object();
private static IEnumerable<string> tablesLockOrder {
get {
return Cache<IEnumerable<string>>.instance.get(
tablesLockOrder_locker,
() => new List<string>() {
"Boards",
"Threads",
"Posts",
}
);
}
}
private object locker = new object();
private bool isAdding;
private bool isProcessing;
private bool isProcessed;
private Dictionary<string, HashSet<AbstractChange>> changesByTable;
public ChangeSet() {
this.isAdding = false;
this.isProcessing = false;
this.isProcessed = false;
this.changesByTable = new Dictionary<string,HashSet<AbstractChange>>();
foreach(string table in tablesLockOrder) {
this.changesByTable[table] = new HashSet<AbstractChange>();
}
}
public void Add(AbstractChange change) {
lock(locker) {
if(this.isAdding) throw new CriticalException("Cannot add inside add");
if(this.isProcessing || this.isProcessed) throw new CriticalException("ChangeSet is readonly");
this.isAdding = true;
}
if(!this.changesByTable.ContainsKey(change.tableSpec.name)) throw new CriticalException("Table not supported");
this.changesByTable[change.tableSpec.name].Add(change);
this.isAdding = false;
foreach(AbstractChange referencedChange in change.references) {
this.Add(referencedChange);
}
}
private void ApplyToChange(Transaction transaction, AbstractChange change) {
foreach(AbstractChange referenced in change.references) {
if(!referenced.getId().HasValue) {
this.ApplyToChange(transaction, referenced);
}
}
change.Apply(transaction);
}
public void Apply(Transaction transaction) {
lock(this.locker) {
if(this.isAdding) throw new CriticalException("Cannot process while adding");
if(this.isProcessing || this.isProcessed) throw new CriticalException("ChangeSet is already processed");
this.isProcessing = true;
}
foreach(string table in tablesLockOrder) {
foreach(AbstractChange change in (from AbstractChange _change in this.changesByTable[table] orderby _change.getId() select _change)) {
change.Lock(transaction);
}
}
foreach(KeyValuePair<string, HashSet<AbstractChange>> kvp in this.changesByTable) {
foreach(AbstractChange change in kvp.Value) {
this.ApplyToChange(transaction, change);
}
}
this.isProcessed = true;
}
public void Dispose() {
//if(!this.isProcessed) throw new CriticalException("ChangeSet is not processed yet");
foreach(KeyValuePair<string, HashSet<AbstractChange>> kvp in this.changesByTable) {
foreach(AbstractChange change in kvp.Value) {
change.tableSpec.refreshSqlObject(change.getId().Value);
}
}
}
}
}

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FLocal.Common.actions {
class IncrementFieldValue : AbstractFieldValue {
private readonly Func<string, string> incrementor;
public IncrementFieldValue(Func<string, string> incrementor) {
this.incrementor = incrementor;
}
public IncrementFieldValue()
: this(str => (int.Parse(str)+1).ToString()) {
}
public override string getStringRepresentation() {
throw new NotSupportedException();
}
public override string getStringRepresentation(string oldInfo) {
return this.incrementor(oldInfo);
}
}
}

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FLocal.Core.DB;
namespace FLocal.Common.actions {
class InsertChange : AbstractChange {
private int? id;
public InsertChange(ISqlObjectTableSpec tableSpec, Dictionary<string, AbstractFieldValue> data, int id)
: base(tableSpec, data) {
this.id = null;
}
public override int? getId() {
return this.id;
}
public override void Lock(Transaction transaction) {
Config.instance.mainConnection.lockTable(transaction, this.tableSpec);
}
protected override void doApply(Transaction transaction) {
Dictionary<string, string> processedData = new Dictionary<string,string>();
foreach(KeyValuePair<string, AbstractFieldValue> kvp in this.data) {
processedData[kvp.Key] = kvp.Value.getStringRepresentation();
}
this.id = int.Parse(Config.instance.mainConnection.insert(
transaction,
this.tableSpec,
processedData
));
}
}
}

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FLocal.Core;
using FLocal.Core.DB;
using FLocal.Core.DB.conditions;
namespace FLocal.Common.actions {
class InsertOrUpdateChange : AbstractChange {
private int? id;
private AbstractCondition condition;
public InsertOrUpdateChange(ISqlObjectTableSpec tableSpec, Dictionary<string, AbstractFieldValue> data, AbstractCondition condition)
: base(tableSpec, data) {
this.id = null;
this.condition = condition;
}
public override int? getId() {
return this.id;
}
public override void Lock(Transaction transaction) {
List<string> ids = Config.instance.mainConnection.LoadIdsByConditions(this.tableSpec, this.condition, Diapasone.unlimited, new JoinSpec[0]);
if(ids.Count > 1) {
throw new CriticalException("Not unique");
} else if(ids.Count == 1) {
this.id = int.Parse(ids[0]);
Config.instance.mainConnection.lockRow(transaction, this.tableSpec, this.id.ToString());
} else {
Config.instance.mainConnection.lockTable(transaction, this.tableSpec);
ids = Config.instance.mainConnection.LoadIdsByConditions(this.tableSpec, this.condition, Diapasone.unlimited, new JoinSpec[0]);
if(ids.Count > 1) {
throw new CriticalException("Not unique");
} else if(ids.Count == 1) {
this.id = int.Parse(ids[0]);
} else {
this.id = null;
}
}
}
protected override void doApply(Transaction transaction) {
Dictionary<string, string> processedData = new Dictionary<string,string>();
foreach(KeyValuePair<string, AbstractFieldValue> kvp in this.data) {
processedData[kvp.Key] = kvp.Value.getStringRepresentation();
}
if(this.id.HasValue) {
Config.instance.mainConnection.update(
transaction,
this.tableSpec,
this.id.ToString(),
processedData
);
} else {
Config.instance.mainConnection.insert(
transaction,
this.tableSpec,
processedData
);
}
}
}
}

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FLocal.Common.actions {
class ReferenceFieldValue : AbstractFieldValue {
public readonly AbstractChange referenced;
public ReferenceFieldValue(AbstractChange referenced) {
this.referenced = referenced;
}
public override string getStringRepresentation() {
return this.referenced.getId().Value.ToString();
}
public override string getStringRepresentation(string oldInfo) {
return this.referenced.getId().Value.ToString();
}
}
}

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FLocal.Common.actions {
class ScalarFieldValue : AbstractFieldValue {
private readonly string scalar;
public ScalarFieldValue(string scalar) {
this.scalar = scalar;
}
public override string getStringRepresentation() {
return this.scalar;
}
public override string getStringRepresentation(string oldInfo) {
return this.scalar;
}
}
}

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FLocal.Core.DB;
namespace FLocal.Common.actions {
class UpdateChange : AbstractChange {
private readonly int id;
public UpdateChange(ISqlObjectTableSpec tableSpec, Dictionary<string, AbstractFieldValue> data, int id)
: base(tableSpec, data) {
this.id = id;
}
public override int? getId() {
return this.id;
}
public override void Lock(Transaction transaction) {
Config.instance.mainConnection.lockRow(transaction, this.tableSpec, this.id.ToString());
}
protected override void doApply(Transaction transaction) {
Dictionary<string, string> row = Config.instance.mainConnection.LoadByIds(transaction, this.tableSpec, new List<string>() { this.id.ToString() })[0];
Dictionary<string, string> processedData = new Dictionary<string,string>();
foreach(KeyValuePair<string, AbstractFieldValue> kvp in this.data) {
processedData[kvp.Key] = kvp.Value.getStringRepresentation(row[kvp.Key]);
}
Config.instance.mainConnection.update(
transaction,
this.tableSpec,
this.id.ToString(),
processedData
);
}
}
}

@ -10,7 +10,7 @@ using FLocal.Core.DB.conditions;
namespace FLocal.Common.dataobjects { namespace FLocal.Common.dataobjects {
public class Board : SqlObject<Board> { public class Board : SqlObject<Board> {
public class TableSpec : FLocal.Core.DB.ITableSpec { public class TableSpec : ISqlObjectTableSpec {
public const string TABLE = "Boards"; public const string TABLE = "Boards";
public const string FIELD_ID = "Id"; public const string FIELD_ID = "Id";
public const string FIELD_SORTORDER = "SortOrder"; public const string FIELD_SORTORDER = "SortOrder";
@ -24,9 +24,10 @@ namespace FLocal.Common.dataobjects {
public static readonly TableSpec instance = new TableSpec(); public static readonly TableSpec instance = new TableSpec();
public string name { get { return TABLE; } } public string name { get { return TABLE; } }
public string idName { get { return FIELD_ID; } } public string idName { get { return FIELD_ID; } }
public void refreshSqlObject(int id) { Refresh(id); }
} }
protected override FLocal.Core.DB.ITableSpec table { get { return TableSpec.instance; } } protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } }
private int _sortOrder; private int _sortOrder;
public int sortOrder { public int sortOrder {

@ -9,7 +9,7 @@ using System.Xml.Linq;
namespace FLocal.Common.dataobjects { namespace FLocal.Common.dataobjects {
public class Category : SqlObject<Category> { public class Category : SqlObject<Category> {
public class TableSpec : ITableSpec { public class TableSpec : ISqlObjectTableSpec {
public const string TABLE = "Categories"; public const string TABLE = "Categories";
public const string FIELD_ID = "Id"; public const string FIELD_ID = "Id";
public const string FIELD_SORTORDER = "SortOrder"; public const string FIELD_SORTORDER = "SortOrder";
@ -18,9 +18,10 @@ namespace FLocal.Common.dataobjects {
public static readonly TableSpec instance = new TableSpec(); public static readonly TableSpec instance = new TableSpec();
public string name { get { return TABLE; } } public string name { get { return TABLE; } }
public string idName { get { return FIELD_ID; } } public string idName { get { return FIELD_ID; } }
public void refreshSqlObject(int id) { Refresh(id); }
} }
protected override ITableSpec table { get { return TableSpec.instance; } } protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } }
private string _name; private string _name;
public string name { public string name {

@ -9,7 +9,7 @@ using FLocal.Core.DB;
namespace FLocal.Common.dataobjects { namespace FLocal.Common.dataobjects {
public class Post : SqlObject<Post> { public class Post : SqlObject<Post> {
public class TableSpec : FLocal.Core.DB.ITableSpec { public class TableSpec : ISqlObjectTableSpec {
public const string TABLE = "Posts"; public const string TABLE = "Posts";
public const string FIELD_ID = "Id"; public const string FIELD_ID = "Id";
public const string FIELD_POSTERID = "PosterId"; public const string FIELD_POSTERID = "PosterId";
@ -24,9 +24,10 @@ namespace FLocal.Common.dataobjects {
public static readonly TableSpec instance = new TableSpec(); public static readonly TableSpec instance = new TableSpec();
public string name { get { return TABLE; } } public string name { get { return TABLE; } }
public string idName { get { return FIELD_ID; } } public string idName { get { return FIELD_ID; } }
public void refreshSqlObject(int id) { Refresh(id); }
} }
protected override FLocal.Core.DB.ITableSpec table { get { return TableSpec.instance; } } protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } }
private int _posterId; private int _posterId;
public int posterId { public int posterId {

@ -6,11 +6,12 @@ using System.Xml.Linq;
using FLocal.Core; using FLocal.Core;
using FLocal.Core.DB; using FLocal.Core.DB;
using FLocal.Core.DB.conditions; using FLocal.Core.DB.conditions;
using FLocal.Common.actions;
namespace FLocal.Common.dataobjects { namespace FLocal.Common.dataobjects {
public class Thread : SqlObject<Thread> { public class Thread : SqlObject<Thread> {
public class TableSpec : FLocal.Core.DB.ITableSpec { public class TableSpec : ISqlObjectTableSpec {
public const string TABLE = "Threads"; public const string TABLE = "Threads";
public const string FIELD_ID = "Id"; public const string FIELD_ID = "Id";
public const string FIELD_BOARDID = "BoardId"; public const string FIELD_BOARDID = "BoardId";
@ -26,9 +27,10 @@ namespace FLocal.Common.dataobjects {
public static readonly TableSpec instance = new TableSpec(); public static readonly TableSpec instance = new TableSpec();
public string name { get { return TABLE; } } public string name { get { return TABLE; } }
public string idName { get { return FIELD_ID; } } public string idName { get { return FIELD_ID; } }
public void refreshSqlObject(int id) { Refresh(id); }
} }
protected override FLocal.Core.DB.ITableSpec table { get { return TableSpec.instance; } } protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } }
private int _boardId; private int _boardId;
public int boardId { public int boardId {
@ -193,5 +195,25 @@ namespace FLocal.Common.dataobjects {
); );
} }
public void incrementViewsCounter() {
using(ChangeSet changeSet = new ChangeSet()) {
changeSet.Add(new UpdateChange(
TableSpec.instance,
new Dictionary<string,AbstractFieldValue>() {
{
TableSpec.FIELD_TOTALVIEWS,
new IncrementFieldValue()
}
},
this.id
));
using(Transaction transaction = Config.instance.mainConnection.beginTransaction()) {
changeSet.Apply(transaction);
transaction.Commit();
}
}
}
} }
} }

@ -9,7 +9,7 @@ using FLocal.Core.DB;
namespace FLocal.Common.dataobjects { namespace FLocal.Common.dataobjects {
public class User : SqlObject<User> { public class User : SqlObject<User> {
public class TableSpec : FLocal.Core.DB.ITableSpec { public class TableSpec : ISqlObjectTableSpec {
public const string TABLE = "Users"; public const string TABLE = "Users";
public const string FIELD_ID = "Id"; public const string FIELD_ID = "Id";
public const string FIELD_REGDATE = "RegDate"; public const string FIELD_REGDATE = "RegDate";
@ -23,9 +23,10 @@ namespace FLocal.Common.dataobjects {
public static readonly TableSpec instance = new TableSpec(); public static readonly TableSpec instance = new TableSpec();
public string name { get { return TABLE; } } public string name { get { return TABLE; } }
public string idName { get { return FIELD_ID; } } public string idName { get { return FIELD_ID; } }
public void refreshSqlObject(int id) { Refresh(id); }
} }
protected override FLocal.Core.DB.ITableSpec table { get { return TableSpec.instance; } } protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } }
private DateTime _regDate; private DateTime _regDate;
public DateTime regDate { public DateTime regDate {

@ -19,6 +19,7 @@ namespace FLocal.IISHandler.handlers {
override protected XElement[] getSpecificData(WebContext context) { override protected XElement[] getSpecificData(WebContext context) {
Post post = Post.LoadById(int.Parse(context.requestParts[1])); Post post = Post.LoadById(int.Parse(context.requestParts[1]));
post.thread.incrementViewsCounter();
return new XElement[] { return new XElement[] {
new XElement("currentLocation", post.exportToXmlSimpleWithParent(context)), new XElement("currentLocation", post.exportToXmlSimpleWithParent(context)),
new XElement("posts", post.exportToXmlWithoutThread(context, true)) new XElement("posts", post.exportToXmlWithoutThread(context, true))

@ -51,6 +51,7 @@ namespace FLocal.IISHandler.handlers {
} }
); );
IEnumerable<Post> posts = thread.getPosts(pageOuter, context); IEnumerable<Post> posts = thread.getPosts(pageOuter, context);
thread.incrementViewsCounter();
return new XElement[] { return new XElement[] {
new XElement("currentLocation", thread.exportToXmlSimpleWithParent(context)), new XElement("currentLocation", thread.exportToXmlSimpleWithParent(context)),
new XElement("posts", new XElement("posts",

@ -41,7 +41,7 @@ namespace FLocal.MySQLConnector {
case ComparisonType.NOTEQUAL: case ComparisonType.NOTEQUAL:
return left + " != " + right; return left + " != " + right;
default: default:
throw new NotImplementedException(); throw new NotSupportedException();
} }
} }
@ -76,7 +76,7 @@ namespace FLocal.MySQLConnector {
} else if(condition is MultiValueCondition) { } else if(condition is MultiValueCondition) {
return CompileCondition((MultiValueCondition)condition); return CompileCondition((MultiValueCondition)condition);
} else { } else {
throw new NotImplementedException(); throw new NotSupportedException();
} }
} }
@ -92,7 +92,7 @@ namespace FLocal.MySQLConnector {
case ConditionsJoinType.OR: case ConditionsJoinType.OR:
return string.Join(" OR ", parts.ToArray()); return string.Join(" OR ", parts.ToArray());
default: default:
throw new NotImplementedException(); throw new NotSupportedException();
} }
} }
@ -102,7 +102,7 @@ namespace FLocal.MySQLConnector {
} else if(condition is SimpleCondition) { } else if(condition is SimpleCondition) {
return CompileCondition((SimpleCondition)condition); return CompileCondition((SimpleCondition)condition);
} else { } else {
throw new NotImplementedException(); throw new NotSupportedException();
} }
} }
@ -116,7 +116,7 @@ namespace FLocal.MySQLConnector {
} else if(condition is EmptyCondition) { } else if(condition is EmptyCondition) {
return CompileCondition((EmptyCondition)condition); return CompileCondition((EmptyCondition)condition);
} else { } else {
throw new NotImplementedException(); throw new NotSupportedException();
} }
} }

@ -194,7 +194,7 @@ namespace FLocal.MySQLConnector {
using(DbCommand command = transaction.sqlconnection.CreateCommand()) { using(DbCommand command = transaction.sqlconnection.CreateCommand()) {
command.Transaction = transaction.sqltransaction; command.Transaction = transaction.sqltransaction;
command.CommandType = System.Data.CommandType.Text; command.CommandType = System.Data.CommandType.Text;
command.CommandText = "LOCK TABLES " + table.compile(this.traits); command.CommandText = "LOCK TABLE " + table.compile(this.traits);
command.ExecuteNonQuery(); command.ExecuteNonQuery();
} }
} }
@ -240,6 +240,7 @@ namespace FLocal.MySQLConnector {
foreach(KeyValuePair<string, string> kvp in paramsholder.data) { foreach(KeyValuePair<string, string> kvp in paramsholder.data) {
command.AddParameter(kvp.Key, kvp.Value); command.AddParameter(kvp.Key, kvp.Value);
} }
// throw new CriticalException(command.CommandText + "; parameters: " + string.Join(", ", (from DbParameter parameter in command.Parameters select parameter.ParameterName + "='" + parameter.Value.ToString() + "'").ToArray()));
command.ExecuteNonQuery(); command.ExecuteNonQuery();
} }
} }

Loading…
Cancel
Save