3

Quite often in my LINQ to SQL code, I need to "find or create" an entity as such:

var invoiceDb = ctx.Invoices.FirstOrDefault(a => a.InvoicerId == InvoicerId &&
                                                 a.Number == invoiceNumber);
if (invoiceDb == null)
{
    invoiceDb = new Invoice();
    invoiceDb.Number = invoiceNumber;
    ctx.Invoices.InsertOnSubmit(invoiceDb);
}

I'm looking to make this a generic method... Any good ideas?

5 Answers 5

2

I came up with these extension methods that seems to work well for me.

    public static T FindOrCreate<T>(this Table<T> table, Func<T, bool> find, Action<T> create) where T : class, new()
    {
        T val = table.FirstOrDefault(find);
        if (val == null)
        {
            val = new T();
            create(val);
            table.InsertOnSubmit(val);
        }
        return val;
    }

    public static T FindOrCreate<T>(this Table<T> table, Func<T, bool> find) where T : class, new()
    {
        return FindOrCreate(table, find, a => { });
    }

And it's used like so:

    var invoiceDb = ctx.Invoices.FindOrCreate(a => a.InvoicerId == InvoicerId &&
                                                     a.Number == invoiceNumber);
    invoiceDb.Number = invoiceNumber;

Or

    var invoiceDb = ctx.Invoices.FindOrCreate(a => a.InvoicerId == InvoicerId &&
                                                     a.Number == invoiceNumber,
                                              a => a.Number = invoiceNumber);
Sign up to request clarification or add additional context in comments.

1 Comment

That will always set the "Number" property on the entity, marking it as dirty. This can/will give you unnecessary updates when performing SubmitChanges()
2

VB.NET version:

Module dbi
    <System.Runtime.CompilerServices.Extension()> _
    Public Function FindOrCreate( _
        Of T As {Class, New})(ByVal table As Data.Linq.Table(Of T), _
        ByVal find As Func(Of T, Boolean), _
        ByVal create As Action(Of T)) _
        As T

        Dim val As T = table.FirstOrDefault(find)
        If val Is Nothing Then
            val = New T()
            create(val)
            table.InsertOnSubmit(val)
        End If
        Return val
    End Function

    <System.Runtime.CompilerServices.Extension()> _
    Public Function FindOrCreate( _
        Of T As {Class, New})(ByVal table As Data.Linq.Table(Of T), _
        ByVal find As Func(Of T, Boolean)) _
        As T

        Return FindOrCreate(table, find, Function(a))
    End Function

End Module

Comments

1

How about using an extension method like so:

public static T FirstOrCreate<T>(this IEnumerable<T> source) where T : class, new()
{
    var result = source.FirstOrDefault();
    return result != null ? result : new T();
}

If you want it to be able to accept a predicate, you can use this definition:

public static T FirstOrCreate<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate) where T : class, new()
{
    var result = source.FirstOrDefault(predicate);
    return result != null ? result : new T();
}

That way you can use it in place of FirstOrDefault() like so:

Invoice selectedInvoice = (from i in Invoices
                           where i.ID == invoiceID
                           select i).FirstOrCreate();

..or with the use of a Predicate:

Invoice selectedInvoice = db.Invoices.FirstOrCreate(i => i.ID == invoiceID);

Will either return a matching entity or a new (non-null) entity instead.

Edit: I've been thinking about this today, and I occurs to me that the above will require you to detect that the entity is new (not existing) and attach it to the DataContext, so I came up with this compromise, using the same approach:

public static T FirstOrCreate<T>(this IEnumerable<T> source, DataClassesDataContext db) where T : class, new()
{
    var result = source.FirstOrDefault();
    if (result == null)
    {
        result = new T();
        db.GetTable<T>().InsertOnSubmit(result);
    }
    return result;
}

The drawback is you have to pass the DataContext in as a parameter, but it should work nicely enough:

Customer selectedCustomer = (from c in db.Customers
                             where c.CustomerId == selectedCustomerId
                             select c).FirstOrCreate(db);

Surely one upvote is out there? :)

Comments

0

You could use the Null-Coalescing Operator (??)

var invoice = ctx.Invoices.SingleOrDefault(a => a.InvoicerId == InvoicerId &&
                                             a.Number == invoiceNumber) ??
                          new Invoice();

3 Comments

This will not add a new invoice to the table, however.
See also: en.wikipedia.org/wiki/%3F%3F_Operator (I wrote the first version of that article, so I link it whenever I can)
Still needs the logic to determine if an insert is needed
0

It could be shortened to.

if(invoiceDb == null) ctx.Invoices.InsertOnSubmit(invoiceDB = new Invoice() {Number = invoiceNumber});

2 Comments

but then invoiceDb isn't assigned
Really? A downvote because I didn't give you a complete pasteable code example? It's intended to show you HOW to do it, not do it for you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.