31

How can I store an array of doubles to database using Entity Framework Code-First with no impact on the existing code and architecture design?

I've looked at Data Annotation and Fluent API, I've also considered converting the double array to a string of bytes and store that byte to the database in it own column.

I cannot access the public double[] Data { get; set; } property with Fluent API, the error message I then get is:

The type double[] must be a non-nullable value type in order to use it as parameter 'T'.

The class where Data is stored is successfully stored in the database, and the relationships to this class. I'm only missing the Data column.

jonas
  • 1,592
  • 3
  • 20
  • 29
  • What does the data represent? Maybe there's a way to tweak your code to make everything work without changing the architecture too much. – Nathan White Mar 05 '13 at 10:05
  • Or, for a simple fix, could you not make a comma-separated string with all of the double values when writing to the database, then parse the string when you need the values? – Nathan White Mar 05 '13 at 10:06
  • @NathanWhite going with this comma separated string approach is something we're actually considering. But we would like something more automated from EF. Considering the answers to this tells me that there is no way to automatically say array of doubles to the database with EF. Correct? – jonas Mar 05 '13 at 10:22
  • 2
    Unfortunately, in EF, an array of primitives is not possible. Consider it if you were to apply an SQL statement to map your database... how would you store an array of doubles anyway? Mapping `List<>` of objects is possible because EF creates a resolved many-to-many relationship table set. With primitives, this is unfortunately not possible. It would be possible, however, if you created a class specifically to hold a double (with an ID because EF expects it) and have a List in your model. – Nathan White Mar 05 '13 at 10:37
  • 1
    Some RDBMSes, like PostgreSQL, support arrays. This very issue is what stops me from using Entity Framework with PostgreSQL. – NathanAldenSr Sep 03 '16 at 22:57
  • Use JSON string as dababase propery, then a function in object to retrive by index or whole array. – Sith2021 May 15 '19 at 03:07

10 Answers10

51

You can do a thing like this :

    [NotMapped]
    public double[] Data
    {
        get
        {
            string[] tab = this.InternalData.Split(',');
            return new double[] { double.Parse(tab[0]), double.Parse(tab[1]) };
        }
        set
        {
            this.InternalData = string.Format("{0},{1}", value[0], value[1]);
        }
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public string InternalData { get; set; }
Joffrey Kern
  • 6,449
  • 3
  • 25
  • 25
30

Thank you all for your inputs, due to your help I was able to track down the best way to solve this. Which is:

 public string InternalData { get; set; }
 public double[] Data
 {
    get
    {
        return Array.ConvertAll(InternalData.Split(';'), Double.Parse);                
    }
    set
    {
        _data = value;
        InternalData = String.Join(";", _data.Select(p => p.ToString()).ToArray());
    }
 }

Thanks to these stackoverflow posts: String to Doubles array and Array of Doubles to a String

Community
  • 1
  • 1
jonas
  • 1,592
  • 3
  • 20
  • 29
  • 4
    In case anyone is wondering, `InternalData` has to be public, or it will not be part of the database schema. It may also be a good idea to add `[NotMapped]` to the `Data` attribute, even though it is unnecessary. This makes it clear that the attribute is not part of the database schema. – Florian Winter Oct 02 '17 at 11:37
  • The setter can be simplified to `set => InternalData = string.Join(";", value);` – Rudey Jan 08 '21 at 15:33
  • You could use a value converter, but I would recommend a child table. https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions?tabs=data-annotations – Jeremy Lakeman Apr 15 '21 at 03:28
  • 1
    I don't think this solution is complete. Every time the array is index accessed, the array is automatically recreated from the string. In my opinion, it would be better for `get` to return `_data`. Another problem is when you go to set a single element: this is not stored because it passes through `get` and not `set`! Try to declare `Data=new []{0,0,0}` then call `Data[1]=2` and try to read back `Data[1]`. You will see that it is 0 – tedebus Dec 15 '21 at 14:34
5

I know it is a bit expensive, but you could do this

class Primitive
{
    public int PrimitiveId { get; set; }
    public double Data { get; set; }

    [Required]
    public Reference ReferenceClass { get; set; }
}

// This is the class that requires an array of doubles
class Reference
{
    // Other EF stuff

    // EF-acceptable reference to an 'array' of doubles
    public virtual List<Primitive> Data { get; set; }
}

This will now map a single entity (here 'Reference') to a 'list' of your Primitive class. This is basically to allow the SQL database to be happy, and allow you to use your list of data appropriately.

This may not suit your needs, but will be a way to make EF happy.

Nathan White
  • 1,082
  • 7
  • 21
4

It would be far easier if you use List<double> rather then double[]. You already have a table that stores your Data values. You probably have foreign key from some table to the table where your double values are stored. Create another model that reflects the table where doubles are stored and add foreign key mappings in the mappings class. That way you will not need to add some complex background logic which retrieves or stores values in a class property.

Huske
  • 9,186
  • 2
  • 36
  • 53
3

In my opinion almost all other answers work on the opposite of how it should be. Entity EF should manage the string and the array must be generated from it. So the array must be whole read and written only when the string is accessed by EF.

A solution involving logic on Data[] is wrong because, as I wrote in a comment, you would run into paradoxical conditions. In all other conditions the variable must remain a pure array. By putting the "get" and "set" logic in Data[], as I've seen so far, this happens:

1 - Every time an index access is made to the array, the array is automatically recreated from the string. This is a useless work, think of an iteration in a loop...

2 - when you go to set a single element it is not stored because it passes through "get" and not "set". If you try to declare Data=new []{0,0,0} then set Data[1]=2 , going to re-read Data[1] the result is 0.

My solution is to completely turn the logic around.

    public string Data_string
    {
        get => string.Join(';', Data??Array.Empty());
        set => Data= value == null ? Array.Empty<double>() : Array.ConvertAll(value.Split(';',StringSplitOptions.RemoveEmptyEntries), double.Parse);
    }
   
    [NotMapped]
    public double[] Data {get;set;}
   

Obviously this only applies to storing and retrieving data on databases, access to Data_string is exclusive to EF.

Once the string is read from the DB it is associated to Data_string which, through set, creates the Data array. At this point you can work on Data without affecting the string in any way, like a normal array. When you will ask EF to save in the DB, through the get in the Data_string property, the string will be completely reconstructed based on the Data elements and then stored as a string.

Practically the string is modified only twice, at the moment of reading from the DB and at the moment of saving.

In my opinion this solution is much more efficient than operating continuously on the string.

tedebus
  • 978
  • 13
  • 20
1

Nathan White has the best answer (got my vote).

Here is a small improvement over Joffrey Kern's answer to allow lists of any length (untested):

    [NotMapped]
    public IEnumerable<double> Data
    {
        get
        {
            var tab = InternalData.Split(',');
            return tab.Select(double.Parse).AsEnumerable();
        }
        set { InternalData = string.Join(",", value); }
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public string InternalData { get; set; }
Jacob Brewer
  • 2,574
  • 1
  • 22
  • 25
1

Don't use double[] use List insted.

Like this.

public class MyModel{
    ...
    public List<MyClass> Data { get; set; }
    ...
}

public class MyClass{
    public int Id { get; set; }
    public double Value { get; set; }
}

All that solution that I see there are bad, because:

  1. If you create table, you don't want to store data like this: "99.5,89.65,78.5,15.5" that's not valid! Firstly its a string that means if you can type letter into it and at the moment when your ASP.NET server call double.Parse it will result in FormatException and that you really don't want!

  2. It's slower, because your server must parse the string. Why parse the string instead getting almost ready data from SQL Server to use?

wonea
  • 4,783
  • 17
  • 86
  • 139
Pavel B.
  • 843
  • 1
  • 7
  • 17
  • 1
    You are not wrong. At the time though, I couldn't change the datatype. Today I would create a new model just for the database layer, and either store it as a List, or just put the raw json/xml inside a data property. I wrote this question 5 years ago, so my memory of the situation is a little dusty. – jonas May 29 '18 at 07:55
  • I wonder if it string parsing is really slower than your solution, which involves SQL joins. – Rudey Jan 08 '21 at 15:28
1

i know this post is Ancient, but in case someone still needs to do something like this, PLEASE DO NOT USE THE ABOVE SOLUTIONS,

as the above solutions are EXTREMELY inefficient (Performance and Disk Space wise).., the best way is to store the array as a Byte array

    public byte[] ArrayData;
    
    [NotMapped]
    public double[] Array {
        get {
            var OutputArray = new double[ArrayData.Length / 8];

            for (int i = 0;i < ArrayData.Length / 8;i++)
                OutputArray[i] = BitConverter.ToDouble(ArrayData, i * 8);

            return OutputArray;
        }

        set {
            var OutputData = new byte[value.Length * 8];

            for (int i = 0;i < value.Length;i++) {
                var BinaryValue = BitConverter.GetBytes(value[i]);

                OutputData[(i*8)] = BinaryValue[0];
                OutputData[(i*8)+1] = BinaryValue[1];
                OutputData[(i*8)+2] = BinaryValue[2];
                OutputData[(i*8)+3] = BinaryValue[3];
                           
                OutputData[(i*8)+4] = BinaryValue[4];
                OutputData[(i*8)+5] = BinaryValue[5];
                OutputData[(i*8)+6] = BinaryValue[6];
                OutputData[(i*8)+7] = BinaryValue[7];
            }

            ArrayData = OutputData;
        }
    }

`

And if you need more performance, you can go for Unsafe code and use pointers .. instead of BitConverter ..

This is way better than saving double values (that can get huge) as string, then spliting the string array !! and then parsing the strings to double !!!

These getter/setters work on the whole array, but if you need to get just one item from the array, you can make a function that gets a single item from the array with a complexity of O(1) :

for Get :

    public double Array_GetValue(int Index) {
        return BitConverter.ToDouble(ArrayData, Index * 8);
    }

for Set :

    public void Array_SetValue(int Index, double Value) {
        var BinaryValue = BitConverter.GetBytes(Value);

        ArrayData[(Index*8)] = BinaryValue[0];
        ArrayData[(Index*8)+1] = BinaryValue[1];
        ArrayData[(Index*8)+2] = BinaryValue[2];
        ArrayData[(Index*8)+3] = BinaryValue[3];

        ArrayData[(Index*8)+4] = BinaryValue[4];
        ArrayData[(Index*8)+5] = BinaryValue[5];
        ArrayData[(Index*8)+6] = BinaryValue[6];
        ArrayData[(Index*8)+7] = BinaryValue[7];
    }
Robert258
  • 136
  • 7
0

If your collection can be null or empty, and you want this to be preserved, do this:

[NotMapped]
public double[] Data
{
    get => InternalData != null ? Array.ConvertAll(Data.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), double.Parse) : null;
    set => InternalData = value != null ? string.Join(";", value) : null;
}

Also, specify [Column(TypeName = "varchar")] on the string property for a more efficient storage data type.

Rudey
  • 4,717
  • 4
  • 42
  • 84
-1

A perfect enhancement to @Jonas's answer will be to add the necessary annotations. So, a cleaner version would be

[EditorBrowsable(EditorBrowsableState.Never)]
[JsonIgnore]
public string InternalData { get; set; }

[NotMapped]
public double[] Data
{
    get => Array.ConvertAll(InternalData.Split(';'), double.Parse);
    set 
    {
        InternalData = string.Join(";", value.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray());
    }
}

The [JsonIgnore] Annotation will ignore the InternalData field from JSON serialization and Swagger UI.

[EditorBrowsable(EditorBrowsableState.Never)] will hide the public method from the IDE IntelliSense

samtax01
  • 834
  • 9
  • 11