SQLCE - Upsert(更新或插入) - 如何使用常用方法准备一行?

Posted

技术标签:

【中文标题】SQLCE - Upsert(更新或插入) - 如何使用常用方法准备一行?【英文标题】:SQLCE - Upsert (Update or Insert) - How to prepare a row using common method? 【发布时间】:2011-02-16 12:12:30 【问题描述】:

以下是伪代码:

SqlCeResultSet myResultSet = cmd.ExecuteResultSet(Options...etc);
bool found = myResultSet.Seek();
if found 
    //do an Update
    myResultSet.Read() //make current

    //At this point we have a cursor positioned at a row to be edited
    myResultSet.SetString(1, "value for col 1");
    myResultSet.SetString(2, "value for col 2");
    //... etc... 
    myResultSet.SetString(100, "value for col 100");
    //i want to replace above with: CommonMethodToFillRowData(someRow)

    //finally update
    myResultSet.Update();

 else 
    //do an insert
    SqlCeUpdatableRecord myRec = myResultSet.CreateRecord();
    //set primaryKey
    myRec.SetInt32(0, pkValue);

    //At this point we have a cursor positioned at a row to be edited   
    myRec.SetString(1, "value for col 1");
    myRec.SetString(2, "value for col 2");
    //... etc...
    myRec.SetString(100, "value for col 100");
    //i want to replace above with: CommonMethodToFillRowData(someRow)

    //finally insert
    myResultSet.Insert(myRec);

从上面看,如果我有 100 列要准备,则必须重复两次;我想要的是一些 CommonMethodToFillRowData();但是对于这种方法,我使用什么类型的参数?

CommonMethodToFillRowData(SqlCeResultSet or SqlCeUpdatableRecord ? parmRow) 
    parmRow.SetInt32(col1, value1)
    parmRow.SetString(col2, value2)
    ...etc.
    parmRow.SetString(100, "value for col 100");

在 SqlCeUpdatableRecord 类上直接引用 MSDN doco: --> 表示来自数据源的一行可更新值。 SqlCeResultSet 对象包含一个或多个 UpdatableRecords。

如果是这样,为什么我不能直接访问 SqlCeResultSet 中的单个 UpdatableRecord,一旦我通过 Seek() 定位光标?

如果可能的话,那将使我能够使用:

CommonMethodToFillRowData(SqlCeUpdatableRecord parmRow) 
   //end of story

【问题讨论】:

这个 JoeDotNot 你是怎么相处的??? @Sébastien - 请查看我的“最后一分钟”回答。 【参考方案1】:

创建一个可以表示SqlCeResultSetSqlCeUpdatableRecord 的包装器对象(视情况而定)。然后编写一次“保存”代码并将其应用为更新或插入,具体取决于是否发现记录已存在。

警告:这是未经测试的代码。

public void SavingMyData()

    SqlCeResultSet resultSet = cmd.ExecuteResultSet(Options...etc);
    SqlCeWrapper wrapper = new SqlCeWrapper(resultSet);

    wrapper.SetInt32(0, pkValue, true); // Primary Key = true
    wrapper.SetString(1, "value for col 1");
    wrapper.SetString(2, "value for col 2");
    wrapper.SetString(100, "value for col 100");
    wrapper.Commit();


...

public class SqlCeWrapper

    private readonly bool _found;
    private readonly SqlCeResultSet _resultSet;
    private readonly SqlCeUpdatableRecord _newRecord;

    public SqlCeWrapper(SqlCeResultSet resultSet)
    
        _resultSet = resultSet;

        _found = resultSet.Seek();
        if (_found)
            resultSet.Read();
        else
            _newRecord = resultSet.CreateRecord();
    

    public void SetInt32(int ordinal, int value, bool isPrimary = false)
    
        if (_found && !isPrimary)
            _resultSet.SetInt32(ordinal, value);
        else if (!_found)
            _newRecord.SetInt32(ordinal, value);
    

    public void SetString(int ordinal, string value, bool isPrimary = false)
    
        if (_found && !isPrimary)
             _resultSet.SetString(ordinal, value);
        else if (!_found)
            _newRecord.SetString(ordinal, value);
    

    public void Commit()
    
        if (_found)
            _resultSet.Update();
        else
            _resultSet.Insert(_newRecord);
    

注意:如果您不使用 .NET 4,则必须删除可选参数。您还可以根据需要在SqlCeWrapper 中添加额外的SetX() 方法。

【讨论】:

谢谢@Greg。您的答案与@Calvin 的答案相同。它可以完成这项工作,只需要编写一次,并且在使用时非常优雅。我不确定性能,但在这种情况下可能根本不是问题。在你的答案和@WarrenG 之间做出艰难的选择,但我选择了@WarrenG,他真的以最简单的方式回答了这个问题。无论如何,谢谢! @Greg,感谢您发布这个优雅的解决方案!我很久以前就放弃了它无法完成并将我的代码作为两个分支,但我终于有时间查看您的答案并且它可以 100% 工作。给你正确答案!【参考方案2】:

怎么样:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlServerCe;
using System.Collections;
using System.Data;

namespace SqlCeRecord_Test

    class Program
    
        static void Main(string[] args)
        
            // Arguments for update
            int lookFor = 1;
            string value = "AC/DC";

            // Arguments for insert
            lookFor = Int16.MaxValue;
            value = "joedotnet";

            using (SqlCeConnection conn = new SqlCeConnection(@"Data Source=C:\Users\xeej\Downloads\ChinookPart2\Chinook.sdf"))
            
                conn.Open();

                using (SqlCeCommand cmd = new SqlCeCommand("Artist"))
                
                    SqlCeUpdatableRecord myRec = null;
                    cmd.Connection = conn;
                    cmd.CommandType = System.Data.CommandType.TableDirect;
                    cmd.IndexName = "PK_Artist";
                    SqlCeResultSet myResultSet = cmd.ExecuteResultSet(ResultSetOptions.Updatable | ResultSetOptions.Scrollable);
                    bool found = myResultSet.Seek(DbSeekOptions.FirstEqual, new object[]  lookFor );

                    if (found)
                    
                        myResultSet.Read();
                    
                    else
                    
                        myRec = myResultSet.CreateRecord();
                    
                    foreach (KeyValuePair<int, object> item in CommonMethodToFillRowData(value))
                    
                        if (found)
                        
                            myResultSet.SetValue(item.Key, item.Value);
                        
                        else
                        
                            myRec.SetValue(item.Key, item.Value);
                        
                    
                    if (found)
                    
                        myResultSet.Update();
                    
                    else
                    
                        myResultSet.Insert(myRec);
                    
                
            
        

        private static Dictionary<int, object> CommonMethodToFillRowData(string value1) //TODO add more values
        
            var dict = new Dictionary<int, object>();
            dict.Add(1, value1);
            return dict;
         
    

【讨论】:

不完全是我的想法:) 虽然我同意@WarrenG 对此的回答,但我要感谢您花在这个赏金上的时间。【参考方案3】:

如何在 SqlCeResultSet 上使用枚举器而不是像这样的 Read 方法

IEnumerator enumerator = myResultSet.GetEnumerator();
bool found = enumerator.MoveNext();
SqlCeUpdatableRecord record;
if (found) 
    record = (SqlCeUpdatableRecord)enumerator.Current;
    MethodToFill(record);
    myResultSet.Update();
 else 
    record = myResultSet.CreateRecord();
    MethodToFill(record);
    myResultSet.Insert(record);


private void MethodToFill(SqlCeUpdatableRecord recordToFill) 
    recordToFill.SetString(0, "Hello");
    recordToFill.SetString(1, "World");
    // etc

【讨论】:

这正是我一直在寻找的答案(我认为@JoeDotNot 也在寻找答案)。谢谢@WarrenG! 对不起,当赏金即将结束时,我处于 ​​AFK 状态,这似乎起到了作用。唉,经过测试,这似乎在现实生活中不起作用。我把赏金给了错误的人......如果我能改变它,我会选择@Greg的答案...... 我终于开始审查答案了,因为我很久以前就放弃了它无法完成,并将我的代码保留为两个分支。 @WarrenG:感谢您的努力,您的答案看起来很简单,直到我在 MSDN 上遇到这个问题“枚举器可用于读取集合中的数据,但它们不能用于修改基础集合。” @Sebastien 你太快扣动扳机了,哈哈,我不赞成这个答案以对抗一些(错误授予的)赏金积分:-) @joedot 不对,我没有注意枚举器的限制。要放弃另一种可能的解决方案,如果您使用的是 C# 4,则可以将填充方法的签名更改为 MehodToFill(dynamic objectToFill),然后使用上面的原始代码,您可以传递 SqlCeResultSet 或 SqlCeUpdateableRecord 并且将被填充适当的。【参考方案4】:

我不确定您要做什么,也不确定具体的这些组件,因此可能有一种更有效的方法。但是根据MSDN,SqlCeResultSet是一个SqlCeDataReader,也就是一个DbDataReader,它实现了IDataRecord...而SqlCeUpdateableRecord也实现了IDataRecord。

那么,这行得通吗?

public static void DoStuff()

    SqlCeCommand cmd = new SqlCeCommand();
    SqlCeResultSet myResultSet = cmd.ExecuteResultSet(ResultSetOptions.None);
    var reader = myResultSet.Read();

    bool found = myResultSet.Seek(DbSeekOptions.After);
    if (found)
    
        myResultSet.Read();
        CommonMethodToFillRowData(myResultSet);
        myResultSet.Update();
    
    else 
    
        SqlCeUpdatableRecord myRec = myResultSet.CreateRecord();
        CommonMethodToFillRowData(myRec);
        myResultSet.Insert(myRec);
    


// All the messy Type-wrangling is hidden behind the scenes
public static void CommonMethodToFillRowData(this IDataRecord RowToFill)

    RowToFill.SetInt32(1, 42);
    RowToFill.SetString(2, "Foo");
    // etc...


// Since SetInt32 seems to do the same thing in either inherited Type
public static void SetInt32(this IDataRecord RowToFill, int Ordinal, int Value)

    Type rowType = RowToFill.GetType();
    if (rowType == typeof(SqlCeResultSet))
        ((SqlCeResultSet)RowToFill).SetInt32(Ordinal, Value);
    else if (rowType == typeof(SqlCeUpdatableRecord))
        ((SqlCeUpdatableRecord)RowToFill).SetInt32(Ordinal, Value);
    else
        throw new ArgumentException("Method does not know what to do with Type " + rowType.ToString());


// Since SetString seems to do the same thing in either inherited Type
public static void SetString(this IDataRecord RowToFill, int Ordinal, string Value)

    Type rowType = RowToFill.GetType();
    if (rowType == typeof(SqlCeResultSet))
        ((SqlCeResultSet)RowToFill).SetString(Ordinal, Value);
    else if (rowType == typeof(SqlCeUpdatableRecord))
        ((SqlCeUpdatableRecord)RowToFill).SetString(Ordinal, Value);
    else
        throw new ArgumentException("Method does not know what to do with Type " + rowType.ToString());

这很糟糕,性能也不会很好,但如果您只想将行填充到一段代码中,它可能会满足您的需求。

【讨论】:

感谢您的回答@Calvin。 @Greg 提供了一个基于您的更优雅的解决方案,但最后我选择了@WarrenG 的答案,这是我认为最简单的答案。谢谢@Calvin!

以上是关于SQLCE - Upsert(更新或插入) - 如何使用常用方法准备一行?的主要内容,如果未能解决你的问题,请参考以下文章

SQLite UPSERT / 更新或插入

如何在SQL Server 2005中进行upsert(更新或插入)

mysql实现upsert

laravel:更新或创建(更新插入)数据透视表

在 Doctrine 2 中查找或创建(更新插入)功能

SQLite 插入 - 重复键更新 (UPSERT)