views:

320

answers:

5

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?

A: 

It could be shortened to.

if(invoiceDb == null) ctx.Invoices.InsertOnSubmit(invoiceDB = new Invoice() {Number = invoiceNumber});
Adam Robinson
but then invoiceDb isn't assigned
TheSoftwareJedi
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.
Adam Robinson
The entity is now assigned.
Adam Robinson
A: 

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

var invoice = ctx.Invoices.SingleOrDefault(a => a.InvoicerId == InvoicerId &&
                                             a.Number == invoiceNumber) ??
                          new Invoice();
CMS
This will not add a new invoice to the table, however.
Adam Robinson
See also: http://en.wikipedia.org/wiki/%3F%3F_Operator (I wrote the first version of that article, so I link it whenever I can)
Matt Grande
Still needs the logic to determine if an insert is needed
TheSoftwareJedi
+1  A: 

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);
TheSoftwareJedi
That will always set the "Number" property on the entity, marking it as dirty. This can/will give you unnecessary updates when performing SubmitChanges()
Adam Robinson
+1  A: 

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? :)

RobS
A: 

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 Function

End Module
Paul Hutchinson