none
Linq Update After Postback (No ViewState!) RRS feed

  • Question

  •  

    I have to say that after my initial enthusiasm for linq, my patience is wearing really thin. How have you guys made simple stuff SO difficult?

     

    Right, all I want to do is Attach a ListView to a linq datasource, turn OFF viewstate (as it grows to 4K on my noddy examples) and then use edit and update.

     

    So how do you do it? There are no examples that I can find, and what I have puzzled out so far:

     

    CREATE TABLE [dbo].[InvalidWords](

    [ID] [bigint] IDENTITY(1,1) NOT NULL,

    [Value] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,

    [Checked] [bit] NOT NULL CONSTRAINT [DF_InvalidWords_Checked] DEFAULT ((0)),

    [CategoryID] [bigint] NULL,

    [timestamp] [timestamp] NOT NULL,

    CONSTRAINT [PK_InvalidWords] PRIMARY KEY CLUSTERED

    (

    [ID] ASC

    )WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]

    ) ON [PRIMARY]

     

    ------------------------------------

    DecoderDataContext context = new DecoderDataContext();

     

    protected void LinqDataSource1_ContextCreating(object sender, LinqDataSourceContextEventArgs e)

    {

    e.ObjectInstance = context;

    }

     

    protected void LinqDataSource1_Updating(object sender, LinqDataSourceUpdateEventArgs e)

    {

    InvalidWord newInvalidWord = e.NewObject as InvalidWord;

    InvalidWord origionalInvalidWord = e.OriginalObject as InvalidWord;

    newInvalidWord.InvalidWordCategory = default(InvalidWordCategory);

    context.InvalidWords.Attach(newInvalidWord,true);

    context.SubmitChanges();

    e.Cancel = true;

    }

     

    Throws the same old exception: Cannot add an entity with a key that is already in use.

     

    yes I've also tried: context.InvalidWords.Attach(newInvalidWord,origionalInvalidWord );

    as well, and nothing WORKS!!

     

    Can we PLEASE have a real work example built by a MVP who understands that bandwidth isn't infinite that disables the viewstate and actually demonstrates how to do this! All I want to do is update a damn ROW! This should not be hard. I have the old entity version, the new entity version and the damn current DB context. What do I have to give? BLOOD?

     

    I'm seriouly thinking that DataReaders are a hell of a lot easier at this point. Please prove me wrong!

     

     

    Thanks

    A tired and pissed-off, Ben

    Friday, April 18, 2008 8:17 PM

All replies

  • Okay, continuing my long tradition of answering my own posts, I've made some progress. This:

     

    protected void LinqDataSource1_Updating(object sender, LinqDataSourceUpdateEventArgs e)

    {

    using (DecoderDataContext context = new DecoderDataContext())

    {

    InvalidWord newInvalidWord = e.NewObject as InvalidWord;

    InvalidWord origionalInvalidWord = e.OriginalObject as InvalidWord;

    newInvalidWord.InvalidWordCategory = default(InvalidWordCategory);

    context.InvalidWords.Attach(newInvalidWord, origionalInvalidWord);

    context.SubmitChanges();

    }

     

    e.Cancel = true;

    }

     

    Saves the right data to the database, but because the update operation is canceled (if it continues, the "Cannot add an entity with a key that is already in use." exception is thrown), the UI reverts to to old data in the non-editing mode. If I then go into editing mode again, the correct data is shown. If I press cancel then the main UI is showing the right data.

     

    This is better than the exception, but it's hardly "Right". Can I have a little help here?

     

    Ben

    Saturday, April 19, 2008 3:46 PM
  • Poking around with reflector, I find that:

     

     

    protected override int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues)
    {
    ....

    So we can see where the On updating is called, and can also see that if I return true in the cancel member, it exits with -1. This I assume is what makes the UI revert without updating the data - fair enough. But this also means that the UpdateDataObject is the thing that causes the error. Looking inside that we find:

     

    protected virtual void UpdateDataObject(object dataContext, object table, object oldDataObject, object newDataObject)
    {
        this._linqToSql.Attach((ITable) table, oldDataObject);
        Dictionary<string, Exception> innerExceptions = this.SetDataObjectProperties(oldDataObject, newDataObject);
        if (innerExceptions != null)
        {
            throw new LinqDataSourceValidationException(string.Format(CultureInfo.InvariantCulture, AtlasWeb.LinqDataSourceView_ValidationFailed, new object[] { oldDataObject.GetType() }), innerExceptions);
        }
        this._linqToSql.SubmitChanges((DataContext) dataContext);
    }
    

    So the update dataobject starts by attatching the old object, then works out what changes it would need to make to get to the new object. But of course it can't get that far because the old one would already be present ... But how would it know at this point? I assume it has yet to hit the database, so as far as it knows this is just a new row when you call attach. My call works because I explicitly tell it, here is my old object, here is my new one, it's obviously an update, get on with it. My call ends up here:

     

    public void Attach(TEntity entity, TEntity original)
    {
        if (entity == null)
        {
            throw Error.ArgumentNull("entity");
        }
        if (original == null)
        {
            throw Error.ArgumentNull("original");
        }
        if (entity.GetType() != original.GetType())
        {
            throw Error.OriginalEntityIsWrongType();
        }
        this.CheckReadOnly();
        this.context.CheckNotInSubmitChanges();
        this.context.VerifyTrackingEnabled();
        MetaType inheritanceType = this.metaTable.RowType.GetInheritanceType(entity.GetType());
        if (!Table<TEntity>.IsTrackableType(inheritanceType))
        {
            throw Error.TypeCouldNotBeTracked(inheritanceType.Type);
        }
        TrackedObject trackedObject = this.context.Services.ChangeTracker.GetTrackedObject(entity);
        if ((trackedObject != null) && !trackedObject.IsWeaklyTracked)
        {
            throw Error.CannotAttachAlreadyExistingEntity();
        }
        if (trackedObject == null)
        {
            trackedObject = this.context.Services.ChangeTracker.Track(entity, true);
        }
        trackedObject.ConvertToPossiblyModified(original);
        if (this.Context.Services.InsertLookupCachedObject(inheritanceType, entity) != entity)
        {
            throw new DuplicateKeyException(entity, Strings.CantAddAlreadyExistingKey);
        }
        trackedObject.InitializeDeferredLoaders();
    }
    

    Sunday, April 20, 2008 9:04 AM
  • This is obviously very different. It checks that the objects are sensible. Then in GetTrackedObject:

    internal override TrackedObject GetTrackedObject(object obj)
    {
        StandardTrackedObject obj2;
        if (!this.items.TryGetValue(obj, out obj2) && this.IsFastTracked(obj))
        {
            return this.PromoteFastTrackedObject(obj);
        }
        return obj2;
    }
    
    it will either return an existing tracking object, or more probably in my case, magic up a fast tracked object and return that.

    That done, it will add my new entity to the tracking system manually, and then tag it as possibly modified based on the values in my original object:

     

    internal override void ConvertToPossiblyModified(object originalState)
    {
        this.state = State.PossiblyModified;
        this.original = this.CreateDataCopy(originalState);
        this.isWeaklyTracked = false;
    }


     

    I assume that when you call submit changes it will paw through these new and original states, go " oh look they've changed" and construct an update statement accordingly.

     

    This all seems sensible. I am however at a loss to what the default functionality thinks it's doing. I adds an old item first, without indicating that it's an updated item! I can only assume that because they believe the ViewState will persist the entire damn sub-select of the source table, the attach will see the existing item, and mark it as an update. Please say it isn't so!

     

    I can see why what's happening is happening..... I just can't fathom why you would do it that way!

     

    What is wrong with the way I'm doing it? They hand ME the new and old entities I just hand them straight back and go yup, make it happen. And it works. No extra overhead I can see, and no massive viewstate. I assume the old object is stored in the control state - fair enough, that's one object while you are editing the row.

     

    In conclusion, pending someone pointing out how totally wrong and moronic I am (which I am perfectly willing to believe):

     

    I would say that LinqDataSources cannot be used for updates with the viewstate turned off.  

     

    As such they are like the old chocolate teapot. Great with cold water .... no so much when you really want to use em.

     

     

    Please show me I'm wrong.

     

    Off now to stop wasting time, and rewrite this lot with a proper Entity Mapper - thinking NHibernate atm.

     

     

    Sunday, April 20, 2008 9:30 AM
  • I wonder is this related to an issue I'm having. I have a Formview containing a GridView using LinqDataSources. I want to tick the boxes in the GridView and when I perform an insert I insert the header (Formview) and child (GridView) details.

     

    It's all very bog standard stuff that I've done before. The problem is once I save I'm getting a 'not marked as serializable' error. My (limited) understanding is that this isn't serializable due to delayed loading (how can you serialize the data when you don't have it), but why would I want to serialize the LinqDataSource anyway? I've tried to disable ViewState on the datasource but it seems to make no difference.

     

    Interested to hear any thoughts on this.

    Cheers!

     

    Tuesday, April 22, 2008 9:47 PM
  • Ok, I fixed my ''not marked as serializable' as follows:

     

    1. Creat a partial class and marked it as serializable

    2. Returned a list

    ProjectsDataContext db = new ProjectsDataContext();

    assignee = db.GetTable<ProjectAssignee>().Where(p => p.GroupID == GroupID).ToList();

    3. Binding to an ObjectDataSource instead of a LinqDataSource

     

    It just seems a bit long winded, I thought I could just switch off ViewState on my LinqDataSource. This way does work though.

    Tuesday, April 22, 2008 10:01 PM
  •  

    Hmm, starts to look a bit ropey doesn't it. Great in demo, but all the wizzy magic behind the scences stuff really catches up to you on stuff like this.

     

    As we both found, LinqDataSource just doesn't seem to have been tried out much in these real world situations. Or at-least the solutions are so un-intuative and the learning curve so steep as to render any benefit moot. We're back to ObjectDataSource, and probably business objects anyway . ... so exactly what is linq-to-sql doing for us? If you are still using Stored Proceedures (as many people do) then it's a very fancy mapping from the stored procs to the entitys. But you can get all the funky inline language stuff from regular Linq, with your own objects..... if you actually want it, and I'm not totally sure I do. But it's there if you want it and not if you don't.

     

    I'm struggling to see the point of Linq-To-SQL in a real development environment. It seems really funky, but  I can't actually pin down an advantage. Not writing your own SQL is cool I guess, but 90% of my SQL is going to be CRUD in various forms, and you can get code gen tools to do all that. If there weren't any downsides to using the linq stuff I'd say the othe 10% of SQL writting would be worth the hasle....but these flaws get you wondering what else is waiting out there to cause grief in the middle of a big project.

     

    The "just delete the entity on the diagram and drag it back on stuff to update the database changes" doesn't exactly fill me with joy either. If you have a bunch of settings on the entity, you loose them.

     

    I think it's definately a wait until the next version case for me.

     

    Ben

    Wednesday, April 23, 2008 1:11 PM
  • The ASP.NET team are responsible for the ListView controls and also the LinqDataSource components and because they have their own forums over at http://forums.asp.net/ would not have actually seen your post to be able to offer assistance on such scenarios.

     

    Sorry we could not be of more assistance,

     

    [)amien

     

     

    Tuesday, June 10, 2008 5:39 PM
    Moderator
  • I usually received this error: Throws the same old exception: Cannot add an entity with a key that is already in use.

    when I have a RadioButtionList bound to a datasource (SQLDataSource1), then i try to programatically change it's datasource to SQLDataSource2.  What I do to fix this issue is to un-bind the RadioButtoList, then in PageLoad, bind it programatically to whichever datasource I need.  Not sure if this is the cause for your error, but in my case it was.  Hope this helps.
    Tuesday, July 15, 2008 5:26 PM