30

変更を行った実際のユーザーとともに、すべてのデータ変更をロギング テーブルに記録する必要があります。アプリケーションは 1 人の SQL ユーザーを使用してデータベースにアクセスしていますが、「実際の」ユーザー ID をログに記録する必要があります。

テーブルの挿入と更新ごとにトリガーを記述し、context_info を使用してユーザー ID を保存することで、t-sql でこれを行うことができます。ユーザー ID をストアド プロシージャに渡し、ユーザー ID を contextinfo に格納すると、トリガーはこの情報を使用してログ行をログ テーブルに書き込むことができます。

EFを使用して同様のことをどこで、またはどのように行うことができるか、場所または方法が見つかりません。したがって、主な目標は次のとおりです。EFを介してデータを変更した場合、正確なデータ変更を半自動でテーブルに記録したいと思います(そのため、変更前にすべてのフィールドをチェックしたくありませんオブジェクトを保存します)。EntitySQL を使用しています。

残念ながら、SQL 2000 に固執する必要があるため、SQL2008 で導入されたデータ変更キャプチャはオプションではありません (しかし、それも適切な方法ではない可能性があります)。

アイデア、リンク、出発点はありますか?

[編集] いくつかのメモ: ObjectContext.SavingChanges イベントハンドラーを使用することで、SQL ステートメントを挿入して contextinfo を初期化できるポイントを取得できます。ただし、EF と標準 SQL を混在させることはできません。したがって、EntityConnection を取得できますが、それを使用して T-SQL ステートメントを実行することはできません。または、EntityConnection の接続文字列を取得し、それに基づいて SqlConnection を作成することもできますが、それは別の接続になるため、contextinfo は EF による保存に影響しません。

SavingChanges ハンドラーで次のことを試しました。

testEntities te = (testEntities)sender;
DbConnection dc = te.Connection;
DbCommand dcc = dc.CreateCommand();
dcc.CommandType = CommandType.StoredProcedure;
DbParameter dp = new EntityParameter();
dp.ParameterName = "userid";
dp.Value = textBox1.Text;
dcc.CommandText = "userinit";
dcc.Parameters.Add(dp);
dcc.ExecuteNonQuery();

エラー: EntityCommand.CommandText の値は StoredProcedure コマンドに対して有効ではありません。EntityParameter の代わりに SqlParameter を使用した場合も同様です。SqlParameter は使用できません。

StringBuilder cStr = new StringBuilder("declare @tx char(50); set @tx='");
cStr.Append(textBox1.Text);
cStr.Append("'; declare @m binary(128); set @m = cast(@tx as binary(128)); set context_info @m;");

testEntities te = (testEntities)sender;
DbConnection dc = te.Connection;
DbCommand dcc = dc.CreateCommand();
dcc.CommandType = CommandType.Text;
dcc.CommandText = cStr.ToString();
dcc.ExecuteNonQuery();

エラー: クエリ構文が無効です。

ここで、Entity Framework と ADO.NET の間のブリッジを作成することに行き詰まっています。動作させることができれば、概念実証を投稿します。

4

8 に答える 8

13

コンテキストの処理はどうですか。SavingChanges

于 2008-11-17T15:28:08.857 に答える
13

私を正しい方向に向けてくれてありがとう。ただし、私の場合は、select ステートメントを実行するときにコンテキスト情報も設定する必要があります。これは、コンテキスト情報を使用してユーザーによる行レベルのセキュリティを制御するビューを照会しているためです。

接続の StateChanged イベントにアタッチして、非オープンからオープンへの変化を監視するのが最も簡単であることがわかりました。次に、コンテキストを設定するプロシージャを呼び出すと、EF が接続をリセットすることを決定した場合でも、毎回動作します。

private int _contextUserId;

public void SomeMethod()
{
    var db = new MyEntities();
    db.Connection.StateChange += this.Connection_StateChange;
    this._contextUserId = theCurrentUserId;

    // whatever else you want to do
}

private void Connection_StateChange(object sender, StateChangeEventArgs e)
{
    // only do this when we first open the connection
    if (e.OriginalState == ConnectionState.Open ||
        e.CurrentState != ConnectionState.Open)
        return;

    // use the existing open connection to set the context info
    var connection = ((EntityConnection) sender).StoreConnection;
    var command = connection.CreateCommand();
    command.CommandText = "proc_ContextInfoSet";
    command.CommandType = CommandType.StoredProcedure;
    command.Parameters.Add(new SqlParameter("ContextUserID", this._contextUserId));
    command.ExecuteNonQuery();
}
于 2011-02-25T20:42:10.437 に答える
10

最後に Craig の助けを借りて、ここに概念実証を示します。さらにテストが必要ですが、一見すると機能しています。

最初に、2 つのテーブルを作成しました。1 つはデータ用で、もう 1 つはロギング用です。

-- This is for the data
create table datastuff (
    id int not null identity(1, 1),
    userid nvarchar(64) not null default(''),
    primary key(id)
)
go

-- This is for the log
create table naplo (
    id int not null identity(1, 1),
    userid nvarchar(64) not null default(''),
    datum datetime not null default('2099-12-31'),
    primary key(id)
)
go

2 番目: 挿入のトリガーを作成します。

create trigger myTrigger on datastuff for insert as

    declare @User_id int,
        @User_context varbinary(128),
        @User_id_temp varchar(64)

    select @User_context = context_info
        from master.dbo.sysprocesses
        where spid=@@spid

    set @User_id_temp = cast(@User_context as varchar(64))

    declare @insuserid nvarchar(64)

    select @insuserid=userid from inserted

    insert into naplo(userid, datum)
        values(@User_id_temp, getdate())

go

更新のトリガーも作成する必要があります。これは、変更されたコンテンツのすべてのフィールドをチェックする必要があるため、もう少し洗練されたものになります。

ログテーブルとトリガーは、作成/変更されたテーブルとフィールドを保存するように拡張する必要がありますが、アイデアが得られたことを願っています.

3 番目: SQL コンテキスト情報にユーザー ID を入力するストアド プロシージャを作成します。

create procedure userinit(@userid varchar(64))
as
begin
    declare @m binary(128)
    set @m = cast(@userid as binary(128))
    set context_info @m
end
go

SQL 側の準備が整いました。ここに C# の部分があります。

プロジェクトを作成し、EDM をプロジェクトに追加します。EDM には、datastuff テーブル (または変更を監視する必要があるテーブル) と SP が含まれている必要があります。

ここで、エンティティ オブジェクトを使用して何かを行い (たとえば、新しい datastuff オブジェクトを追加する)、SavingChanges イベントにフックします。

using (testEntities te = new testEntities())
{
    // Hook to the event
    te.SavingChanges += new EventHandler(te_SavingChanges);

    // This is important, because the context info is set inside a connection
    te.Connection.Open();

    // Add a new datastuff
    datastuff ds = new datastuff();

    // This is coming from a text box of my test form
    ds.userid = textBox1.Text;
    te.AddTodatastuff(ds);

    // Save the changes
    te.SaveChanges(true);

    // This is not needed, only to make sure
    te.Connection.Close();
}

SavingChanges 内にコードを挿入して、接続のコンテキスト情報を設定します。

// Take my entity
testEntities te = (testEntities)sender;

// Get it's connection
EntityConnection dc = (EntityConnection )te.Connection;

// This is important!
DbConnection storeConnection = dc.StoreConnection;

// Create our command, which will call the userinit SP
DbCommand command = storeConnection.CreateCommand();
command.CommandText = "userinit";
command.CommandType = CommandType.StoredProcedure;

// Put the user id as the parameter
command.Parameters.Add(new SqlParameter("userid", textBox1.Text));

// Execute the command
command.ExecuteNonQuery();

したがって、変更を保存する前に、オブジェクトの接続を開き、コードを挿入して (この部分で接続を閉じないでください!)、変更を保存します。

そして、忘れないでください!これはロギングのニーズに合わせて拡張する必要があり、十分にテストする必要があります。これは可能性しか示していないからです!

于 2008-11-20T09:03:22.813 に答える
3

ストアド プロシージャをエンティティ モデルに追加しようとしましたか?

于 2008-11-19T20:47:38.570 に答える
2

この問題は別の方法で解決していました。

  • 生成されたエンティティ コンテナー クラスからクラスを継承する
  • 基本エンティティ クラスを抽象化します。別のファイルで部分的なクラス定義によってそれを行うことができます
  • 継承されたクラスで、メソッド定義で new キーワードを使用して、独自の SavingChanges メソッドを非表示にします
  • SavingChanges メソッドで:

    1. a、エンティティ接続を開く
    2. ebtityclient でユーザー コンテキスト ストアド プロシージャを実行する
    3. base.SaveChanges() を呼び出します
    4. エンティティ接続を閉じる

コードでは、継承されたクラスを使用する必要があります。

于 2009-10-08T19:55:31.307 に答える
2

DbContext または ObjectContext を使用して、SET CONTEXT_INFO の実行を強制するだけです。

...
FileMoverContext context = new FileMoverContext();
context.SetSessionContextInfo(Environment.UserName);
...
context.SaveChanges();

FileMoverContext は DbContext を継承し、SetSessionContextInfo メソッドを持ちます。私の SetSessionContextInfo(...) は次のようになります。

public bool SetSessionContextInfo(string infoValue)
{
   try
   {
      if (infoValue == null)
         throw new ArgumentNullException("infoValue");

      string rawQuery =
                   @"DECLARE @temp varbinary(128)
                     SET @temp = CONVERT(varbinary(128), '";

      rawQuery = rawQuery + infoValue + @"');
                    SET CONTEXT_INFO @temp";
      this.Database.ExecuteSqlCommand(rawQuery);

      return true;
   }
   catch (Exception e)
   {
      return false;
   }
}

ここで、CONTEXT_INFO() にアクセスできるデータベース トリガーを設定し、それを使用してデータベース フィールドを設定します。

于 2014-08-06T21:44:01.057 に答える
0

次の手順で解決した、やや似たシナリオがありました。

  1. 最初に、次のようなすべての CRUD 操作用の汎用リポジトリを作成します。これは常に適切なアプローチです。public class GenericRepository : IGenericRepository T : クラス

  2. "public virtual void Update(T entityToUpdate)" のようなアクションを記述します。

  3. ロギング/監査が必要な場所。"LogEntity(entityToUpdate, "U");" のようにユーザー定義関数を呼び出すだけです。
  4. 以下の貼り付けたファイル/クラスを参照して、「LogEntity」関数を定義します。この関数では、更新と削除の場合、主キーを介して古いエンティティを取得し、監査テーブルに挿入します。主キーを識別してその値を取得するために、リフレクションを使用しました。

以下の完全なクラスのリファレンスを見つけてください。

 public class GenericRepository<T> : IGenericRepository<T> where T : class
{
    internal SampleDBContext Context;
    internal DbSet<T> DbSet;

    /// <summary>
    /// Constructor to initialize type collection
    /// </summary>
    /// <param name="context"></param>
    public GenericRepository(SampleDBContext context)
    {
        Context = context;
        DbSet = context.Set<T>();
    }

    /// <summary>
    /// Get query on current entity
    /// </summary>
    /// <returns></returns>
    public virtual IQueryable<T> GetQuery()
    {
        return DbSet;
    }

    /// <summary>
    /// Performs read operation on database using db entity
    /// </summary>
    /// <param name="filter"></param>
    /// <param name="orderBy"></param>
    /// <param name="includeProperties"></param>
    /// <returns></returns>
    public virtual IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>,
                                            IOrderedQueryable<T>> orderBy = null, string includeProperties = "")
    {
        IQueryable<T> query = DbSet;

        if (filter != null)
        {
            query = query.Where(filter);
        }

        query = includeProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, includeProperty) => current.Include(includeProperty));

        if (orderBy == null)
            return query.ToList();
        else
            return orderBy(query).ToList();
    }

    /// <summary>
    /// Performs read by id operation on database using db entity
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public virtual T GetById(object id)
    {
        return DbSet.Find(id);
    }

    /// <summary>
    /// Performs add operation on database using db entity
    /// </summary>
    /// <param name="entity"></param>
    public virtual void Insert(T entity)
    {
        //if (!entity.GetType().Name.Contains("AuditLog"))
        //{
        //    LogEntity(entity, "I");
        //}
        DbSet.Add(entity);
    }

    /// <summary>
    /// Performs delete by id operation on database using db entity
    /// </summary>
    /// <param name="id"></param>
    public virtual void Delete(object id)
    {
        T entityToDelete = DbSet.Find(id);
        Delete(entityToDelete);
    }

    /// <summary>
    /// Performs delete operation on database using db entity
    /// </summary>
    /// <param name="entityToDelete"></param>
    public virtual void Delete(T entityToDelete)
    {
        if (!entityToDelete.GetType().Name.Contains("AuditLog"))
        {
            LogEntity(entityToDelete, "D");
        }

        if (Context.Entry(entityToDelete).State == EntityState.Detached)
        {
            DbSet.Attach(entityToDelete);
        }
        DbSet.Remove(entityToDelete);
    }

    /// <summary>
    /// Performs update operation on database using db entity
    /// </summary>
    /// <param name="entityToUpdate"></param>
    public virtual void Update(T entityToUpdate)
    {
        if (!entityToUpdate.GetType().Name.Contains("AuditLog"))
        {
            LogEntity(entityToUpdate, "U");
        }
        DbSet.Attach(entityToUpdate);
        Context.Entry(entityToUpdate).State = EntityState.Modified;
    }

    public void LogEntity(T entity, string action = "")
    {
        try
        {
            //*********Populate the audit log entity.**********
            var auditLog = new AuditLog();
            auditLog.TableName = entity.GetType().Name;
            auditLog.Actions = action;
            auditLog.NewData = Newtonsoft.Json.JsonConvert.SerializeObject(entity);
            auditLog.UpdateDate = DateTime.Now;
            foreach (var property in entity.GetType().GetProperties())
            {
                foreach (var attribute in property.GetCustomAttributes(false))
                {
                    if (attribute.GetType().Name == "KeyAttribute")
                    {
                        auditLog.TableIdValue = Convert.ToInt32(property.GetValue(entity));

                        var entityRepositry = new GenericRepository<T>(Context);
                        var tempOldData = entityRepositry.GetById(auditLog.TableIdValue);
                        auditLog.OldData = tempOldData != null ? Newtonsoft.Json.JsonConvert.SerializeObject(tempOldData) : null;
                    }

                    if (attribute.GetType().Name == "CustomTrackAttribute")
                    {
                        if (property.Name == "BaseLicensingUserId")
                        {
                            auditLog.UserId = ValueConversion.ConvertValue(property.GetValue(entity).ToString(), 0);
                        }
                    }
                }
            }

            //********Save the log in db.*********
            new UnitOfWork(Context, null, false).AuditLogRepository.Insert(auditLog);
        }
        catch (Exception ex)
        {
            Logger.LogError(string.Format("Error occured in [{0}] method of [{1}]", Logger.GetCurrentMethod(), this.GetType().Name), ex);
        }
    }
}

CREATE TABLE [dbo].[AuditLog](
[AuditId] [BIGINT] IDENTITY(1,1) NOT NULL,
[TableName] [nvarchar](250) NULL,
[UserId] [int] NULL,
[Actions] [nvarchar](1) NULL,
[OldData] [text] NULL,
[NewData] [text] NULL,
[TableIdValue] [BIGINT] NULL,
[UpdateDate] [datetime] NULL,
 CONSTRAINT [PK_DBAudit] PRIMARY KEY CLUSTERED 
(
[AuditId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = 
OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
于 2015-09-09T10:17:40.870 に答える